HEX
Server: nginx/1.30.4
System: Linux d-twindigital-web-1.host-ed.eu 4.18.0-553.141.2.el8_10.x86_64 #1 SMP Wed Jul 8 10:28:18 EDT 2026 x86_64
User: u_twindigital_html (1009)
PHP: 8.3.30
Disabled: NONE
Upload Files
File: //proc/thread-self/root/bin/phan.phar
#!/usr/bin/env php
<?php

Phar::mapPhar('phan.phar');

require 'phar://phan.phar/src/phan.php';

__HALT_COMPILER(); ?>
���	phan.pharsrc/phan.php�/)�Z���7�src/spl_object_id.php�/)�Z�0E$��src/requirements.php�/)�Z�7稶'src/Phan/Language/NamespaceMapEntry.php�/)�Z��:�ƶsrc/Phan/Language/Type.php*/)�Z*�}?ݶ;src/Phan/Language/FQSEN/FullyQualifiedClassConstantName.php}/)�Z}���߶6src/Phan/Language/FQSEN/FullyQualifiedPropertyName.php�/)�Z��SN�)src/Phan/Language/FQSEN/AbstractFQSEN.php*/)�Z*7���:src/Phan/Language/FQSEN/FullyQualifiedFunctionLikeName.php/)�Z5�ﭶAsrc/Phan/Language/FQSEN/FullyQualifiedGlobalStructuralElement.php#&/)�Z#&��v�4src/Phan/Language/FQSEN/FullyQualifiedMethodName.php6	/)�Z6	���3src/Phan/Language/FQSEN/FullyQualifiedClassName.phpj/)�Zj����(src/Phan/Language/FQSEN/Alternatives.php
	/)�Z
	�E���6src/Phan/Language/FQSEN/FullyQualifiedConstantName.php�/)�Z��!b��6src/Phan/Language/FQSEN/FullyQualifiedClassElement.php/)�Z���Ҷ<src/Phan/Language/FQSEN/FullyQualifiedGlobalConstantName.phpi/)�Zi5m-�6src/Phan/Language/FQSEN/FullyQualifiedFunctionName.php�/)�Z�.ӣy�src/Phan/Language/UnionType.php�./)�Z�.k񕸶src/Phan/Language/Scope.php�$/)�Z�$8��Ͷsrc/Phan/Language/FQSEN.php�/)�Z���U6�*src/Phan/Language/Element/TypedElement.php�/)�Z��x��.src/Phan/Language/Element/TraitAliasSource.phps/)�Zs�U�7src/Phan/Language/Element/UnaddressableTypedElement.php�/)�Z�ݬ��#src/Phan/Language/Element/Clazz.php
S/)�Z
SO"@�"src/Phan/Language/Element/Func.php�,/)�Z�,��ڶ9src/Phan/Language/Element/AddressableElementInterface.phpQ/)�ZQ	~�W�+src/Phan/Language/Element/ConstantTrait.php�/)�Z�n��,src/Phan/Language/Element/GlobalConstant.php�
/)�Z�
pG���&src/Phan/Language/Element/Property.php{/)�Z{���&src/Phan/Language/Element/Variable.php�/)�Z�����0src/Phan/Language/Element/ClosedScopeElement.php�/)�Z���t�+src/Phan/Language/Element/ClassConstant.php�/)�Z��F@b�0src/Phan/Language/Element/AddressableElement.php�/)�Z��y�/src/Phan/Language/Element/FunctionInterface.php�,/)�Z�,Ky�ض+src/Phan/Language/Element/FunctionTrait.php�/)�Z�(߬��.src/Phan/Language/Element/ClassAliasRecord.php�/)�Z�D�:�*src/Phan/Language/Element/ClassElement.php5/)�Z5k賕�-src/Phan/Language/Element/FunctionFactory.php$/)�Z$xUȶ#src/Phan/Language/Element/Flags.phpU	/)�ZU	3ж4src/Phan/Language/Element/ElementFutureUnionType.php/)�Z
��H�/src/Phan/Language/Element/Comment/Parameter.php�/)�Z��T��,src/Phan/Language/Element/Comment/Method.php'/)�Z'��vֶ3src/Phan/Language/Element/TypedElementInterface.phpS	/)�ZS	���R�'src/Phan/Language/Element/Parameter.phpC/)�ZC���X�/src/Phan/Language/Element/ConstantInterface.php�/)�Z�g�+�5src/Phan/Language/Element/PassByReferenceVariable.php�	/)�Z�	�d�%src/Phan/Language/Element/Comment.php��/)�Z��S�4�.src/Phan/Language/Element/TraitAdaptations.php�/)�Z�jIco�/src/Phan/Language/Element/VariadicParameter.phpA/)�ZA�;Q�$src/Phan/Language/Element/Method.php�X/)�Z�X�򐒶src/Phan/Language/Context.php�[/)�Z�[w�'src/Phan/Language/Scope/ClosedScope.php}/)�Z}��!u�(src/Phan/Language/Scope/ClosureScope.php�/)�Z�$��-src/Phan/Language/Scope/FunctionLikeScope.php/)�Z�>��&src/Phan/Language/Scope/ClassScope.php�/)�Z�1u�Y�'src/Phan/Language/Scope/BranchScope.php</)�Z<9�I߶)src/Phan/Language/Scope/PropertyScope.php�/)�Z�7*�'src/Phan/Language/Scope/GlobalScope.php�
/)�Z�
�ݡ��&src/Phan/Language/UnionTypeBuilder.phpI/)�ZI��4�?src/Phan/Language/Internal/FunctionSignatureMap_php71_delta.php�
/)�Z�
����?src/Phan/Language/Internal/FunctionSignatureMap_php72_delta.php�0/)�Z�0��恶1src/Phan/Language/Internal/DynamicPropertyMap.php�/)�Z�2�5��3src/Phan/Language/Internal/FunctionSignatureMap.php�S/)�Z�S�]ƶ*src/Phan/Language/Internal/PropertyMap.php0-/)�Z0-�6�ض6src/Phan/Language/Type/ClosureDeclarationParameter.php

/)�Z

�ƈж&src/Phan/Language/Type/ClosureType.php�/)�Z�l�u��'src/Phan/Language/Type/ResourceType.php�/)�Z��r�<�$src/Phan/Language/Type/FalseType.php/)�Z�-���#src/Phan/Language/Type/NullType.php/)�Z%ei)�$src/Phan/Language/Type/MixedType.php�/)�Z�����2src/Phan/Language/Type/CallableDeclarationType.php/)�Z���)src/Phan/Language/Type/ArrayShapeType.phpl2/)�Zl2�s��%src/Phan/Language/Type/StaticType.php�	/)�Z�	>���#src/Phan/Language/Type/VoidType.php�/)�Z��ƂK�#src/Phan/Language/Type/TrueType.php#/)�Z#f���#src/Phan/Language/Type/BoolType.php/)�ZLI�x�'src/Phan/Language/Type/TemplateType.php//)�Z/D�ƨ�$src/Phan/Language/Type/MultiType.php/)�Z=���$src/Phan/Language/Type/FloatType.php�/)�Z�>�!��6src/Phan/Language/Type/FunctionLikeDeclarationType.phpA/)�ZAi�¶'src/Phan/Language/Type/CallableType.phpg/)�Zg+jR�$src/Phan/Language/Type/ArrayType.php�/)�Z���n��'src/Phan/Language/Type/IterableType.php./)�Z.L�gܶ0src/Phan/Language/Type/GenericMultiArrayType.phpu/)�Zu��
�(src/Phan/Language/Type/ScalarRawType.php�/)�Z��ٿ�%src/Phan/Language/Type/NativeType.phpx/)�Zx�KaQ�%src/Phan/Language/Type/ObjectType.php*/)�Z*����1src/Phan/Language/Type/ClosureDeclarationType.php/)�Z��^v�%src/Phan/Language/Type/StringType.php�/)�Z��M��"src/Phan/Language/Type/IntType.php�/)�Z��+�+src/Phan/Language/Type/GenericArrayType.phpL@/)�ZL@�:���%src/Phan/Language/Type/ScalarType.php
/)�Z
�tRx�$src/Phan/Language/EmptyUnionType.php�a/)�Z�a�FO��(src/Phan/Language/AnnotatedUnionType.php�
/)�Z�
W��%src/Phan/Language/FutureUnionType.php�/)�Z������src/Phan/Language/FileRef.php/)�Z˅���src/Phan/Plugin.phpD
/)�ZD
��ض(src/Phan/Exception/CodeBaseException.php�/)�Z�G�k�$src/Phan/Exception/TypeException.phpe/)�Ze�8���*src/Phan/Exception/EmptyFQSENException.php�/)�Z���Qo�,src/Phan/Exception/UnanalyzableException.phpp/)�Zpn#
�%src/Phan/Exception/IssueException.php�/)�Z�Y�e�$src/Phan/Exception/NodeException.php�/)�Z�cwP��!src/Phan/Debug/DebugUnionType.phpv/)�Zv�]UQ�src/Phan/Debug/Breakpoint.php/)�Z!f1�src/Phan/Phan.phpP\/)�ZP\�CG�src/Phan/Profile.phpB
/)�ZB
�E2!�src/Phan/CodeBase.phpo�/)�Zo��=�src/Phan/CLI.php��/)�Z��c��'src/Phan/Config/InitializedSettings.php�/)�Z��ɀ��src/Phan/Config/Initializer.php~O/)�Z~O�
-#�src/Phan/PluginV2.php_/)�Z_$f^�)src/Phan/LanguageServer/ClientHandler.phpP/)�ZP�-�*src/Phan/LanguageServer/ProtocolWriter.php�/)�Z��j��0src/Phan/LanguageServer/ProtocolStreamWriter.php		/)�Z		�(��*src/Phan/LanguageServer/LanguageServer.phpN�/)�ZN�ݛ�u�!src/Phan/LanguageServer/Utils.phpv/)�Zv/8[�/src/Phan/LanguageServer/Client/TextDocument.php./)�Z.^� �*src/Phan/LanguageServer/ProtocolReader.php!/)�Z!|�tU�'src/Phan/LanguageServer/IdGenerator.php�/)�Z��\�ö*src/Phan/LanguageServer/LanguageClient.phpR/)�ZR�>t�/src/Phan/LanguageServer/Server/TextDocument.phpW/)�ZW���u�,src/Phan/LanguageServer/Server/Workspace.php�
/)�Z�
�`
�0src/Phan/LanguageServer/ProtocolStreamReader.php/)�Z�s��"src/Phan/LanguageServer/Logger.php�/)�Z��m�M�.src/Phan/LanguageServer/Protocol/FileEvent.phpi/)�ZiĀ0J�;src/Phan/LanguageServer/Protocol/TextDocumentIdentifier.php�/)�Z�bB(�5src/Phan/LanguageServer/Protocol/InitializeResult.php�/)�Z�P��<src/Phan/LanguageServer/Protocol/TextDocumentSyncOptions.php/)�Z� �7src/Phan/LanguageServer/Protocol/ServerCapabilities.php�	/)�Z�	&�ٳ�/src/Phan/LanguageServer/Protocol/Diagnostic.php�/)�Z�@����0src/Phan/LanguageServer/Protocol/SaveOptions.php�/)�Z��h1��*src/Phan/LanguageServer/Protocol/Range.php�/)�Z�E�s��Dsrc/Phan/LanguageServer/Protocol/VersionedTextDocumentIdentifier.php�/)�Z�qǚ3�Csrc/Phan/LanguageServer/Protocol/TextDocumentContentChangeEvent.php�/)�Z��D�R�3src/Phan/LanguageServer/Protocol/FileChangeType.php/)�Z�Dc�7src/Phan/LanguageServer/Protocol/ClientCapabilities.php/)�ZN
dc�-src/Phan/LanguageServer/Protocol/Position.phpQ/)�ZQ�Cv�5src/Phan/LanguageServer/Protocol/TextDocumentItem.php�/)�Z��H��7src/Phan/LanguageServer/Protocol/DiagnosticSeverity.phpK/)�ZK��)<�,src/Phan/LanguageServer/Protocol/Message.phpl/)�Zl��9src/Phan/LanguageServer/Protocol/TextDocumentSyncKind.php/)�Z����'src/Phan/LanguageServer/FileMapping.phpp/)�Zp�ӟ��src/Phan/Config.php��/)�Z���O�/src/Phan/Daemon/Transport/CapturerResponder.php�/)�Z���O�-src/Phan/Daemon/Transport/StreamResponder.php�
/)�Z�
9���'src/Phan/Daemon/Transport/Responder.phpA/)�ZA���!src/Phan/Daemon/ExitException.phpb/)�Zb"1��src/Phan/Daemon/Request.php�S/)�Z�S���#src/Phan/Plugin/ClosuresForKind.php/)�Z$ڃ�#src/Phan/Plugin/ConfigPluginSet.php�u/)�Z�uy��,src/Phan/Plugin/Internal/MiscParamPlugin.phpU0/)�ZU0����>src/Phan/Plugin/Internal/DependentReturnTypeOverridePlugin.php�/)�Z��xV�<src/Phan/Plugin/Internal/ClosureReturnTypeOverridePlugin.php�,/)�Z�,ħ�^�0src/Phan/Plugin/Internal/CallableParamPlugin.php�/)�Z�E7�~�*src/Phan/Plugin/Internal/CompactPlugin.php�
/)�Z�
�a�1src/Phan/Plugin/Internal/StringFunctionPlugin.php^$/)�Z^$*4�(�:src/Phan/Plugin/Internal/ArrayReturnTypeOverridePlugin.phpgL/)�ZgL>�0��(src/Phan/Plugin/PluginImplementation.php/)�Z:#�r�src/Phan/Bootstrap.php�
/)�Z�
S2�l�3src/Phan/PluginV2/PluginAwarePreAnalysisVisitor.phpW/)�ZW$��ܶ1src/Phan/PluginV2/BeforeAnalyzeFileCapability.phpE/)�ZE�B��4src/Phan/PluginV2/PluginAwareBaseAnalysisVisitor.php�
/)�Z�
�w߶3src/Phan/PluginV2/AnalyzeFunctionCallCapability.php�/)�Z�����"src/Phan/PluginV2/IssueEmitter.php�	/)�Z�	�{��/src/Phan/PluginV2/AnalyzeFunctionCapability.php�/)�Z�a�.�-src/Phan/PluginV2/AnalyzeMethodCapability.php�/)�Z�>Z5ö,src/Phan/PluginV2/AnalyzeClassCapability.php�/)�Z�N��B�0src/Phan/PluginV2/StopParamAnalysisException.phpj/)�ZjI
�0src/Phan/PluginV2/PluginAwareAnalysisVisitor.php|/)�Z|�0�j�+src/Phan/PluginV2/AnalyzeNodeCapability.php�/)�Z�@��u�/src/Phan/PluginV2/AnalyzePropertyCapability.php�/)�Z��(��.src/Phan/PluginV2/PreAnalyzeNodeCapability.php�/)�Z����5�0src/Phan/PluginV2/AfterAnalyzeFileCapability.php@/)�Z@Kk���1src/Phan/PluginV2/LegacyAnalyzeNodeCapability.php�/)�Z��@}�2src/Phan/PluginV2/ReturnTypeOverrideCapability.php�/)�Z�tA�5src/Phan/PluginV2/LegacyPostAnalyzeNodeCapability.php�/)�Z�Y�϶4src/Phan/PluginV2/PluginAwarePostAnalysisVisitor.php�/)�Z��O�Ѷ/src/Phan/PluginV2/FinalizeProcessCapability.php�/)�Z��\�4src/Phan/PluginV2/LegacyPreAnalyzeNodeCapability.php�/)�Z��we��/src/Phan/PluginV2/PostAnalyzeNodeCapability.php�/)�Z��듲�src/Phan/Parse/ParseVisitor.php5�/)�Z5��\ʑ�src/Phan/Ordering.php/)�Zؼ�[�src/Phan/Memoize.php�/)�Z���w�src/Phan/Daemon.php�(/)�Z�(����&src/Phan/Library/Hasher/Sequential.php�/)�Z��	,�&src/Phan/Library/Hasher/Consistent.php[
/)�Z[
3ž��!src/Phan/Library/StderrLogger.php/)�Z2.�-�src/Phan/Library/None.php�/)�Z�Dx
�src/Phan/Library/Tuple3.phpw/)�Zwx����src/Phan/Library/Tuple4.php�/)�Z�)��src/Phan/Library/Map.php/)�Z�����src/Phan/Library/Option.php�/)�Z��;���src/Phan/Library/FileCache.php�
/)�Z�
�b���src/Phan/Library/Tuple.php�/)�Z��<�ݶsrc/Phan/Library/Hasher.php�/)�Z��� �src/Phan/Library/Tuple2.php�/)�Z�+�@�src/Phan/Library/Some.phpk/)�Zk�n���src/Phan/Library/Tuple5.php�/)�Z��h޶#src/Phan/Library/FileCacheEntry.php�/)�Z����src/Phan/Library/Set.php/)�Z�b��src/Phan/Library/Tuple1.php�/)�Z���J�src/Phan/Issue.phpi�/)�Zi��Ǎ��src/Phan/Analysis.phpWO/)�ZWO�KP��/src/Phan/Analysis/BinaryOperatorFlagVisitor.phpl>/)�Zl>�~�d�+src/Phan/Analysis/PropertyTypesAnalyzer.php�/)�Z�4"�l�)src/Phan/Analysis/CompositionAnalyzer.php�/)�Z��wG�.src/Phan/Analysis/ClassInheritanceAnalyzer.php5/)�Z5Ds�Ͷ-src/Phan/Analysis/ReferenceCountsAnalyzer.phpuB/)�ZuB��`� src/Phan/Analysis/Analyzable.php(/)�Z(Q�b!�/src/Phan/Analysis/AssignOperatorFlagVisitor.php�$/)�Z�$d�ԗ�,src/Phan/Analysis/BlockExitStatusChecker.php�[/)�Z�[1&B�-src/Phan/Analysis/PreOrderAnalysisVisitor.php	�/)�Z	��-J�)src/Phan/Analysis/ReturnTypesAnalyzer.php�/)�Z��2V¶,src/Phan/Analysis/ParameterTypesAnalyzer.php��/)�Z����V��&src/Phan/Analysis/ConditionVisitor.php�|/)�Z�|!3@�"src/Phan/Analysis/ArgumentType.php�e/)�Z�ew�bw�,src/Phan/Analysis/DuplicateClassAnalyzer.php�	/)�Z�	!�U|�.src/Phan/Analysis/PostOrderAnalysisVisitor.php�q/)�Z�qh�#˶"src/Phan/Analysis/ScopeVisitor.php/)�ZfE~�-src/Phan/Analysis/NegatedConditionVisitor.php=�/)�Z=�V\̶'src/Phan/Analysis/AssignmentVisitor.php�/)�Z�8^�*src/Phan/Analysis/ConditionVisitorUtil.php�C/)�Z�Cs6݃�,src/Phan/Analysis/AbstractMethodAnalyzer.php�/)�Z�*x޶/src/Phan/Analysis/DuplicateFunctionAnalyzer.phpP	/)�ZP	�f�j�)src/Phan/Analysis/ContextMergeVisitor.phpv6/)�Zv6��(��5src/Phan/Analysis/ParentConstructorCalledAnalyzer.php�/)�Z��ԏ��)src/Phan/Analysis/ThrowsTypesAnalyzer.phpN/)�ZN�~|R�src/Phan/IssueInstance.php�
/)�Z�
}��2�src/Phan/Prep.php
/)�Z
;1�src/Phan/Debug.php7/)�Z7�t�(�src/Phan/AST/ContextNode.php�/)�Z��E�� src/Phan/AST/AnalysisVisitor.phpo/)�Zo{�նsrc/Phan/AST/Parser.php�/)�Z��A�Ҷ!src/Phan/AST/UnionTypeVisitor.php�q/)�Z�q1i;7�$src/Phan/AST/PhanAnnotationAdder.php�/)�Z�Чm��src/Phan/AST/ASTSimplifier.php�Q/)�Z�Q$�?��$src/Phan/AST/Visitor/FlagVisitor.php�#/)�Z�#�,�Զ$src/Phan/AST/Visitor/KindVisitor.php�+/)�Z�+�!�� src/Phan/AST/Visitor/Element.php{C/)�Z{C{!��2src/Phan/AST/Visitor/FlagVisitorImplementation.php�/)�Z�\��2src/Phan/AST/Visitor/KindVisitorImplementation.php�'/)�Z�'6����4src/Phan/AST/TolerantASTConverter/ParseException.phpX/)�ZX�ldw�-src/Phan/AST/TolerantASTConverter/String_.phpi/)�Zi�.Q�0src/Phan/AST/TolerantASTConverter/NodeDumper.php�/)�Z��b�ض.src/Phan/AST/TolerantASTConverter/ast_shim.phpB/)�ZB��&�:src/Phan/AST/TolerantASTConverter/TolerantASTConverter.php�/)�Z�'��:src/Phan/AST/TolerantASTConverter/InvalidNodeException.php�/)�Z���Ƕ!src/Phan/BlockAnalysisVisitor.php��/)�Z���|��,src/Phan/Output/BufferedPrinterInterface.php�/)�Z��6
P�"src/Phan/Output/PrinterFactory.php�/)�Z�n3ԋ�+src/Phan/Output/IssueCollectorInterface.php�/)�Z�ߵ��/src/Phan/Output/IgnoredFilesFilterInterface.php/)�Z��5��*src/Phan/Output/Filter/FileIssueFilter.php//)�Z/zG�K�$src/Phan/Output/Filter/AnyFilter.phpm/)�Zm��e�0src/Phan/Output/Filter/MinimumSeverityFilter.php�/)�Z�Mδ-�.src/Phan/Output/Filter/CategoryIssueFilter.phpQ/)�ZQ0�*�-src/Phan/Output/Filter/ChainedIssueFilter.php/)�Zz.�$�&src/Phan/Output/Printer/CSVPrinter.php�/)�Z�.>�'src/Phan/Output/Printer/JSONPrinter.php7/)�Z7{�(�,src/Phan/Output/Printer/PlainTextPrinter.php)/)�Z)���-src/Phan/Output/Printer/CheckstylePrinter.php�/)�Z���
Ҷ.src/Phan/Output/Printer/CodeClimatePrinter.phpp	/)�Zp	?�š�)src/Phan/Output/Printer/PylintPrinter.php(/)�Z(
�Ŷsrc/Phan/Output/Colorizing.php/)�Z	1�)src/Phan/Output/IssuePrinterInterface.php�/)�Z�!�ն4src/Phan/Output/Collector/ParallelChildCollector.php�/)�Z��?��0src/Phan/Output/Collector/BufferingCollector.php$	/)�Z$	���5src/Phan/Output/Collector/ParallelParentCollector.phpn/)�ZnDu��(src/Phan/Output/IssueFilterInterface.php�/)�Z�9�̔�src/Phan/ForkPool.php�/)�Z���h׶src/Phan/CodeBase/ClassMap.php�
/)�Z�
t	ض!src/Phan/CodeBase/UndoTracker.php�/)�Z�-*"S�src/prep.phpi/)�Ziₗ�src/codebase.php�/)�Z�gNvQ�.vendor/symfony/polyfill-mbstring/bootstrap.phpI/)�ZI@��@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php9J/)�Z9J|Q��@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php�I/)�Z�I�҈�-vendor/symfony/polyfill-mbstring/Mbstring.phpg/)�Zg����3vendor/symfony/console/Exception/LogicException.php�/)�Z�SML��;vendor/symfony/console/Exception/InvalidOptionException.php�/)�Z��;�=vendor/symfony/console/Exception/InvalidArgumentException.php�/)�Z��u i�7vendor/symfony/console/Exception/ExceptionInterface.php�/)�Z����U�5vendor/symfony/console/Exception/RuntimeException.php�/)�Z��*b�=vendor/symfony/console/Exception/CommandNotFoundException.php�/)�Z���Ͷ(vendor/symfony/console/ConsoleEvents.php!/)�Z!��Ω�>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php�	/)�Z�	\g�V�4vendor/symfony/console/Formatter/OutputFormatter.php_/)�Z_߅6�Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php7/)�Z7�.@Y�=vendor/symfony/console/Formatter/OutputFormatterInterface.php�/)�Z�#7�u�9vendor/symfony/console/Formatter/OutputFormatterStyle.php�/)�Z����ʶ,vendor/symfony/console/Style/OutputStyle.php�/)�Z��gݶ/vendor/symfony/console/Style/StyleInterface.phpO/)�ZO��Q�-vendor/symfony/console/Style/SymfonyStyle.php	//)�Z	/���/vendor/symfony/console/Tester/CommandTester.php�/)�Z�	A��3vendor/symfony/console/Tester/ApplicationTester.php/)�Ze5�6vendor/symfony/console/EventListener/ErrorListener.php�
/)�Z�
�/ű�&vendor/symfony/console/Application.php��/)�Z��,�ؽ�9vendor/symfony/console/Descriptor/DescriptorInterface.php�/)�Z�JZ0<�8vendor/symfony/console/Descriptor/MarkdownDescriptor.php�/)�Z���`�0vendor/symfony/console/Descriptor/Descriptor.php�/)�Z��j�<vendor/symfony/console/Descriptor/ApplicationDescription.php/)�Z����4vendor/symfony/console/Descriptor/JsonDescriptor.phpd/)�Zd�m�h�4vendor/symfony/console/Descriptor/TextDescriptor.php�0/)�Z�04�t�3vendor/symfony/console/Descriptor/XmlDescriptor.php$/)�Z${g�L�/vendor/symfony/console/Logger/ConsoleLogger.php/)�Z z���2vendor/symfony/console/Question/ChoiceQuestion.php�/)�Z���ֶ,vendor/symfony/console/Question/Question.php�/)�Z�܎�8vendor/symfony/console/Question/ConfirmationQuestion.php/)�Z�����.vendor/symfony/console/Command/ListCommand.php	
/)�Z	
��4�.vendor/symfony/console/Command/HelpCommand.phpI	/)�ZI	�)~��0vendor/symfony/console/Command/LockableTrait.php�/)�Z���O�*vendor/symfony/console/Command/Command.php�I/)�Z�IRӲ�Dvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php�/)�Z���X�&vendor/symfony/console/Input/Input.php�/)�Z����Ŷ,vendor/symfony/console/Input/StringInput.phpP	/)�ZP	�I7�+vendor/symfony/console/Input/ArrayInput.php�/)�Z�:�v�4vendor/symfony/console/Input/InputAwareInterface.php^/)�Z^9K�h�/vendor/symfony/console/Input/InputInterface.php//)�Z/S�x�*vendor/symfony/console/Input/ArgvInput.php�*/)�Z�**8��0vendor/symfony/console/Input/InputDefinition.php,/)�Z,Y��Z�,vendor/symfony/console/Input/InputOption.php!/)�Z!�y�.vendor/symfony/console/Input/InputArgument.php�/)�Z�	��9vendor/symfony/console/Input/StreamableInputInterface.phpi/)�Zi���3vendor/symfony/console/Helper/ProgressIndicator.php/)�ZfG.�2vendor/symfony/console/Helper/InputAwareHelper.php�/)�Z���˶6vendor/symfony/console/Helper/DebugFormatterHelper.phpV/)�ZV�ݔ��1vendor/symfony/console/Helper/HelperInterface.phpp/)�Zp���n�/vendor/symfony/console/Helper/ProcessHelper.php�/)�Z��2R�7vendor/symfony/console/Helper/SymfonyQuestionHelper.php�/)�Z�KV�k�0vendor/symfony/console/Helper/TableSeparator.php/)�Z�=_�,vendor/symfony/console/Helper/TableStyle.php�/)�Z�w�U\�+vendor/symfony/console/Helper/TableCell.phpy/)�Zyp/#6�+vendor/symfony/console/Helper/HelperSet.php�	/)�Z�	.��ض'vendor/symfony/console/Helper/Table.php�N/)�Z�N��NԶ(vendor/symfony/console/Helper/Helper.php�/)�Z�
FY>�-vendor/symfony/console/Helper/ProgressBar.php�C/)�Z�C�Vde�0vendor/symfony/console/Helper/QuestionHelper.php9/)�Z9P��2vendor/symfony/console/Helper/DescriptorHelper.php1
/)�Z1
i��1vendor/symfony/console/Helper/FormatterHelper.php�/)�Z����=vendor/symfony/console/CommandLoader/FactoryCommandLoader.php7/)�Z7�m_�?vendor/symfony/console/CommandLoader/CommandLoaderInterface.php�/)�Z��NEe�?vendor/symfony/console/CommandLoader/ContainerCommandLoader.phpW/)�ZW��Ȟ�(vendor/symfony/console/Output/Output.php{/)�Z{�m�n�,vendor/symfony/console/Output/NullOutput.phpn/)�Znt�D��/vendor/symfony/console/Output/ConsoleOutput.php�/)�Z���U�.vendor/symfony/console/Output/StreamOutput.phpf
/)�Zf
$��0vendor/symfony/console/Output/BufferedOutput.phpH/)�ZH�9�Զ1vendor/symfony/console/Output/OutputInterface.php
/)�Z
���9�8vendor/symfony/console/Output/ConsoleOutputInterface.php�/)�Z�����4vendor/symfony/console/Event/ConsoleCommandEvent.php%/)�Z%{˾�2vendor/symfony/console/Event/ConsoleErrorEvent.php�/)�Z�T/�[�-vendor/symfony/console/Event/ConsoleEvent.php�/)�Z�xS*�6vendor/symfony/console/Event/ConsoleExceptionEvent.phpS/)�ZS���ֶ6vendor/symfony/console/Event/ConsoleTerminateEvent.php/)�Z{e��#vendor/symfony/console/Terminal.php�/)�Z�h�7�=vendor/symfony/debug/Exception/UndefinedFunctionException.php+/)�Z+�O��6vendor/symfony/debug/Exception/FatalErrorException.php/)�ZU��l�9vendor/symfony/debug/Exception/ClassNotFoundException.php</)�Z<'D���6vendor/symfony/debug/Exception/FatalThrowableError.phpS/)�ZSO�M�8vendor/symfony/debug/Exception/ContextErrorException.phpS/)�ZSc3��;vendor/symfony/debug/Exception/UndefinedMethodException.php&/)�Z&ïy߶3vendor/symfony/debug/Exception/FlattenException.php(/)�Z(�D�D�7vendor/symfony/debug/Exception/OutOfMemoryException.php�/)�Z���h�7vendor/symfony/debug/Exception/SilencedErrorContext.phph/)�Zhz=��3vendor/symfony/debug/Tests/MockExceptionHandler.php�/)�Z��
8�=vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php~//)�Z~/�2$��/vendor/symfony/debug/Tests/ErrorHandlerTest.php�K/)�Z�K��H��6vendor/symfony/debug/Tests/Fixtures/AnnotatedClass.php�/)�Z����2vendor/symfony/debug/Tests/Fixtures/FinalClass.phpt/)�Zt!P\I�2vendor/symfony/debug/Tests/Fixtures/notPsr0Bis.phpO/)�ZO�����9vendor/symfony/debug/Tests/Fixtures/InternalInterface.phpo/)�Zov��7vendor/symfony/debug/Tests/Fixtures/ToStringThrower.php/)�Z�1B��5vendor/symfony/debug/Tests/Fixtures/InternalClass.php�/)�Z�����;vendor/symfony/debug/Tests/Fixtures/ExtendedFinalMethod.php�/)�Z���5vendor/symfony/debug/Tests/Fixtures/InternalTrait.phpg/)�Zg�u���7vendor/symfony/debug/Tests/Fixtures/DeprecatedClass.php�/)�Z�c����4vendor/symfony/debug/Tests/Fixtures/casemismatch.phpQ/)�ZQiܶ1vendor/symfony/debug/Tests/Fixtures/PEARClass.phpB/)�ZB�[���5vendor/symfony/debug/Tests/Fixtures/reallyNotPsr0.phpL/)�ZLV�7��=vendor/symfony/debug/Tests/Fixtures/psr4/Psr4CaseMismatch.phpU/)�ZU)6:�0vendor/symfony/debug/Tests/Fixtures/Throwing.php$/)�Z$�<��6vendor/symfony/debug/Tests/Fixtures/InternalTrait2.phpI/)�ZIS�2Ӷ2vendor/symfony/debug/Tests/Fixtures/ClassAlias.php~/)�Z~c��	�>vendor/symfony/debug/Tests/Fixtures/NonDeprecatedInterface.php{/)�Z{;��t�3vendor/symfony/debug/Tests/Fixtures/FinalMethod.php�/)�Z�٤tֶ;vendor/symfony/debug/Tests/Fixtures/DeprecatedInterface.php�/)�Z��{sK�6vendor/symfony/debug/Tests/Fixtures2/RequiredTwice.phpS/)�ZS�Hж3vendor/symfony/debug/Tests/DebugClassLoaderTest.php�>/)�Z�>�{�3vendor/symfony/debug/Tests/ExceptionHandlerTest.php/)�Z&�y�Svendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php�/)�Z�X�]��Uvendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php�/)�Z���M�Wvendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.phph/)�Zhb��ж)vendor/symfony/debug/Tests/HeaderMock.php�/)�Z����ж)vendor/symfony/debug/DebugClassLoader.php�@/)�Z�@wB&��)vendor/symfony/debug/ExceptionHandler.php�/)�Z�G]3�%vendor/symfony/debug/ErrorHandler.php�r/)�Z�r:/��vendor/symfony/debug/Debug.php�/)�Z���f�Ivendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php�/)�Z���(�Kvendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.phpZ/)�ZZ�2�Mvendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php�/)�Z��g���Evendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php�/)�Z��iA��(vendor/symfony/debug/BufferingLogger.php�/)�Z�M0	=�:vendor/netresearch/jsonmapper/src/JsonMapper/Exception.php/)�Zi"�0vendor/netresearch/jsonmapper/src/JsonMapper.php%Z/)�Z%Z6�'u�;vendor/microsoft/tolerant-php-parser/src/DiagnosticKind.php�/)�Z�Q�zG�@vendor/microsoft/tolerant-php-parser/src/DiagnosticsProvider.php/)�Z��|�9vendor/microsoft/tolerant-php-parser/src/ParseContext.phpR/)�ZRX,s�9vendor/microsoft/tolerant-php-parser/src/PhpTokenizer.phpN//)�ZN/`R��;vendor/microsoft/tolerant-php-parser/src/CharacterCodes.php�
/)�Z�
�,Ӷ@vendor/microsoft/tolerant-php-parser/src/NamespacedNameTrait.php{/)�Z{��vk�9vendor/microsoft/tolerant-php-parser/src/FunctionLike.php /)�Z �����6vendor/microsoft/tolerant-php-parser/src/bootstrap.php&/)�Z&��նGvendor/microsoft/tolerant-php-parser/src/TokenStreamProviderFactory.phpE/)�ZE��+�7vendor/microsoft/tolerant-php-parser/src/Diagnostic.php/)�ZG��Ѷ<vendor/microsoft/tolerant-php-parser/src/TokenStringMaps.php�/)�Z�|�1vendor/microsoft/tolerant-php-parser/src/Node.phph/)�Zhy(���9vendor/microsoft/tolerant-php-parser/src/SkippedToken.php
/)�Z
��v�2vendor/microsoft/tolerant-php-parser/src/Range.phpX/)�ZX#S�p�9vendor/microsoft/tolerant-php-parser/src/ResolvedName.phpB/)�ZB�M�w�3vendor/microsoft/tolerant-php-parser/src/Parser.phpF/)�ZF!�j϶5vendor/microsoft/tolerant-php-parser/src/TextEdit.php�/)�Z��ە�6vendor/microsoft/tolerant-php-parser/src/ClassLike.php�/)�Z�6�>vendor/microsoft/tolerant-php-parser/src/PositionUtilities.php�/)�Z�C�(u�2vendor/microsoft/tolerant-php-parser/src/Token.php�/)�Z�ze]o�6vendor/microsoft/tolerant-php-parser/src/TokenKind.php�/)�Z�����<vendor/microsoft/tolerant-php-parser/src/FilePositionMap.php�/)�Z�3P�=�Dvendor/microsoft/tolerant-php-parser/src/NamespacedNameInterface.php�/)�Z���;
�Bvendor/microsoft/tolerant-php-parser/src/LineCharacterPosition.phpX/)�ZX<UT��Ivendor/microsoft/tolerant-php-parser/src/TokenStreamProviderInterface.php�/)�Z�CJAܶJvendor/microsoft/tolerant-php-parser/src/Node/TraitSelectOrAliasClause.php�/)�Z�;��>vendor/microsoft/tolerant-php-parser/src/Node/ReservedWord.php6/)�Z6��d�@vendor/microsoft/tolerant-php-parser/src/Node/FunctionHeader.php�/)�Z�w+��@vendor/microsoft/tolerant-php-parser/src/Node/TraitUseClause.php�/)�Z�O6�	�@vendor/microsoft/tolerant-php-parser/src/Node/SourceFileNode.php�/)�Z�@"�%�>vendor/microsoft/tolerant-php-parser/src/Node/ForeachValue.php�/)�Z�q,��Bvendor/microsoft/tolerant-php-parser/src/Node/ElseIfClauseNode.php�/)�Z���N�Evendor/microsoft/tolerant-php-parser/src/Node/PropertyDeclaration.phpa/)�Za�	���Jvendor/microsoft/tolerant-php-parser/src/Node/MissingMemberDeclaration.php_/)�Z_�y���<vendor/microsoft/tolerant-php-parser/src/Node/Expression.php�/)�Z�C�.�Cvendor/microsoft/tolerant-php-parser/src/Node/RelativeSpecifier.php�/)�Z�B{�e�Lvendor/microsoft/tolerant-php-parser/src/Node/AnonymousFunctionUseClause.php�/)�Z���M�Cvendor/microsoft/tolerant-php-parser/src/Node/MethodDeclaration.php�/)�Z�D9�c�Bvendor/microsoft/tolerant-php-parser/src/Node/InterfaceMembers.php/)�Z��hжBvendor/microsoft/tolerant-php-parser/src/Node/ClassMembersNode.php�/)�Z�\{	��>vendor/microsoft/tolerant-php-parser/src/Node/ConstElement.php./)�Z.��@��Avendor/microsoft/tolerant-php-parser/src/Node/ClassBaseClause.php�/)�Z�v�E�Pvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/ConstElementList.php�/)�Z��J"�Vvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/StaticVariableNameList.php�/)�Z��,kB�Pvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/ArrayElementList.php�/)�Z��n�X�\vendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/TraitSelectOrAliasClauseList.php/)�Z,��y�Rvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/ListExpressionList.php�/)�Z�+n]�Qvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/QualifiedNameList.php�/)�Z���5�Vvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/ArgumentExpressionList.php�/)�Z��|�m�Svendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/UseVariableNameList.php�/)�Z��g
�Nvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/ExpressionList.php�/)�Z���*ٶVvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/NamespaceUseClauseList.php�/)�Z��Y\��Pvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/VariableNameList.php�/)�Z��|W�Xvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/ParameterDeclarationList.php�/)�Z���$X�Rvendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/QualifiedNameParts.php�/)�Z�2��v�[vendor/microsoft/tolerant-php-parser/src/Node/DelimitedList/NamespaceUseGroupClauseList.php/)�Z��Ei�>vendor/microsoft/tolerant-php-parser/src/Node/ArrayElement.php5/)�Z5�/f�@vendor/microsoft/tolerant-php-parser/src/Node/NumericLiteral.php8/)�Z8!d�o�Ivendor/microsoft/tolerant-php-parser/src/Node/NamespaceUseGroupClause.php+/)�Z+���Fvendor/microsoft/tolerant-php-parser/src/Node/DefaultStatementNode.php/)�Z��F�Fvendor/microsoft/tolerant-php-parser/src/Node/ClassInterfaceClause.php�/)�Z�n�ק�?vendor/microsoft/tolerant-php-parser/src/Node/DelimitedList.php�/)�Z��W��?vendor/microsoft/tolerant-php-parser/src/Node/StatementNode.php�/)�Z��i�?vendor/microsoft/tolerant-php-parser/src/Node/QualifiedName.php4 /)�Z4 �	H�=vendor/microsoft/tolerant-php-parser/src/Node/CatchClause.php�/)�Z����@vendor/microsoft/tolerant-php-parser/src/Node/ElseClauseNode.php�/)�Z����9�Ivendor/microsoft/tolerant-php-parser/src/Node/NamespaceAliasingClause.php�/)�Z����?vendor/microsoft/tolerant-php-parser/src/Node/StringLiteral.php-/)�Z-Eu�I�Cvendor/microsoft/tolerant-php-parser/src/Node/CaseStatementNode.phpR/)�ZR�v3~�Avendor/microsoft/tolerant-php-parser/src/Node/UseVariableName.php�/)�Z�����Cvendor/microsoft/tolerant-php-parser/src/Node/FunctionUseClause.php�/)�Z�>�$�?vendor/microsoft/tolerant-php-parser/src/Node/FinallyClause.php�/)�Z�"�m�Uvendor/microsoft/tolerant-php-parser/src/Node/Expression/IssetIntrinsicExpression.php�/)�Z�u�R�Tvendor/microsoft/tolerant-php-parser/src/Node/Expression/ListIntrinsicExpression.php�/)�Z�k���Kvendor/microsoft/tolerant-php-parser/src/Node/Expression/EchoExpression.php�/)�Z�l҃��Svendor/microsoft/tolerant-php-parser/src/Node/Expression/ErrorControlExpression.php�/)�Z���Svendor/microsoft/tolerant-php-parser/src/Node/Expression/MemberAccessExpression.php$/)�Z$�ȱֶPvendor/microsoft/tolerant-php-parser/src/Node/Expression/SubscriptExpression.php�/)�Z�����Svendor/microsoft/tolerant-php-parser/src/Node/Expression/PrefixUpdateExpression.php�/)�Z��2�k�Uvendor/microsoft/tolerant-php-parser/src/Node/Expression/EmptyIntrinsicExpression.phpQ/)�ZQ��F��Nvendor/microsoft/tolerant-php-parser/src/Node/Expression/TernaryExpression.php�/)�Z�����Vvendor/microsoft/tolerant-php-parser/src/Node/Expression/ScriptInclusionExpression.php�/)�Z�$�e��Evendor/microsoft/tolerant-php-parser/src/Node/Expression/Variable.php�/)�Z��0<�Nvendor/microsoft/tolerant-php-parser/src/Node/Expression/UnaryOpExpression.php�/)�Z���ݶQvendor/microsoft/tolerant-php-parser/src/Node/Expression/AssignmentExpression.phpN/)�ZN�
���Tvendor/microsoft/tolerant-php-parser/src/Node/Expression/ExitIntrinsicExpression.phpg/)�Zg&�v��Uvendor/microsoft/tolerant-php-parser/src/Node/Expression/UnsetIntrinsicExpression.php�/)�Z��/q�`vendor/microsoft/tolerant-php-parser/src/Node/Expression/AnonymousFunctionCreationExpression.php]/)�Z]_D߶Mvendor/microsoft/tolerant-php-parser/src/Node/Expression/BracedExpression.php�/)�Z�>D�"�Lvendor/microsoft/tolerant-php-parser/src/Node/Expression/YieldExpression.php�/)�Z�M"��Tvendor/microsoft/tolerant-php-parser/src/Node/Expression/PostfixUpdateExpression.php�/)�Z�֘Y�Lvendor/microsoft/tolerant-php-parser/src/Node/Expression/UnaryExpression.php_/)�Z_�ւ�Tvendor/microsoft/tolerant-php-parser/src/Node/Expression/ArrayCreationExpression.php�/)�Z�25��Kvendor/microsoft/tolerant-php-parser/src/Node/Expression/CallExpression.php�/)�Z��^���Uvendor/microsoft/tolerant-php-parser/src/Node/Expression/ObjectCreationExpression.php/)�ZI�RA�Mvendor/microsoft/tolerant-php-parser/src/Node/Expression/BinaryExpression.php	/)�Z	��m�Kvendor/microsoft/tolerant-php-parser/src/Node/Expression/CastExpression.php/)�Zb�͔�[vendor/microsoft/tolerant-php-parser/src/Node/Expression/ScopedPropertyAccessExpression.phpw/)�Zw{���Uvendor/microsoft/tolerant-php-parser/src/Node/Expression/PrintIntrinsicExpression.php�/)�Z�=�� �Ovendor/microsoft/tolerant-php-parser/src/Node/Expression/ArgumentExpression.phpr/)�Zr@q@a�Lvendor/microsoft/tolerant-php-parser/src/Node/Expression/CloneExpression.php�/)�Z�Gj��Tvendor/microsoft/tolerant-php-parser/src/Node/Expression/EvalIntrinsicExpression.phpN/)�ZN�y�t�Tvendor/microsoft/tolerant-php-parser/src/Node/Expression/ParenthesizedExpression.php/)�Z�i��Gvendor/microsoft/tolerant-php-parser/src/Node/ClassConstDeclaration.phpL/)�ZLC\7ڶ;vendor/microsoft/tolerant-php-parser/src/Node/Parameter.php�/)�Z�K&?�>vendor/microsoft/tolerant-php-parser/src/Node/FunctionBody.phpV/)�ZVK�m�Dvendor/microsoft/tolerant-php-parser/src/Node/FunctionReturnType.phph/)�Zh�P�Evendor/microsoft/tolerant-php-parser/src/Node/InterfaceBaseClause.php�/)�Z���s�<vendor/microsoft/tolerant-php-parser/src/Node/ForeachKey.php�/)�Z�]�]E�Dvendor/microsoft/tolerant-php-parser/src/Node/NamespaceUseClause.php/)�ZWyІ�>vendor/microsoft/tolerant-php-parser/src/Node/TraitMembers.php�/)�Z�F�n�Kvendor/microsoft/tolerant-php-parser/src/Node/StaticVariableDeclaration.php/)�Z�
�Mvendor/microsoft/tolerant-php-parser/src/Node/Statement/GlobalDeclaration.phpX/)�ZX��	^�Lvendor/microsoft/tolerant-php-parser/src/Node/Statement/ForeachStatement.php�/)�Z�e��Fvendor/microsoft/tolerant-php-parser/src/Node/Statement/InlineHtml.php!/)�Z!%�…�Tvendor/microsoft/tolerant-php-parser/src/Node/Statement/BreakOrContinueStatement.php�	/)�Z�	׍HöOvendor/microsoft/tolerant-php-parser/src/Node/Statement/NamespaceDefinition.phpu/)�Zu��I�Jvendor/microsoft/tolerant-php-parser/src/Node/Statement/ThrowStatement.php-/)�Z-��SʶKvendor/microsoft/tolerant-php-parser/src/Node/Statement/ReturnStatement.php5/)�Z51��Svendor/microsoft/tolerant-php-parser/src/Node/Statement/NamespaceUseDeclaration.php�/)�Z�w%�Ivendor/microsoft/tolerant-php-parser/src/Node/Statement/GotoStatement.php�/)�Z���*�Lvendor/microsoft/tolerant-php-parser/src/Node/Statement/DeclareStatement.php�/)�Z��B��Hvendor/microsoft/tolerant-php-parser/src/Node/Statement/ForStatement.php�/)�Z��!�Ovendor/microsoft/tolerant-php-parser/src/Node/Statement/ExpressionStatement.php�/)�Z�+jC�Uvendor/microsoft/tolerant-php-parser/src/Node/Statement/FunctionStaticDeclaration.phpr/)�Zr�#/ͶJvendor/microsoft/tolerant-php-parser/src/Node/Statement/EmptyStatement.phps/)�Zs�d�Kvendor/microsoft/tolerant-php-parser/src/Node/Statement/IfStatementNode.php�/)�Z��>ҶHvendor/microsoft/tolerant-php-parser/src/Node/Statement/TryStatement.php�/)�Z�Ϭ��Lvendor/microsoft/tolerant-php-parser/src/Node/Statement/ConstDeclaration.phpN/)�ZNW<��Ovendor/microsoft/tolerant-php-parser/src/Node/Statement/SwitchStatementNode.php_/)�Z_|�Y�Jvendor/microsoft/tolerant-php-parser/src/Node/Statement/WhileStatement.php�/)�Z��R��Gvendor/microsoft/tolerant-php-parser/src/Node/Statement/DoStatement.php//)�Z/p��&�Lvendor/microsoft/tolerant-php-parser/src/Node/Statement/TraitDeclaration.phpN/)�ZN�]3�Pvendor/microsoft/tolerant-php-parser/src/Node/Statement/InterfaceDeclaration.php/)�ZKە��Lvendor/microsoft/tolerant-php-parser/src/Node/Statement/ClassDeclaration.php�/)�Z���*�Ovendor/microsoft/tolerant-php-parser/src/Node/Statement/FunctionDeclaration.php?/)�Z?��q}�Qvendor/microsoft/tolerant-php-parser/src/Node/Statement/CompoundStatementNode.php)/)�Z)��玶Ovendor/microsoft/tolerant-php-parser/src/Node/Statement/NamedLabelStatement.php�/)�Z���m�Bvendor/microsoft/tolerant-php-parser/src/Node/DeclareDirective.php�/)�Z��I4��9vendor/microsoft/tolerant-php-parser/src/MissingToken.php�/)�Z���)޶=vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.phpu/)�Zu⹰�Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php�	/)�Z�	�\�Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.phpF/)�ZF.h�)�Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php�/)�Z�X�
c�Bvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php�	/)�Z�	
y%��]vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php+/)�Z+��ܶWvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php�/)�Z����Gvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php�/)�Z�]]H��Hvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php�/)�Z�Dy7�Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php|/)�Z|�Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php�/)�Z��@��Ovendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php�/)�Z���R�Svendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php�/)�Z��2i��Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php�/)�Z��ȈضEvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.phpn/)�ZnС�Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php�/)�Z�s��ȶKvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php�/)�Z��	2�Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php�	/)�Z�	����Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.phpp/)�Zp�H���Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php�	/)�Z�	�yaöRvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php,/)�Z,�8(��Nvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php�/)�Z���c�Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.phpM/)�ZM���|�Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.phpX
/)�ZX
D� �Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.phpN/)�ZNV���Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php
/)�Z
��϶Ivendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php�
/)�Z�
���Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php�/)�Z�~�|��Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.phpP/)�ZPT,��Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.phpp/)�Zp3��{�Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php/)�ZP;ͶGvendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php�/)�Z��x��Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php//)�Z/>�	�Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php�/)�Z�"~.0�Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.phpR./)�ZR.Ø��9vendor/phpdocumentor/reflection-docblock/src/DocBlock.php/)�Z��Z�Ivendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php!/)�Z!��}�@vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php�$/)�Z�$�Ń��/vendor/phpdocumentor/type-resolver/src/Type.php�/)�Z���[L�7vendor/phpdocumentor/type-resolver/src/Types/Array_.php-/)�Z-��_�8vendor/phpdocumentor/type-resolver/src/Types/Integer.php�/)�Z�"s��:vendor/phpdocumentor/type-resolver/src/Types/Callable_.php�/)�Z�4ɿ�8vendor/phpdocumentor/type-resolver/src/Types/Boolean.php�/)�Z��f��8vendor/phpdocumentor/type-resolver/src/Types/Static_.phpU/)�ZU�ޟ�9vendor/phpdocumentor/type-resolver/src/Types/Compound.php	/)�Z	���|�8vendor/phpdocumentor/type-resolver/src/Types/Context.phpV/)�ZV�{��:vendor/phpdocumentor/type-resolver/src/Types/Iterable_.php�/)�Z�O��Ӷ5vendor/phpdocumentor/type-resolver/src/Types/This.php�/)�Z��h²�7vendor/phpdocumentor/type-resolver/src/Types/Scalar.php/)�ZU�6vendor/phpdocumentor/type-resolver/src/Types/Void_.phpW/)�ZW��ֶ7vendor/phpdocumentor/type-resolver/src/Types/Float_.php�/)�Z��w,�8vendor/phpdocumentor/type-resolver/src/Types/String_.php�/)�Z����6vendor/phpdocumentor/type-resolver/src/Types/Null_.php�/)�Z�@�%��9vendor/phpdocumentor/type-resolver/src/Types/Nullable.php�/)�Z��Ab��8vendor/phpdocumentor/type-resolver/src/Types/Parent_.php4/)�Z4��j�8vendor/phpdocumentor/type-resolver/src/Types/Object_.php�/)�Z���Q��7vendor/phpdocumentor/type-resolver/src/Types/Mixed_.php�/)�Z�ϯ�ʶ6vendor/phpdocumentor/type-resolver/src/Types/Self_.php/)�Z��9'�?vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php�/)�Z�E�3�:vendor/phpdocumentor/type-resolver/src/Types/Resource_.php�/)�Z�Nf
��8vendor/phpdocumentor/type-resolver/src/FqsenResolver.php	/)�Z	�R�ٶ7vendor/phpdocumentor/type-resolver/src/TypeResolver.phpw$/)�Zw$U��y�6vendor/phpdocumentor/reflection-common/src/Project.php/)�Z/H� �3vendor/phpdocumentor/reflection-common/src/File.php7/)�Z7�3"�=vendor/phpdocumentor/reflection-common/src/ProjectFactory.php/)�ZQ�"ܶ4vendor/phpdocumentor/reflection-common/src/Fqsen.php/)�Z��Ӝ�7vendor/phpdocumentor/reflection-common/src/Location.phpH/)�ZH?-��6vendor/phpdocumentor/reflection-common/src/Element.php1/)�Z1�iUҶ%vendor/psr/log/Psr/Log/NullLogger.php�/)�Z���Zf�#vendor/psr/log/Psr/Log/LogLevel.phpP/)�ZP���3vendor/psr/log/Psr/Log/InvalidArgumentException.php`/)�Z` �X1�+vendor/psr/log/Psr/Log/LoggerAwareTrait.php�/)�Z�z%��)vendor/psr/log/Psr/Log/AbstractLogger.php/)�Z�Gl�/vendor/psr/log/Psr/Log/LoggerAwareInterface.php)/)�Z)�j��3vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php�/)�Z���$s�*vendor/psr/log/Psr/Log/LoggerInterface.php�/)�Z�?}�&vendor/psr/log/Psr/Log/LoggerTrait.php
/)�Z
�ý��vendor/autoload.php�/)�Z���8�&vendor/webmozart/assert/src/Assert.php;�/)�Z;�Ѽ�V�;vendor/felixfbecker/advanced-json-rpc/tests/MessageTest.phpj/)�Zj�Ɓ�6vendor/felixfbecker/advanced-json-rpc/tests/Target.php%/)�Z%��Q�8vendor/felixfbecker/advanced-json-rpc/tests/Argument.php,/)�Z,X���>vendor/felixfbecker/advanced-json-rpc/tests/DispatcherTest.php#/)�Z#;I�J�:vendor/felixfbecker/advanced-json-rpc/tests/MethodCall.php/)�Z�%�6vendor/felixfbecker/advanced-json-rpc/lib/Response.phpy/)�Zy��A�8vendor/felixfbecker/advanced-json-rpc/lib/Dispatcher.php+/)�Z+KR���3vendor/felixfbecker/advanced-json-rpc/lib/Error.php�/)�Z�nD\b�=vendor/felixfbecker/advanced-json-rpc/lib/SuccessResponse.phpP/)�ZPh�}q�;vendor/felixfbecker/advanced-json-rpc/lib/ErrorResponse.php\/)�Z\ͬ�ն:vendor/felixfbecker/advanced-json-rpc/lib/Notification.php�/)�Z���Q�7vendor/felixfbecker/advanced-json-rpc/lib/ErrorCode.php�/)�Z��0[�5vendor/felixfbecker/advanced-json-rpc/lib/Message.phpV/)�ZV�E�*�5vendor/felixfbecker/advanced-json-rpc/lib/Request.phpo/)�ZoN�k��4vendor/composer/semver/src/Constraint/Constraint.phpV/)�ZV�zn��=vendor/composer/semver/src/Constraint/ConstraintInterface.php^/)�Z^)��X�9vendor/composer/semver/src/Constraint/MultiConstraint.php&
/)�Z&
��(�9vendor/composer/semver/src/Constraint/EmptyConstraint.php8/)�Z8)�Ͷ<vendor/composer/semver/src/Constraint/AbstractConstraint.phpW/)�ZW�P��%vendor/composer/semver/src/Semver.php�/)�Z�=nQA�,vendor/composer/semver/src/VersionParser.phpAM/)�ZAM7Ǐ1�)vendor/composer/semver/src/Comparator.php�	/)�Z�	�g3	�'vendor/composer/autoload_namespaces.php�/)�Z�ʈ���vendor/composer/ClassLoader.phpl4/)�Zl4��[��%vendor/composer/autoload_classmap.php�/)�Z�ӻh��!vendor/composer/autoload_real.php7/)�Z7u@6ȶ#vendor/composer/autoload_static.phpjC/)�ZjC�us�4vendor/composer/xdebug-handler/src/XdebugHandler.php�,/)�Z�,U-¶-vendor/composer/xdebug-handler/src/Status.php�
/)�Z�
��fZ�.vendor/composer/xdebug-handler/src/Process.phpB/)�ZB���"vendor/composer/autoload_files.php/)�Z<�u��!vendor/composer/autoload_psr4.php�/)�Z����<�"vendor/sabre/event/lib/Version.php~/)�Z~К��:vendor/sabre/event/lib/PromiseAlreadyResolvedException.php�/)�Z���2�"vendor/sabre/event/lib/Emitter.php�/)�Z�S�d��$vendor/sabre/event/lib/coroutine.php�/)�Z��p�ʶ'vendor/sabre/event/lib/EventEmitter.php�/)�Z�w�(�/vendor/sabre/event/lib/WildcardEmitterTrait.phpP/)�ZP���Ķ'vendor/sabre/event/lib/EmitterTrait.phpF/)�ZF�I��,vendor/sabre/event/lib/Promise/functions.php�
/)�Z�
p�š�*vendor/sabre/event/lib/WildcardEmitter.php�/)�Z��J�)�+vendor/sabre/event/lib/EmitterInterface.php8
/)�Z8
V�㞶$vendor/sabre/event/lib/Loop/Loop.php�#/)�Z�#�`_�)vendor/sabre/event/lib/Loop/functions.phph/)�Zh��,.�"vendor/sabre/event/lib/Promise.php�"/)�Z�"��.phan/config.php,a/)�Z,aܪ��%.phan/plugins/PrintfCheckerPlugin.phpd�/)�Zd���R�$.phan/plugins/DollarDollarPlugin.php�/)�Z�"�F�'.phan/plugins/UnreachableCodePlugin.php�/)�Z�����.phan/plugins/DemoPlugin.phps/)�Zsb8#:�$.phan/plugins/AlwaysReturnPlugin.php�/)�Z���׶-.phan/plugins/NonBoolInLogicalArithPlugin.php�/)�Z�a|�+�*.phan/plugins/PHPUnitNotDeadCodePlugin.php�/)�Z���Ѷ2.phan/plugins/InvokePHPNativeSyntaxCheckPlugin.php�+/)�Z�+�Y���(.phan/plugins/PregRegexCheckerPlugin.phpS/)�ZSZ��G�".phan/plugins/DemoLegacyPlugin.php�/)�Z� �+�).phan/plugins/DuplicateArrayKeyPlugin.php/)�Z�ˡ�%.phan/plugins/NonBoolBranchPlugin.php�	/)�Z�	��%]�,.phan/plugins/InvalidVariableIssetPlugin.php/)�Z��h	�).phan/plugins/UnusedSuppressionPlugin.php	/)�Z	D�m�+.phan/plugins/NumericalComparisonPlugin.php�/)�Z�cJѕ�<?php declare(strict_types=1);

// Phan does a ton of GC and this offers a major speed
// improvement if your system can handle it (which it
// should be able to)
gc_disable();

// Check the environment to make sure Phan can run successfully
require_once(__DIR__ . '/requirements.php');

// Build a code base based on PHP internally defined
// functions, methods and classes before loading our
// own
$code_base = require_once(__DIR__ . '/codebase.php');

require_once(__DIR__ . '/Phan/Bootstrap.php');

use Phan\CLI;
use Phan\Phan;

if (extension_loaded('ast')) {
    // Warn if the php-ast version is too low.
    $ast_version = (new ReflectionExtension('ast'))->getVersion();
    if (version_compare($ast_version, '0.1.5') < 0) {
        fprintf(STDERR, "Phan supports php-ast version 0.1.5 or newer, but the installed php-ast version is $ast_version. You may see bugs in some edge cases\n");
    }
}

// Create our CLI interface and load arguments
$cli = new CLI();

// Analyze the file list provided via the CLI
$is_issue_found =
    Phan::analyzeFileList(
        $code_base,
        function (bool $recomputeFileList = false) use ($cli) {
            if ($recomputeFileList) {
                $cli->recomputeFileList();
            }
            return $cli->getFileList();
        }  // Daemon mode will reload the file list.
    );

// Provide an exit status code based on if
// issues were found
exit($is_issue_found ? EXIT_ISSUES_FOUND : EXIT_SUCCESS);
<?php declare(strict_types=1);

/**
 * PHP Polyfill for spl_object_id() for PHP <= 7.1
 * This file will be included even in releases which will analyze PHP 7.2,
 * there aren't any major compatibilities preventing analysis of PHP 7.2 from running in PHP 7.1.
 */
if (function_exists('spl_object_id')) {
    return;
}
// Workaround for global suppression
call_user_func(/** @suppress PhanUndeclaredFunctionInCallable for ReflectionFunction */ function () {
    if (function_exists('runkit_object_id') &&
        !(new ReflectionFunction('runkit_object_id'))->isUserDefined()) {
        /**
         * See https://github.com/runkit7/runkit_object_id for a faster native version (Improves phan's speed by 10% or so).
         * runkit_object_id 1.1.0+ provides a fast native implementation spl_object_id() for php <= 7.1,
         * in which case this file wouldn't be included.
         *
         * @param object $object
         * @return int The object id
         * @suppress PhanRedefineFunctionInternal
         * @suppress PhanRedefineFunction
         * @suppress PhanUndeclaredFunction
         */
        function spl_object_id($object)
        {
            return runkit_object_id($object);
        }
    } elseif (PHP_INT_SIZE === 8) {
        /**
         * See https://github.com/runkit7/runkit_object_id for a faster native version (Improves phan's speed by 10% or so).
         * spl_object_hash() exists, but spl_object_id() is only available in php 7.2+.
         * Also, we currently get an object from var_dump(), but that isn't nicely exposed.
         *
         * @param object $object
         * @return int (The object id, XORed with a random number)
         * @suppress PhanRedefineFunctionInternal
         * @suppress PhanRedefineFunction
         */
        function spl_object_id($object)
        {
            $hash = spl_object_hash($object);
            // Fit this into a php long (32-bit or 64-bit signed int).
            // The first 16 hex digits (64 bytes) vary, the last 16 don't.
            // Values are usually padded with 0s at the front.
            return intval(substr($hash, 1, 15), 16);
        }
    } else {
        /**
         * See https://github.com/runkit7/runkit_object_id for a faster native version (Improves phan's speed by 10% or so).
         * spl_object_hash() exists, but spl_object_id() is only available in php 7.2+.
         * Also, we currently get an object from var_dump(), but that isn't nicely exposed.
         *
         * @param object $object
         * @return int (The object id, XORed with a random number)
         * @suppress PhanRedefineFunctionInternal
         * @suppress PhanRedefineFunction
         */
        function spl_object_id($object)
        {
            $hash = spl_object_hash($object);
            // Fit this into a php long (32-bit or 64-bit signed int).
            // The first 16 hex digits (64 bytes) vary, the last 16 don't.
            // Values are usually padded with 0s at the front.
            return intval(substr($hash, 9, 7), 16);
        }
    }
});
<?php declare(strict_types = 1);

assert(
    (int)phpversion()[0] >= 7,
    'Phan requires PHP version 7 or greater. See https://github.com/phan/phan#getting-it-running for more details.'
);

assert(
    file_exists(__DIR__ . '/../vendor/autoload.php') || file_exists(__DIR__ . '/../../../autoload.php'),
    'Autoloader not found. Make sure you run `composer install` before running Phan. See https://github.com/phan/phan#getting-it-running for more details.'
);

// Fix turkish locales(tr_TR) - strtolower('I') is not 'i', so phan lookup might fail.
// (But continue formatting times, etc. in the user's locale)
setlocale(LC_CTYPE, 'C');
<?php declare(strict_types=1);
namespace Phan\Language;

use Phan\Language\FQSEN\FullyQualifiedGlobalStructuralElement;

/**
 * Tracks a `use Foo\Bar;` statement at the top of a class.
 */
class NamespaceMapEntry implements \Serializable
{
    /**
     * @var FullyQualifiedGlobalStructuralElement
     */
    public $fqsen;

    /**
     * @var string the original case sensitive name of the use statement
     */
    public $original_name;

    /**
     * @var int the line number of the use statement
     */
    public $lineno;

    /**
     * @var bool
     */
    public $is_used = false;

    public function __construct(
        FullyQualifiedGlobalStructuralElement $fqsen,
        string $original_name,
        int $lineno
    ) {
        $this->fqsen = $fqsen;
        $this->original_name = $original_name;
        $this->lineno = $lineno;
    }

    /**
     * @return string
     */
    public function serialize()
    {
        return serialize([
            get_class($this->fqsen),
            (string)$this->fqsen,
            $this->original_name,
            $this->lineno,
            $this->is_used
        ]);
    }

    /**
     * @param string $representation
     */
    public function unserialize($representation)
    {
        list($fqsen_class, $fqsen, $this->original_name, $this->lineno, $this->is_used) = unserialize($representation);
        if (!is_string($fqsen_class) || !is_subclass_of($fqsen_class, FullyQualifiedGlobalStructuralElement::class)) {
            // Should not happen
            throw new \RuntimeException("Not a global fqsen: class " . var_export($fqsen_class));
        }
        $this->fqsen = $fqsen_class::fromFullyQualifiedString($fqsen);
    }
}
<?php declare(strict_types=1);
namespace Phan\Language;

use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\EmptyFQSENException;
use Phan\Language\Element\Comment;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\ArrayShapeType;
use Phan\Language\Type\BoolType;
use Phan\Language\Type\CallableDeclarationType;
use Phan\Language\Type\CallableType;
use Phan\Language\Type\ClosureType;
use Phan\Language\Type\ClosureDeclarationParameter;
use Phan\Language\Type\ClosureDeclarationType;
use Phan\Language\Type\FalseType;
use Phan\Language\Type\FloatType;
use Phan\Language\Type\FunctionLikeDeclarationType;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\GenericMultiArrayType;
use Phan\Language\Type\IntType;
use Phan\Language\Type\IterableType;
use Phan\Language\Type\MixedType;
use Phan\Language\Type\NativeType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\ObjectType;
use Phan\Language\Type\ResourceType;
use Phan\Language\Type\ScalarRawType;
use Phan\Language\Type\StaticType;
use Phan\Language\Type\StringType;
use Phan\Language\Type\TrueType;
use Phan\Language\Type\VoidType;
use Phan\Language\UnionType;
use Phan\Library\None;
use Phan\Library\Option;
use Phan\Library\Some;
use Phan\Library\Tuple5;

use InvalidArgumentException;

/**
 * The base class for all of Phan's types.
 * A plain Type represents a class instance.
 * Separate subclasses exist for NativeType, ArrayType, ScalarType, TemplateType, etc.
 *
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 * @phan-file-suppress PhanPartialTypeMismatchArgumentInternal
 */
class Type
{
    use \Phan\Memoize;

    /**
     * @var string
     * A legal type identifier (e.g. 'int' or 'DateTime')
     */
    const simple_type_regex =
        '(\??)[a-zA-Z_\x7f-\xff\\\][a-zA-Z0-9_\x7f-\xff\\\]*';

    const simple_noncapturing_type_regex =
        '\??[a-zA-Z_\x7f-\xff\\\][a-zA-Z0-9_\x7f-\xff\\\]*';

    /**
     * @var string
     * A legal type identifier (e.g. 'int' or 'DateTime')
     */
    const simple_type_regex_or_this =
        '(\??)([a-zA-Z_\x7f-\xff\\\][a-zA-Z0-9_\x7f-\xff\\\]*|\$this)';

    const shape_key_regex =
        '[-._a-zA-Z0-9\x7f-\xff]+\??';

    /**
     * @var string
     * A legal array entry in an array shape (e.g. 'field:string[]')
     *
     * @suppress PhanUnreferencedPublicClassConstant
     */
    const array_shape_entry_regex_noncapturing =
        '(?:' . self::shape_key_regex . ')\s*:\s*(?:' . self::simple_noncapturing_type_regex . '=?)';

    /**
     * @var string
     * A legal type identifier matching a type optionally with a []
     * indicating that it's a generic typed array (e.g. 'int[]',
     * 'string' or 'Set<DateTime>' or 'array{field:string}')
     *
     * https://www.debuggex.com/ is useful for a visual description of these regexes
     */
    const type_regex =
        '('
        . '(?:\??\((?-1)\)|'  // Recursion: "?(T)" or "(T)" with brackets
        // TODO: Allow parsing nesting.
        . '(?:'
          . '\??(?:\\\\?Closure|callable)(\([^()]*\))'
          . '(?:\s*:\s*'  // optional return type, can be ":T" or ":(T1|T2)" or ": ?(T1|T2)"
            . '(?:'
              . self::simple_noncapturing_type_regex . '|'  // Forbid ambiguity in `Closure():int[]` by disallowing complex return types without '()'. Always parse that as `(Closure():int)[]`.
              . '\((?-2)(?:\s*\|\s*(?-2))*\)'
            . ')'
          . ')?'
        . ')|'
        . '(' . self::simple_type_regex . ')'  // ?T or T. TODO: Get rid of pattern for '?'?
        . '(?:'
          . '<'
            . '('
              . '(?-5)(?:\|(?-5))*'
              . '(\s*,\s*'
                . '(?-6)(?:\|(?-6))*'
              . ')*'
            . ')'
          . '>'
          . '|'
          . '\{('  // Expect either '{' or '<', after a word token.
            . '(?:' . self::shape_key_regex . '\s*:\s*(?-7)(?:\|(?-7))*=?)'  // {shape_key_regex:<type_regex>}
            . '(?:,' . self::shape_key_regex . '\s*:\s*(?-7)(?:\|(?-7))*=?)*'  // {shape_key_regex:<type_regex>}
          . ')?\})?'
        . ')'
        . '(\[\])*'
      . ')';

    /**
     * @var string
     * A legal type identifier matching a type optionally with a []
     * indicating that it's a generic typed array (e.g. 'int[]' or '$this[]',
     * 'string' or 'Set<DateTime>' or 'array<int>' or 'array<int|string>')
     *
     * https://www.debuggex.com/ is useful for a visual description of these regexes
     */
    const type_regex_or_this =
        '('
        . '('
          . '(?:'
            . '\??\((?-1)\)|'
            // TODO: Support nesting
            . '(?:'
              . '\??(?:\\\\?Closure|callable)(\([^()]*\))'
              . '(?:\s*:\s*'  // optional return type, can be ":T" or ":(T1|T2)"
                . '(?:'
                  . self::simple_noncapturing_type_regex . '|'  // Forbid ambiguity in `Closure():int[]` by disallowing complex return types without '()'. Always parse that as `(Closure():int)[]`.
                  . '\((?-2)(?:\s*\|\s*(?-2))*\)'  // Complicated return types can be placed within ().
                . ')'
              . ')?'
            . ')|'
            . '(' . self::simple_type_regex_or_this . ')'  // 3 patterns
            . '(?:<'
              . '('
                . '(?-6)(?:\|(?-6))*'  // We use relative references instead of named references so that more than one one type_regex can be used in a regex.
                . '(\s*,\s*'
                  . '(?-7)(?:\|(?-7))*'
                . ')*'
              . ')'
              . '>'
              . '|'
              . '(\{)('  // Expect either '{' or '<', after a word token. Match '{' to disambiguate 'array{}'
                . '(?:' . self::shape_key_regex . '\s*:\s*(?-9)(?:\|(?-9))*=?)'  // {shape_key_regex:<type_regex>}
                . '(?:,' . self::shape_key_regex . '\s*:\s*(?-9)(?:\|(?-9))*=?)*'  // {shape_key_regex:<type_regex>}
              . ')?\})?'
            . ')'
          . '(\[\])*'
        . ')'
       . ')';

    /**
     * @var array<string,bool> - For checking if a string is an internal type. This is used for case insensitive lookup.
     */
    const _internal_type_set = [
        'array'     => true,
        'bool'      => true,
        'callable'  => true,
        'false'     => true,
        'float'     => true,
        'int'       => true,
        'iterable'  => true,
        'mixed'     => true,
        'null'      => true,
        'object'    => true,
        'resource'  => true,
        'scalar'    => true,
        'static'    => true,
        'string'    => true,
        'true'      => true,
        'void'      => true,
    ];

    /**
     * These can currently be used in phpdoc but not real types.
     * This is a subset of self::_internal_type_set
     *
     * https://secure.php.net/manual/en/reserved.other-reserved-words.php
     * > The following list of words have had soft reservations placed on them.
     * > Whilst they may still be used as class, interface, and trait names (as well as in namespaces),
     * > usage of them is highly discouraged since they may be used in future versions of PHP.
     *
     * (numeric not supported yet)
     */
    const _soft_internal_type_set = [
        'false'     => true,
        'mixed'     => true,
        'object'    => true,
        'resource'  => true,
        'scalar'    => true,
        'true'      => true,
    ];

    // Distinguish between multiple ways types can be created.
    // e.g. integer and resource are phpdoc types, but they aren't actual types.

    /** For types created from a type in an AST node, e.g. `int $x` */
    const FROM_NODE = 0;

    /** For types copied from another type, e.g. `$x = $y` gets types from $y */
    const FROM_TYPE = 1;

    /** For types copied from phpdoc, e.g. `(at)param integer $x` */
    const FROM_PHPDOC = 2;

    /** To distinguish NativeType subclasses and classes with the same name. Overridden in subclasses */
    const KEY_PREFIX = '';

    /** To normalize combinations of union types */
    const _bit_false    = (1 << 0);
    const _bit_true     = (1 << 1);
    const _bit_bool_combination = self::_bit_false | self::_bit_true;
    const _bit_nullable = (1 << 2);

    /**
     * @var string
     * The namespace of this type such as '' (for internal types such as 'int')
     * or '\' or '\Phan\Language'
     */
    protected $namespace = null;

    /**
     * @var string
     * The name of this type such as 'int' or 'MyClass'
     */
    protected $name = '';

    /**
     * @var array<int,UnionType>
     * A possibly empty list of concrete types that
     * act as parameters to this type if it is a templated
     * type.
     */
    protected $template_parameter_type_list = [];

    /**
     * @var bool
     * True if this type is nullable, else false
     */
    protected $is_nullable = false;

    /**
     * @var array<string,Type> - Maps a key to a Type or subclass of Type
     */
    private static $canonical_object_map = [];

    /**
     * @param string $namespace
     * The (optional) namespace of the type such as '\'
     * or '\Phan\Language'.
     *
     * @param string $name
     * The name of the type such as 'int' or 'MyClass'
     *
     * @param array<int,UnionType> $template_parameter_type_list
     * A (possibly empty) list of template parameter types
     *
     * @param bool $is_nullable
     * True if this type can be null, false if it cannot
     * be null.
     */
    protected function __construct(
        string $namespace,
        string $name,
        $template_parameter_type_list,
        bool $is_nullable
    ) {
        $this->namespace = $namespace;
        $this->name = $name;
        $this->template_parameter_type_list = $template_parameter_type_list;
        $this->is_nullable = $is_nullable;
    }

    // Override two magic methods to ensure that Type isn't being cloned accidentally.
    // (It has previously been accidentally cloned in unit tests by phpunit (global_state helper),
    //  which saves and restores some static properties)
    public function __wakeup()
    {
        throw new \Error("Cannot unserialize Type");
    }

    public function __clone()
    {
        throw new \Error("Cannot clone Type");
    }

    /**
     * @param string $namespace
     * The (optional) namespace of the type such as '\'
     * or '\Phan\Language'.
     *
     * @param string $type_name
     * The name of the type such as 'int' or 'MyClass'
     *
     * @param array<int,UnionType> $template_parameter_type_list
     * A (possibly empty) list of template parameter types
     *
     * @param bool $is_nullable
     * True if this type can be null, false if it cannot
     * be null.
     *
     * @param int $source
     * Type::FROM_NODE, Type::FROM_TYPE, or Type::FROM_PHPDOC
     * (E.g. outside of phpdoc, "integer" would be a class name)
     *
     * @return Type
     * A single canonical instance of the given type.
     */
    protected static function make(
        string $namespace,
        string $type_name,
        array $template_parameter_type_list,
        bool $is_nullable,
        int $source
    ) : Type {

        $namespace = \trim($namespace);

        if ('\\' === $namespace && $source) {
            $type_name = self::canonicalNameFromName($type_name);
        }

        // If this looks like a generic type string, explicitly
        // make it as such
        if (self::isGenericArrayString($type_name)
            && ($pos = \strrpos($type_name, '[]')) !== false
        ) {
            return GenericArrayType::fromElementType(Type::make(
                $namespace,
                \substr($type_name, 0, $pos),
                $template_parameter_type_list,
                false,
                $source
            ), $is_nullable, GenericArrayType::KEY_MIXED);
        }

        \assert(
            !empty($namespace),
            "Namespace cannot be empty"
        );

        \assert(
            '\\' === $namespace[0],
            "Namespace must be fully qualified"
        );

        \assert(
            !empty($type_name),
            "Type name cannot be empty"
        );

        if (\strpos($type_name, '|') !== false) {
            throw new \AssertionError("Type name '$type_name' may not contain a pipe");
        }

        // Create a canonical representation of the
        // namespace and name
        $namespace = $namespace ?: '\\';
        if ('\\' === $namespace && $source === Type::FROM_PHPDOC) {
            $type_name = self::canonicalNameFromName($type_name);
        }

        // Make sure we only ever create exactly one
        // object for any unique type
        $key = ($is_nullable ? '?' : '') . static::KEY_PREFIX . $namespace . '\\' . $type_name;

        if ($template_parameter_type_list) {
            $key .= '<' . \implode(',', \array_map(function (UnionType $union_type) {
                return (string)$union_type;
            }, $template_parameter_type_list)) . '>';
        }

        $key = \strtolower($key);

        $value = self::$canonical_object_map[$key] ?? null;
        if (!$value) {
            if ($type_name === 'Closure' && $namespace === '\\') {
                $value = new ClosureType(
                    $namespace,
                    $type_name,
                    $template_parameter_type_list,
                    $is_nullable
                );
            } else {
                $value = new static(
                    $namespace,
                    $type_name,
                    $template_parameter_type_list,
                    $is_nullable
                );
            }
            self::$canonical_object_map[$key] = $value;
        }
        return $value;
    }

    /**
     * Call this before forking and analysis phase, when in daemon mode.
     * This may hurt performance.
     *
     * It's important to clear asExpandedTypes(),
     * as the parent classes may have changed since the last parse attempt.
     *
     * This gets called immediately after the parse phase but before the analysis phase.
     *
     * @return void
     */
    public static function clearAllMemoizations()
    {
        // Clear anything that has memoized state
        foreach (self::$canonical_object_map as $type) {
            $type->memoizeFlushAll();
        }
    }


    /**
     * @param Type $type
     * The base type of this generic type referencing a
     * generic class
     *
     * @param array<int,UnionType> $template_parameter_type_list
     * A map from a template type identifier to a
     * concrete union type
     */
    public static function fromType(
        Type $type,
        array $template_parameter_type_list
    ) : Type {
        return self::make(
            $type->getNamespace(),
            $type->getName(),
            $template_parameter_type_list,
            $type->getIsNullable(),
            Type::FROM_TYPE
        );
    }

    /**
     * If the $name is a reserved constant, then returns the NativeType for that name
     * Otherwise, this returns null.
     * @return Option<NativeType>
     */
    public static function fromReservedConstantName(string $name) : Option
    {
        static $lookup;
        if ($lookup === null) {
            $lookup = self::createReservedConstantNameLookup();
        }
        $result = $lookup[\strtoupper(\ltrim($name, '\\'))] ?? null;
        if (isset($result)) {
            return new Some($result);
        }
        return new None;
    }

    /**
     * @return array<string,NativeType> a map from the **uppercase** reserved constant name to the subclass of NativeType for that constant.
     * Uses the constants and types from https://secure.php.net/manual/en/reserved.constants.php
     */
    private static function createReservedConstantNameLookup() : array
    {
        $false  = FalseType::instance(false);
        // $float  = FloatType::instance(false);
        $int    = IntType::instance(false);
        $null   = NullType::instance(false);
        $string = StringType::instance(false);
        $true   = TrueType::instance(false);

        return [
            'PHP_VERSION'           => $string,
            'PHP_MAJOR_VERSION'     => $int,
            'PHP_MINOR_VERSION'     => $int,
            'PHP_RELEASE_VERSION'   => $int,
            'PHP_VERSION_ID'        => $int,
            'PHP_EXTRA_VERSION'     => $string,
            'PHP_ZTS'               => $int,
            'PHP_MAXPATHLEN'        => $int,
            'PHP_OS'                => $string,
            'PHP_OS_FAMILY'         => $string,
            'PHP_SAPI'              => $string,
            'PHP_EOL'               => $string,
            'PHP_INT_MAX'           => $int,
            'PHP_INT_MIN'           => $int,  // since 7.0.0
            'PHP_INT_SIZE'          => $int,  // since 7.0.0
            //'PHP_FLOAT_DIG'         => $int,  // since 7.2.0
            //'PHP_FLOAT_EPSILON'     => $float,  // since 7.2.0
            //'PHP_FLOAT_MIN'         => $int, // since 7.2.0
            //'PHP_FLOAT_MAX'         => $int, // since 7.2.0
            'DEFAULT_INCLUDE_PATH'  => $string,
            'PEAR_INSTALL_DIR'      => $string,
            'PHP_EXTENSION_DIR'     => $string,
            'PEAR_EXTENSION_DIR'    => $string,
            'PHP_PREFIX'            => $string,
            'PHP_BINDIR'            => $string,
            'PHP_BINARY'            => $string,
            'PHP_MANDIR'            => $string,
            'PHP_LIBDIR'            => $string,
            'PHP_DATADIR'           => $string,
            'PHP_SYSCONFDIR'        => $string,
            'PHP_LOCALSTATEDIR'     => $string,
            'PHP_CONFIG_FILE_PATH'  => $string,
            'PHP_CONFIG_FILE_SCAN_DIR' => $string,
            'PHP_SHLIB_SUFFIX'      => $string,
            //'PHP_FD_SETSIZE'            => $int,  // 7.2.0 TODO: web page documentation is wrong, says string.
            'E_ERROR'               => $int,
            'E_WARNING'             => $int,
            'E_PARSE'               => $int,
            'E_NOTICE'              => $int,
            'E_CORE_ERROR'          => $int,
            'E_CORE_WARNING'        => $int,
            'E_COMPILE_ERROR'       => $int,
            'E_COMPILE_WARNING'     => $int,
            'E_USER_ERROR'          => $int,
            'E_USER_WARNING'        => $int,
            'E_USER_NOTICE'         => $int,
            'E_DEPRECATED'          => $int,
            'E_USER_DEPRECATED'     => $int,
            'E_ALL'                 => $int,
            'E_STRICT'              => $int,
            '__COMPILER_HALT_OFFSET__' => $int,
            '__LINE__'              => $int,
            'TRUE'                  => $true,
            'FALSE'                 => $false,
            'NULL'                  => $null,
        ];
    }

    /**
     * @return Type
     * Get a type for the given object
     */
    public static function fromObject($object) : Type
    {
        static $type_map = null;
        if ($type_map === null) {
            $type_map = [
                'integer' => IntType::instance(false),
                'boolean' => BoolType::instance(false),
                'double'   => FloatType::instance(false),
                'string'  => StringType::instance(false),
                'object'  => ObjectType::instance(false),
                'NULL'    => NullType::instance(false),
                'array'   => ArrayType::instance(false),
                'resource' => ResourceType::instance(false),  // For inferring the type of constants STDIN, etc.
            ];
        }
        // gettype(2) doesn't return 'int', it returns 'integer', so use FROM_PHPDOC
        return $type_map[\gettype($object)];
    }

    /**
     * @param string $type_name
     * The name of the internal type such as 'int'
     *
     * @param bool $is_nullable
     * Set to true if the type should be nullable, else pass
     * false
     *
     * @param int $source Type::FROM_*
     *
     * @return Type
     * Get a type for the given type name
     */
    public static function fromInternalTypeName(
        string $type_name,
        bool $is_nullable,
        int $source
    ) : Type {

        // If this is a generic type (like int[]), return
        // a generic of internal types.
        //
        // When there's a nullability operator such as in
        // `?int[]`, it applies to the array rather than
        // the int
        if (false !== ($pos = \strrpos($type_name, '[]'))) {
            return GenericArrayType::fromElementType(
                self::fromInternalTypeName(
                    \substr($type_name, 0, $pos),
                    false,
                    $source
                ),
                $is_nullable,
                GenericArrayType::KEY_MIXED
            );
        }

        $type_name =
            self::canonicalNameFromName($type_name);

        // TODO: Is this worth optimizing into a lookup table?
        switch (\strtolower($type_name)) {
            case 'array':
                return ArrayType::instance($is_nullable);
            case 'bool':
                return BoolType::instance($is_nullable);
            case 'callable':
                return CallableType::instance($is_nullable);
            case 'closure':
                return ClosureType::instance($is_nullable);
            case 'false':
                return FalseType::instance($is_nullable);
            case 'float':
                return FloatType::instance($is_nullable);
            case 'int':
                return IntType::instance($is_nullable);
            case 'mixed':
                return MixedType::instance($is_nullable);
            case 'null':
                return NullType::instance($is_nullable);
            case 'object':
                return ObjectType::instance($is_nullable);
            case 'resource':
                return ResourceType::instance($is_nullable);
            case 'scalar':
                return ScalarRawType::instance($is_nullable);
            case 'string':
                return StringType::instance($is_nullable);
            case 'true':
                return TrueType::instance($is_nullable);
            case 'void':
                // TODO: This can't be nullable, right?
                return VoidType::instance($is_nullable);
            case 'iterable':
                return IterableType::instance($is_nullable);
            case 'static':
                return StaticType::instance($is_nullable);
            case '$this':
                return StaticType::instance($is_nullable);
        }

        if (\substr($type_name, 0, 1) === '?') {
            return self::fromInternalTypeName(\substr($type_name, 1), true, $source);
        }
        throw new \AssertionError("No internal type with name $type_name");
    }

    /**
     * @param string $namespace
     * A fully qualified namespace
     *
     * @param string $type_name
     * The name of the type
     *
     * @return Type
     * A type representing the given namespace and type
     * name.
     *
     * @param bool $is_nullable
     * True if this type can be null, false if it cannot
     * be null.
     */
    public static function fromNamespaceAndName(
        string $namespace,
        string $type_name,
        bool $is_nullable
    ) : Type {
        return self::make($namespace, $type_name, [], $is_nullable, Type::FROM_NODE);
    }

    public static function fromReflectionType(
        \ReflectionType $reflection_type
    ) : Type {

        return self::fromStringInContext(
            (string)$reflection_type,
            new Context(),
            Type::FROM_NODE
        );
    }

    /**
     * @param string $fully_qualified_string
     * A fully qualified type name
     *
     * @return Type
     */
    public static function fromFullyQualifiedString(
        string $fully_qualified_string
    ) : Type {
        static $type_cache = [];
        return $type_cache[$fully_qualified_string] ?? ($type_cache[$fully_qualified_string] = self::fromFullyQualifiedStringInner($fully_qualified_string));
    }


    public static function fromFullyQualifiedStringInner(
        string $fully_qualified_string
    ) : Type {
        \assert(
            !empty($fully_qualified_string),
            "Type cannot be empty"
        );
        while (\substr($fully_qualified_string, -1) === ')') {
            if ($fully_qualified_string[0] === '?') {
                $fully_qualified_string = '?' . \substr($fully_qualified_string, 2, -1);
            } else {
                $fully_qualified_string = \substr($fully_qualified_string, 1, -1);
            }
        }
        if (\substr($fully_qualified_string, -2) === '[]') {
            if ($fully_qualified_string[0] === '?') {
                $is_nullable = true;
                $fully_qualified_substring = \substr($fully_qualified_string, 1, -2);
            } else {
                $is_nullable = false;
                $fully_qualified_substring = \substr($fully_qualified_string, 0, -2);
            }
            return GenericArrayType::fromElementType(
                Type::fromFullyQualifiedString($fully_qualified_substring),
                $is_nullable,
                GenericArrayType::KEY_MIXED
            );
        }

        $tuple = self::typeStringComponents($fully_qualified_string);

        $namespace = $tuple->_0;
        $type_name = $tuple->_1;
        $template_parameter_type_name_list = $tuple->_2;
        $is_nullable = $tuple->_3;
        $shape_components = $tuple->_4;
        if (\is_array($shape_components)) {
            if (\strcasecmp($type_name, 'array') === 0) {
                return ArrayShapeType::fromFieldTypes(
                    self::shapeComponentStringsToTypes($shape_components, new Context(), Type::FROM_NODE),
                    $is_nullable
                );
            }
            if ($type_name === 'Closure' || $type_name === 'callable') {
                return self::fromFullyQualifiedFunctionLike($type_name === 'Closure', $shape_components, $is_nullable);
            }
        }

        if (empty($namespace)) {
            if (\strcasecmp($type_name, 'array') === 0 && !empty($template_parameter_type_name_list)) {
                // template parameter type list
                $template_parameter_type_list = \array_map(function (string $type_name) {
                    return UnionType::fromFullyQualifiedString($type_name);
                }, $template_parameter_type_name_list);
                return self::parseGenericArrayTypeFromTemplateParameterList($template_parameter_type_list, $is_nullable);
            }
            return self::fromInternalTypeName(
                $fully_qualified_string,
                $is_nullable,
                Type::FROM_NODE
            );
        }

        // Map the names of the types to actual types in the
        // template parameter type list
        $template_parameter_type_list = \array_map(function (string $type_name) {
            return UnionType::fromFullyQualifiedString($type_name);
        }, $template_parameter_type_name_list);

        if (0 !== \strpos($namespace, '\\')) {
            $namespace = '\\' . $namespace;
        }

        if ($type_name === '') {
            throw new EmptyFQSENException("Type was not fully qualified", $fully_qualified_string);
        }
        if ($namespace === '') {
            throw new InvalidArgumentException("Type was not fully qualified");
        };

        return self::make(
            $namespace,
            $type_name,
            $template_parameter_type_list,
            $is_nullable,
            Type::FROM_NODE
        );
    }

    /**
     * @param bool $is_closure_type
     * @param array<int,string> $shape_components
     * @param bool $is_nullable
     */
    private static function fromFullyQualifiedFunctionLike(
        bool $is_closure_type,
        array $shape_components,
        bool $is_nullable
    ) : FunctionLikeDeclarationType {
        $return_type = \array_pop($shape_components);
        if (!$return_type) {
            // shouldn't happen
            throw new \AssertionError("Expected at least one component of a closure phpdoc type");
        }
        if ($return_type[0] === '(' && \substr($return_type, -1) === ')') {
            // TODO: Maybe catch that in UnionType parsing instead
            $return_type = \substr($return_type, 1, -1);
        }
        $params = self::closureParamComponentStringsToParams($shape_components, new Context(), Type::FROM_NODE);
        $return_type = UnionType::fromStringInContext($return_type, new Context(), Type::FROM_NODE);

        if ($is_closure_type) {
            return new ClosureDeclarationType(new Context(), $params, $return_type, false, $is_nullable);
        } else {
            return new CallableDeclarationType(new Context(), $params, $return_type, false, $is_nullable);
        }
    }
    /**
     * @param array<int,UnionType> $template_parameter_type_list
     * @param bool $is_nullable
     */
    private static function parseGenericArrayTypeFromTemplateParameterList(
        array $template_parameter_type_list,
        bool $is_nullable
    ) : Type {
        $template_count = \count($template_parameter_type_list);
        if ($template_count <= 2) {  // array<T> or array<key, T>
            $key_type = ($template_count === 2)
                ? GenericArrayType::keyTypeFromUnionTypeValues($template_parameter_type_list[0])
                : GenericArrayType::KEY_MIXED;
            $types = $template_parameter_type_list[$template_count - 1]->getTypeSet();
            if (\count($types) === 1) {
                return GenericArrayType::fromElementType(
                    \reset($types),
                    $is_nullable,
                    $key_type
                );
            } elseif (\count($types) > 1) {
                return new GenericMultiArrayType(
                    $types,
                    $is_nullable,
                    $key_type
                );
            }
        }
        return ArrayType::instance($is_nullable);
    }

    /**
     * @param string $string
     * A string representing a type
     *
     * @param Context $context
     * The context in which the type string was
     * found
     *
     * @param int $source
     * Type::FROM_NODE, Type::FROM_TYPE, or Type::FROM_PHPDOC
     *
     * @return Type
     * Parse a type from the given string
     */
    public static function fromStringInContext(
        string $string,
        Context $context,
        int $source
    ) : Type {
        \assert(
            $string !== '',
            "Type cannot be empty"
        );
        while (\substr($string, -1) === ')') {
            if ($string[0] === '?') {
                if ($string[1] !== '(') {
                    // Account for the Closure(params...):return syntax
                    break;
                }
                $string = '?' . \substr($string, 2, -1);
            } else {
                if ($string[0] !== '(') {
                    break;
                }
                $string = \substr($string, 1, -1);
            }
        }

        if (\substr($string, -2) === '[]') {
            if ($string[0] === '?') {
                $is_nullable = true;
                $substring = \substr($string, 1, -2);
            } else {
                $is_nullable = false;
                $substring = \substr($string, 0, -2);
            }
            if ($substring === '') {
                return ArrayType::instance($is_nullable);
            }
            return GenericArrayType::fromElementType(
                self::fromStringInContext(
                    $substring,
                    $context,
                    $source
                ),
                $is_nullable,
                GenericArrayType::KEY_MIXED
            );
        }

        // Extract the namespace, type and parameter type name list
        $tuple = self::typeStringComponents($string);

        $namespace = $tuple->_0;
        $type_name = $tuple->_1;
        $template_parameter_type_name_list = $tuple->_2;
        $is_nullable = $tuple->_3;
        $shape_components = $tuple->_4;
        if (\is_array($shape_components)) {
            if (\strcasecmp($type_name, 'array') === 0) {
                return ArrayShapeType::fromFieldTypes(
                    self::shapeComponentStringsToTypes($shape_components, $context, $source),
                    $is_nullable
                );
            }
            if ($type_name === 'Closure' || $type_name === 'callable') {
                return self::fromFunctionLikeInContext($type_name === 'Closure', $shape_components, $context, $source, $is_nullable);
            }
        }

        // Map the names of the types to actual types in the
        // template parameter type list
        $template_parameter_type_list =
            array_map(function (string $type_name) use ($context, $source) {
                return UnionType::fromStringInContext($type_name, $context, $source);
            }, $template_parameter_type_name_list);

        // @var bool
        // True if this type name if of the form 'C[]'
        $is_generic_array_type =
            self::isGenericArrayString($type_name);

        // If this is a generic array type, get the name of
        // the type of each element
        $non_generic_array_type_name = $type_name;
        if ($is_generic_array_type
           && false !== ($pos = \strrpos($type_name, '[]'))
        ) {
            $non_generic_array_type_name =
                \substr($type_name, 0, $pos);
        }

        // Check to see if the type name is mapped via
        // a using clause.
        //
        // Gotta check this before checking for native types
        // because there are monsters out there that will
        // remap the names via things like `use \Foo\String`.
        $non_generic_partially_qualified_array_type_name =
            $non_generic_array_type_name;
        if ($namespace) {
            $non_generic_partially_qualified_array_type_name =
                $namespace . '\\' . $non_generic_partially_qualified_array_type_name;
        }

        if ($is_generic_array_type && false !== \strrpos($non_generic_array_type_name, '[]')) {
            return GenericArrayType::fromElementType(
                Type::fromStringInContext($non_generic_partially_qualified_array_type_name, $context, $source),
                $is_nullable,
                GenericArrayType::KEY_MIXED
            );
        }
        // TODO: Will \MyClass[] accidentally check the namespace map for use OtherNS\MyClass?
        if ($context->hasNamespaceMapFor(
            \ast\flags\USE_NORMAL,
            $non_generic_partially_qualified_array_type_name
        )) {
            $fqsen =
                $context->getNamespaceMapFor(
                    \ast\flags\USE_NORMAL,
                    $non_generic_partially_qualified_array_type_name
                );

            if ($is_generic_array_type) {
                return GenericArrayType::fromElementType(
                    Type::make(
                        $fqsen->getNamespace(),
                        $fqsen->getName(),
                        $template_parameter_type_list,
                        false,
                        $source
                    ),
                    $is_nullable,
                    GenericArrayType::KEY_MIXED
                );
            }

            return Type::make(
                $fqsen->getNamespace(),
                $fqsen->getName(),
                $template_parameter_type_list,
                $is_nullable,
                $source
            );
        }

        // If this was a fully qualified type, we're all
        // set
        if (!empty($namespace) && 0 === \strpos($namespace, '\\')) {
            return self::make(
                $namespace,
                $type_name,
                $template_parameter_type_list,
                $is_nullable,
                $source
            );
        }

        if (self::isInternalTypeString($type_name, $source)) {
            if (!empty($template_parameter_type_list)) {
                if (\strtolower($type_name) === 'array') {
                    return self::parseGenericArrayTypeFromTemplateParameterList($template_parameter_type_list, $is_nullable);
                }
                // TODO: Warn about unrecognized types.
            }
            return self::fromInternalTypeName($type_name, $is_nullable, $source);
        }

        // Things like `self[]` or `$this[]`
        if ($is_generic_array_type
            && self::isSelfTypeString($non_generic_array_type_name)
            && $context->isInClassScope()
        ) {
            // Callers of this method should be checking on their own
            // to see if this type is a reference to 'parent' and
            // dealing with it there. We don't want to have this
            // method be dependent on the code base
            \assert(
                'parent' !== $non_generic_array_type_name,
                __METHOD__ . " does not know how to handle the type name 'parent'"
            );

            return GenericArrayType::fromElementType(
                static::fromFullyQualifiedString(
                    (string)$context->getClassFQSEN()
                ),
                $is_nullable,
                GenericArrayType::KEY_MIXED
            );
        }

        // If this is a type referencing the current class
        // in scope such as 'self' or 'static', return that.
        if (self::isSelfTypeString($type_name)
            && $context->isInClassScope()
        ) {
            // Callers of this method should be checking on their own
            // to see if this type is a reference to 'parent' and
            // dealing with it there. We don't want to have this
            // method be dependent on the code base
            \assert(
                'parent' !== $type_name,
                __METHOD__ . " does not know how to handle the type name 'parent'"
            );

            return static::fromFullyQualifiedString(
                (string)$context->getClassFQSEN()
            )->withIsNullable($is_nullable);
        }

        // Merge the current namespace with the given relative
        // namespace
        if (!empty($context->getNamespace()) && !empty($namespace)) {
            $namespace = $context->getNamespace() . '\\' . $namespace;
        } elseif (!empty($context->getNamespace())) {
            $namespace = $context->getNamespace();
        } else {
            $namespace = '\\' . $namespace;
        }

        // Attach the context's namespace to the type name
        return self::make(
            $namespace,
            $type_name,
            $template_parameter_type_list,
            $is_nullable,
            $source
        );
    }

    /**
     * @param bool $is_closure_type
     * @param array<int,string> $shape_components
     * @param Context $context
     * @param int $source
     * @param bool $is_nullable
     */
    private static function fromFunctionLikeInContext(
        bool $is_closure_type,
        array $shape_components,
        Context $context,
        int $source,
        bool $is_nullable
    ) : FunctionLikeDeclarationType {
        $return_type = \array_pop($shape_components);
        if (!$return_type) {
            throw new \AssertionError("Expected a return type");
        }
        if ($return_type[0] === '(' && \substr($return_type, -1) === ')') {
            $return_type = \substr($return_type, 1, -1);
        }
        $params = self::closureParamComponentStringsToParams($shape_components, $context, $source);
        $return = UnionType::fromStringInContext($return_type, $context, $source);
        if ($is_closure_type) {
            return new ClosureDeclarationType($context, $params, $return, false, $is_nullable);
        } else {
            return new CallableDeclarationType($context, $params, $return, false, $is_nullable);
        }
    }

    /**
     * @param array<string|int,string> $shape_components Maps field keys (integers or strings) to the corresponding type representations
     * @param Context $context
     * @param int $source
     * @return array<string|int,UnionType> The types for the representations of types, in the given $context
     */
    private static function shapeComponentStringsToTypes(array $shape_components, Context $context, int $source) : array
    {
        $result = [];
        foreach ($shape_components as $key => $component_string) {
            if (\is_string($key) && \substr($key, -1) === '?') {
                if (\substr($component_string, -1) === '=') {
                    $component_string = \substr($component_string, 0, -1);
                }
                $key = \substr($key, 0, -1);
                $result[$key] = UnionType::fromStringInContext($component_string, $context, $source)->withIsPossiblyUndefined(true);
            } elseif (\substr($component_string, -1) === '=') {
                $component_string = \substr($component_string, 0, -1);
                $result[$key] = UnionType::fromStringInContext($component_string, $context, $source)->withIsPossiblyUndefined(true);
            } else {
                $result[$key] = UnionType::fromStringInContext($component_string, $context, $source);
            }
        }
        return $result;
    }

    /**
     * @param array<int,string> $param_components Maps field keys (integers or strings) to the corresponding type representations
     * @param Context $context
     * @param int $source
     * @return array<int,ClosureDeclarationParameter> The types for the representations of types, in the given $context
     *
     * @see Comment::magicParamFromMagicMethodParamString() - This is similar but has minor differences, such as references
     */
    private static function closureParamComponentStringsToParams(array $param_components, Context $context, int $source) : array
    {
        // TODO: Allow () within param types...
        $result = [];
        foreach ($param_components as $param_string) {
            if ($param_string === '') {
                // TODO: warn
                continue;
            }
            if (preg_match('/^(' . UnionType::union_type_regex . ')?\s*(&\s*)?(?:(\.\.\.)\s*)?(?:\$' . Comment::WORD_REGEX . ')?((?:\s*=.*)?)$/', $param_string, $param_match)) {
                // Note: a closure declaration can be by reference, unlike (at)method
                $union_type_string = $param_match[1] ?: 'mixed';
                $union_type = UnionType::fromStringInContext(
                    $union_type_string,
                    $context,
                    $source
                );
                $is_reference = $param_match[19] !== '';
                $is_variadic = $param_match[20] === '...';
                $default_str = $param_match[22];
                $has_default_value = $default_str !== '';
                if ($has_default_value) {
                    $default_value_repr = trim(explode('=', $default_str, 2)[1]);
                    if (strcasecmp($default_value_repr, 'null') === 0) {
                        $union_type = $union_type->nullableClone();
                    }
                }
                // $var_name = $param_match[19]; // would be unused
                $result[] = new ClosureDeclarationParameter($union_type, $is_variadic, $is_reference, $has_default_value);
            }  // TODO: Otherwise, warn
        }
        return $result;
    }


    /**
     * @var ?UnionType of [$this]
     */
    protected $singleton_union_type;

    /**
     * @return UnionType
     * A UnionType representing this and only this type
     */
    public function asUnionType() : UnionType
    {
        // return new UnionType([$this]);
        // Memoize the set of types. The constructed UnionType object can be modified later, so it isn't memoized.
        return $this->singleton_union_type ?? ($this->singleton_union_type = new UnionType([$this], true));
    }

    /**
     * @return FQSEN
     * A fully-qualified structural element name derived
     * from this type
     */
    public function asFQSEN() : FQSEN
    {
        // Note: some subclasses, such as CallableType, return different subtypes of FQSEN
        return FullyQualifiedClassName::fromType($this);
    }

    /**
     * @return FullyQualifiedClassName
     * A fully-qualified class name derived from this type
     * (This differs from asFQSEN() in ClosureType)
     *
     * @deprecated
     * @suppress PhanUnreferencedPublicMethod (just use FullyQualifiedClassName::fromType($this))
     */
    public function asClassFQSEN() : FullyQualifiedClassName
    {
        // Note: some subclasses, such as CallableType, return different subtypes of FQSEN
        return FullyQualifiedClassName::fromType($this);
    }

    /**
     * @return string
     * The name associated with this type
     */
    public function getName() : string
    {
        return $this->name;
    }

    /**
     * @return bool
     * True if this namespace is defined
     * @suppress PhanUnreferencedPublicMethod (TODO: remove?)
     */
    public function hasNamespace() : bool
    {
        return !empty($this->namespace);
    }

    /**
     * @return string
     * The namespace associated with this type
     */
    public function getNamespace() : string
    {
        return $this->namespace;
    }

    /**
     *
     */
    public function getIsNullable() : bool
    {
        return $this->is_nullable;
    }

    public function getIsPossiblyFalsey() : bool
    {
        return $this->is_nullable;
    }

    public function getIsAlwaysFalsey() : bool
    {
        return false;  // overridden in FalseType and NullType
    }

    public function getIsPossiblyTruthy() : bool
    {
        return true;  // overridden in various types. This base class (Type) is implicitly the type of an object, which is always truthy.
    }

    public function getIsAlwaysTruthy() : bool
    {
        return true;  // overridden in various types. This base class (Type) is implicitly the type of an object, which is always truthy.
    }

    public function getIsPossiblyFalse() : bool
    {
        return false;
    }

    public function getIsAlwaysFalse() : bool
    {
        return false;  // overridden in FalseType
    }

    public function getIsPossiblyTrue() : bool
    {
        return false;
    }

    public function getIsAlwaysTrue() : bool
    {
        return false;  // overridden in TrueType
    }

    public function getIsInBoolFamily() : bool
    {
        return false;  // overridden in FalseType, TrueType, BoolType
    }

    /**
     * @param bool $is_nullable
     * Set to true if the type should be nullable, else pass
     * false
     *
     * @return Type
     * A new type that is a copy of this type but with the
     * given nullability value.
     */
    public function withIsNullable(bool $is_nullable) : Type
    {
        if ($is_nullable === $this->is_nullable) {
            return $this;
        }
        return static::make(
            $this->getNamespace(),
            $this->getName(),
            $this->getTemplateParameterTypeList(),
            $is_nullable,
            Type::FROM_TYPE
        );
    }

    public function asNonFalseyType() : Type
    {
        // Overridden by BoolType subclass to return TrueType
        return $this->withIsNullable(false);
    }

    public function asNonTruthyType() : Type
    {
        // Overridden by ScalarType, BoolType, etc.
        return NullType::instance(false);
    }

    public function asNonFalseType() : Type
    {
        // Overridden by BoolType, etc.
        return $this;
    }

    public function asNonTrueType() : Type
    {
        // Overridden by BoolType, etc.
        return $this;
    }

    /**
     * @return bool
     * True if this is a native type (like int, string, etc.)
     *
     */
    public function isNativeType() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this is a native type or an array of native types
     * (like int, string, bool[], etc.),
     */
    private static function isInternalTypeString(string $original_type_name, int $source) : bool
    {
        $type_name = \str_replace('[]', '', \strtolower($original_type_name));
        if ($source === Type::FROM_PHPDOC) {
            $type_name = self::canonicalNameFromName($type_name);  // Have to convert boolean[] to bool
        }
        if (!\array_key_exists($type_name, self::_internal_type_set)) {
            return $original_type_name === '$this';  // This is the only case sensitive check.
        }
        // All values of $type_name exist as a valid phpdoc type, but some don't exist as real types.
        if ($source === Type::FROM_NODE && \array_key_exists($type_name, self::_soft_internal_type_set)) {
            return false;
        }
        return true;
    }

    /**
     * @return bool
     * True if this type is a type referencing the
     * class context in which it exists such as 'static'
     * or 'self'.
     */
    public function isSelfType() : bool
    {
        return $this->namespace === '\\' && self::isSelfTypeString($this->name);
    }

    /**
     * @return bool
     * True if this type is a type referencing the
     * class context 'static'.
     * Overridden in the subclass StaticType
     */
    public function isStaticType() : bool
    {
        return false;
    }

    /**
     * @param string $type_string
     * A string defining a type such as 'self' or 'int'.
     *
     * @return bool
     * True if the given type references the class context
     * in which it exists such as 'self' or 'parent'
     */
    public static function isSelfTypeString(
        string $type_string
    ) : bool {
        // Note: While 'self' and 'parent' are case insensitive, '$this' is case sensitive
        // Not sure if that should extend to phpdoc.
        return \preg_match('/^\\\\?([sS][eE][lL][fF]|[pP][aA][rR][eE][nN][tT]|\$this)$/', $type_string) > 0;
    }

    /**
     * @param string $type_string
     * A string defining a type such as 'static' or 'int'.
     *
     * @return bool
     * True if the given type references the class context
     * in which it exists is '$this' or 'static'
     */
    public static function isStaticTypeString(
        string $type_string
    ) : bool {
        // Note: While 'self' and 'parent' are case insensitive, '$this' is case sensitive
        // Not sure if that should extend to phpdoc.
        return \preg_match('/^\\\\?([sS][tT][aA][tT][iI][cC]|\\$this)$/', $type_string) > 0;
    }

    /**
     * @return bool
     * True if this type is scalar.
     */
    public function isScalar() : bool
    {
        return false;  // Overridden in subclass ScalarType
    }

    /**
     * @return bool
     * True if this type is a printable scalar.
     * @internal
     */
    public function isPrintableScalar() : bool
    {
        return false;  // Overridden in subclass ScalarType
    }

    /**
     * @return bool
     * True if this type is a callable or a Closure.
     */
    public function isCallable() : bool
    {
        return false;  // Overridden in subclass CallableType, ClosureType, FunctionLikeDeclarationType
    }

    /**
     * @return bool
     * True if this type is an object (or the phpdoc `object`)
     */
    public function isObject() : bool
    {
        return true;  // Overridden in various subclasses
    }

    /**
     * @return bool
     * True if this type is an object (and not the phpdoc `object` or a template)
     */
    public function isObjectWithKnownFQSEN() : bool
    {
        return true;  // Overridden in various subclasses
    }

    /**
     * @return bool
     * True if this type is possibly an object (or the phpdoc `object`)
     * This is the same as isObject(), except that it returns true for the exact class of IterableType.
     */
    public function isPossiblyObject() : bool
    {
        return true;  // Overridden in various subclasses
    }

    /**
     * @return bool
     * True if this type is iterable.
     */
    public function isIterable() : bool
    {
        return false;  // Overridden in subclass IterableType (with subclass ArrayType)
    }

    /**
     * @return bool
     * True if this type is array-like (is of type array, is
     * a generic array, or implements ArrayAccess).
     */
    public function isArrayLike() : bool
    {
        // includes both nullable and non-nullable ArrayAccess/array/iterable
        return (
            $this->isIterable()
            || $this->isGenericArray()
            || $this->isArrayAccess()
        );
    }

    /**
     * @return bool
     * True if this is a generic type such as 'int[]' or
     * 'string[]'.
     */
    public function isGenericArray() : bool
    {
        return false;  // Overridden in GenericArrayType and ArrayShapeType
    }

    /**
     * @return bool - Returns true if this is \ArrayAccess (nullable or not)
     */
    public function isArrayAccess() : bool
    {
        return (\strcasecmp($this->getName(), 'ArrayAccess') === 0
            && $this->getNamespace() === '\\');
    }

    /**
     * @return bool - Returns true if this is \Traversable (nullable or not)
     */
    public function isTraversable() : bool
    {
        return (\strcasecmp($this->getName(), 'Traversable') === 0
            && $this->getNamespace() === '\\');
    }

    /**
     * @param string $type_name
     * A non-namespaced type name like 'int[]'
     *
     * @return bool
     * True if this is a generic type such as 'int[]' or
     * 'string[]'.
     */
    private static function isGenericArrayString(string $type_name) : bool
    {
        if (\strrpos($type_name, '[]') !== false) {
            return $type_name !== '[]';
        }
        return false;
    }

    /**
     * @return Type
     * A variation of this type that is not generic.
     * i.e. 'int[]' becomes 'int'.
     */
    public function genericArrayElementType() : Type
    {
        throw new \Error("genericArrayElementType should not be called on Type base class");
    }

    /**
     * @return UnionType
     * A variation of this type that is not generic.
     * i.e. 'int[]' becomes 'int'.
     */
    public function genericArrayElementUnionType() : UnionType
    {
        throw new \Error("genericArrayElementUnionType should not be called on Type base class");
    }

    /**
     * @param int $key_type
     * Corresponds to the type of the array keys. Set this to a GenericArrayType::KEY_* constant.
     *
     * @return Type
     * Get a new type which is the generic array version of
     * this type. For instance, 'int' will produce 'int[]'.
     *
     * As a special case to reduce false positives, 'array' (with no known types) will produce 'array'
     *
     * Overridden in subclasses
     */
    public function asGenericArrayType(int $key_type) : Type
    {
        return GenericArrayType::fromElementType($this, false, $key_type);
    }

    /**
     * @return bool
     * True if this type has any template parameter types
     * @suppress PhanUnreferencedPublicMethod potentially used in the future
     *           TODO: Would need to override this in ArrayShapeType, GenericArrayType?
     */
    public function hasTemplateParameterTypes() : bool
    {
        return !empty($this->template_parameter_type_list);
    }

    /**
     * @return array<int,UnionType>
     * The set of types filling in template parameter types defined
     * on the class specified by this type.
     */
    public function getTemplateParameterTypeList()
    {
        return $this->template_parameter_type_list;
    }

    /**
     * @param CodeBase $code_base
     * The code base to look up classes against
     *
     * @return array<string,UnionType>
     * A map from template type identifier to a concrete type
     */
    public function getTemplateParameterTypeMap(CodeBase $code_base)
    {
        return $this->memoize(__METHOD__, function () use ($code_base) {
            $fqsen = $this->asFQSEN();

            if (!($fqsen instanceof FullyQualifiedClassName)) {
                return [];
            }

            \assert($fqsen instanceof FullyQualifiedClassName);

            if (!$code_base->hasClassWithFQSEN($fqsen)) {
                return [];
            }

            $class = $code_base->getClassByFQSEN($fqsen);

            $template_parameter_type_list =
                $this->getTemplateParameterTypeList();

            $map = [];
            foreach (\array_keys($class->getTemplateTypeMap()) as $i => $identifier) {
                if (isset($template_parameter_type_list[$i])) {
                    $map[$identifier] = $template_parameter_type_list[$i];
                }
            }

            return $map;
        });
    }

    /**
     * @param CodeBase $code_base
     * The code base to use in order to find super classes, etc.
     *
     * @param $recursion_depth
     * This thing has a tendency to run-away on me. This tracks
     * how bad I messed up by seeing how far the expanded types
     * go
     *
     * @return UnionType
     * Expands class types to all inherited classes returning
     * a superset of this type.
     */
    public function asExpandedTypes(
        CodeBase $code_base,
        int $recursion_depth = 0
    ) : UnionType {
        // We're going to assume that if the type hierarchy
        // is taller than some value we probably messed up
        // and should bail out.
        \assert(
            $recursion_depth < 20,
            "Recursion has gotten out of hand"
        );
        $union_type = $this->memoize(__METHOD__, function () use ($code_base, $recursion_depth) {
            $union_type = $this->asUnionType();

            $class_fqsen = $this->asFQSEN();

            if (!($class_fqsen instanceof FullyQualifiedClassName)) {
                return $union_type;
            }

            \assert($class_fqsen instanceof FullyQualifiedClassName);

            if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
                return $union_type;
            }

            $clazz = $code_base->getClassByFQSEN($class_fqsen);

            $union_type = $union_type->withUnionType(
                $clazz->getUnionType()
            );
            $additional_union_type = $clazz->getAdditionalTypes();
            if ($additional_union_type !== null) {
                $union_type = $union_type->withUnionType($additional_union_type);
            }

            // Recurse up the tree to include all types
            $representation = $this->__toString();
            $recursive_union_type_builder = new UnionTypeBuilder();
            foreach ($union_type->getTypeSet() as $clazz_type) {
                if ($clazz_type->__toString() !== $representation) {
                    $recursive_union_type_builder->addUnionType(
                        $clazz_type->asExpandedTypes(
                            $code_base,
                            $recursion_depth + 1
                        )
                    );
                } else {
                    $recursive_union_type_builder->addType($clazz_type);
                }
            }
            if (!empty($this->template_parameter_type_list)) {
                $recursive_union_type_builder->addUnionType(
                    $clazz->resolveParentTemplateType($this->getTemplateParameterTypeMap($code_base))
                );
            }

            // Add in aliases
            // (If enable_class_alias_support is false, this will do nothing)
            $fqsen_aliases = $code_base->getClassAliasesByFQSEN($class_fqsen);
            foreach ($fqsen_aliases as $alias_fqsen_record) {
                $alias_fqsen = $alias_fqsen_record->alias_fqsen;
                $recursive_union_type_builder->addType(
                    $alias_fqsen->asType()
                );
            }

            return $recursive_union_type_builder->getUnionType();
        });
        return $union_type;
    }

    /**
     * @param CodeBase $code_base
     *
     * @param Type $parent
     *
     * @return bool
     * True if this type represents a class which is a sub-type of
     * the class represented by the passed type.
     */
    public function isSubclassOf(CodeBase $code_base, Type $parent) : bool
    {
        $fqsen = $this->asFQSEN();
        \assert($fqsen instanceof FullyQualifiedClassName);

        $this_clazz = $code_base->getClassByFQSEN(
            $fqsen
        );

        $parent_fqsen = $parent->asFQSEN();
        \assert($parent_fqsen instanceof FullyQualifiedClassName);

        $parent_clazz = $code_base->getClassByFQSEN(
            $parent_fqsen
        );

        return $this_clazz->isSubclassOf($code_base, $parent_clazz);
    }

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    public function canCastToType(Type $type) : bool
    {
        // Check to see if we have an exact object match
        if ($this === $type) {
            return true;
        }

        if ($type instanceof MixedType) {
            return true;
        }

        // A nullable type cannot cast to a non-nullable type
        if ($this->getIsNullable() && !$type->getIsNullable()) {
            // If this is nullable, but that isn't, and we've
            // configured nulls to cast as anything (or as arrays), ignore
            // the nullable part.
            if (Config::get_null_casts_as_any_type()) {
                return $this->withIsNullable(false)->canCastToType($type);
            } elseif (Config::get_null_casts_as_array() && $type->isArrayLike()) {
                return $this->withIsNullable(false)->canCastToType($type);
            }

            return false;
        }

        // Get a non-null version of the type we're comparing
        // against.
        if ($type->getIsNullable()) {
            $type = $type->withIsNullable(false);

            // Check one more time to see if the types are equal
            if ($this === $type) {
                return true;
            }
        }

        // Test to see if we can cast to the non-nullable version
        // of the target type.
        return $this->canCastToNonNullableType($type);
    }

    /**
     * @param Type $type
     * A Type which is not nullable. This constraint is not
     * enforced, so be careful.
     *
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    protected function canCastToNonNullableType(Type $type) : bool
    {
        // can't cast native types (includes iterable or array) to object. ObjectType overrides this function.
        if ($type instanceof ObjectType
            && !$this->isNativeType()
        ) {
            return true;
        }

        if ($type instanceof MixedType) {
            return true;
        }
        // A matrix of allowable type conversions
        static $matrix = [
            '\Generator' => [
                'iterable' => true,
            ],
            '\Traversable' => [
                'iterable' => true,
            ],
            '\Closure' => [
                'callable' => true,
            ],
        ];

        return $matrix[$this->__toString()][$type->__toString()] ?? false;
    }

    /**
     * @param UnionType $union_type
     * A union type to compare against. Resolve it before checking.
     *
     * @param Context $context
     * The context in which this type exists.
     *
     * @param CodeBase $code_base
     * The code base in which both this and the given union
     * types exist.
     *
     * @return bool
     * True if each type within this union type can cast
     * to the given union type.
     *
     * @see StaticType->isExclusivelyNarrowedFormOrEquivalentTo for how it resolves static.
     * TODO: Refactor.
     *
     * @see UnionType->isExclusivelyNarrowedFormOrEquivalentTo for a check on union types as a whole.
     */
    public function isExclusivelyNarrowedFormOrEquivalentTo(
        UnionType $union_type,
        Context $context,
        CodeBase $code_base
    ) : bool {

        // Special rule: anything can cast to nothing
        // and nothing can cast to anything
        if ($union_type->isEmpty()) {
            return true;
        }

        // Check to see if the other union type contains this
        if ($union_type->hasType($this)) {
            return true;
        }
        $this_resolved = $this->withStaticResolvedInContext($context);
        // TODO: Allow casting MyClass<TemplateType> to MyClass (Without the template?


        // TODO: Need to resolve expanded union types (parents, interfaces) of classes *before* this is called.

        // Test to see if this (or any ancestor types) can cast to the given union type.
        $expanded_types = $this_resolved->asExpandedTypes($code_base);
        if ($expanded_types->canCastToUnionType(
            $union_type
        )) {
            return true;
        }
        return false;
    }

    /**
     * @return Type
     * Either this or 'static' resolved in the given context.
     */
    public function withStaticResolvedInContext(
        Context $context
    ) : Type {
        // TODO: Create SelfType, to go along with StaticType
        if (\strcasecmp($this->name, 'self') !== 0) {
            return $this;
        }

        // If the context isn't in a class scope, there's nothing
        // we can do
        if (!$context->isInClassScope()) {
            return $this;
        }
        $type = $context->getClassFQSEN()->asType();
        if ($this->getIsNullable()) {
            return $type->withIsNullable(true);
        }
        return $type;
    }

    /**
     * @return string
     * A string representation of this type in FQSEN form.
     */
    public function asFQSENString() : string
    {
        $namespace = $this->namespace;
        if (!$namespace) {
            return $this->name;
        }

        if ('\\' === $namespace) {
            return '\\' . $this->name;
        }

        return "{$namespace}\\{$this->name}";
    }

    /**
     * @return string
     * A human readable representation of this type
     * (This is frequently called, so prefer efficient operations)
     */
    public function __toString()
    {
        return $this->memoize(__METHOD__, function () {
            $string = $this->asFQSENString();

            if (\count($this->template_parameter_type_list) > 0) {
                $string .= $this->templateParameterTypeListAsString();
            }

            if ($this->getIsNullable()) {
                $string = '?' . $string;
            }

            return $string;
        });
    }

    /**
     * Gets the part of the Type string for the template parameters.
     * Precondition: $this->template_parameter_string is not null.
     */
    private function templateParameterTypeListAsString() : string
    {
        return '<' .
            \implode(',', \array_map(function (UnionType $type) {
                return (string)$type;
            }, $this->template_parameter_type_list)) . '>';
    }

    /**
     * @param string $name
     * Any type name
     *
     * @return string
     * A canonical name for the given type name
     */
    public static function canonicalNameFromName(
        string $name
    ) : string {
        static $map = [
            'boolean'  => 'bool',
            'callback' => 'callable',
            'closure'  => 'Closure',
            'double'   => 'float',
            'integer'  => 'int',
        ];

        return $map[\strtolower($name)] ?? $name;
    }

    /**
     * @param string $type_string
     * Any type string such as 'int' or 'Set<int>'
     *
     * @return Tuple5<string,string,array<int,string>,bool,?array<string|int,string>>
     * A 5-tuple with the following types:
     * 0: the namespace
     * 1: the type name.
     * 2: The template parameters, if any
     * 3: Whether or not the type is nullable
     * 4: The shape components, if any. Null unless this is an array shape type string such as 'array{field:int}'
     *
     * NOTE: callers must check for the generic array symbol in the type name or for type names beginning with 'array{' (case insensitive)
     *
     * NOTE: callers must not mutate the result.
     */
    private static function typeStringComponents(
        string $type_string
    ) {
        // This doesn't depend on any configs; the result can be safely cached.
        static $cache = [];
        return $cache[$type_string] ?? ($cache[$type_string] = self::typeStringComponentsInner($type_string));
    }

    /**
     * @return Tuple5<string,string,array<int,string>,bool,?array<string|int,string>>
     * A 5-tuple with the following types:
     * 0: the namespace
     * 1: the type name.
     * 2: The template parameters, if any
     * 3: Whether or not the type is nullable
     * 4: The shape components, if any. Null unless this is an array shape type string such as 'array{field:int}'
     */
    private static function typeStringComponentsInner(
        string $type_string
    ) {
        // Check to see if we have template parameter types
        $template_parameter_type_name_list = [];
        $shape_components = null;

        $match = [];
        $is_nullable = false;
        if (\preg_match('/^' . self::type_regex_or_this. '$/', $type_string, $match)) {
            if ($match[3] !== '') {
                return self::closureTypeStringComponents($type_string, $match[3]);
            }
            if (!isset($match[2])) {
                // Parse '(X)' as 'X'
                return self::typeStringComponents(\substr($match[1], 1, -1));
            } elseif (!isset($match[4])) {
                // Parse '?(X[]) as '?X[]'
                return self::typeStringComponents('?' . \substr($match[2], 2, -1));
            }
            $type_string = $match[4];

            // Rip out the nullability indicator if it
            // exists and note its nullability
            $is_nullable = ($match[5] ?? '') === '?';
            if ($is_nullable) {
                $type_string = \substr($type_string, 1);
            }

            if (($match[9] ?? '') !== '') {
                $shape_components = self::extractShapeComponents($match[10] ?? '');  // will be empty array for 'array{}'
            } else {
                // Recursively parse this
                $template_parameter_type_name_list = ($match[7] ?? '') !== ''
                    ? self::extractNameList($match[7])
                    : [];
            }
        }

        // Determine if the type name is fully qualified
        // (as specified by a leading backslash).
        $is_fully_qualified = (0 === \strpos($type_string, '\\'));

        $fq_class_name_elements =
            \array_filter(\explode('\\', $type_string));

        $class_name =
            (string)\array_pop($fq_class_name_elements);

        $namespace = ($is_fully_qualified ? '\\' : '')
            . \implode('\\', \array_filter(
                $fq_class_name_elements
            ));

        return new Tuple5(
            $namespace,
            $class_name,
            $template_parameter_type_name_list,
            $is_nullable,
            $shape_components
        );
    }

    /**
     * @return Tuple5<string,string,array<int,string>,bool,?array<string|int,string>>
     * A 5-tuple with the following types:
     * 0: the namespace
     * 1: the type name.
     * 2: The template parameters, if any
     * 3: Whether or not the type is nullable
     * 4: The shape components, if any. Null unless this is an array shape type string such as 'array{field:int}'
     */
    private static function closureTypeStringComponents(string $type_string, string $inner) : Tuple5
    {
        $parts = self::closureParams(\trim(\substr($inner, 1, -1)));
        // TODO: parse params, same as @method

        // Parse the optional return type for this closure
        $i = \strpos($type_string, $inner) + \strlen($inner);
        $colon_index = \strpos($type_string, ':', $i);

        if ($colon_index !== false) {
            $return_type_string = \ltrim(\substr($type_string, $colon_index + 1));
        } else {
            $return_type_string = 'void';
        }
        $parts[] = $return_type_string;

        return new Tuple5(
            '\\',
            \strncmp($type_string, 'callable', 8) === 0 ? 'callable' : 'Closure',
            [],
            false,
            $parts
        );
    }

    private static function closureParams(string $arg_list)
    {
        // Special check if param list has 0 params.
        if ($arg_list === '') {
            return [];
        }
        // TODO: Would need to use a different approach if templates were ever supported
        //       e.g. The magic method parsing doesn't support commas?
        return \array_map('trim', \explode(',', $arg_list));
    }

    /**
     * @return array<string|int,string> maps field name to field type.
     */
    private static function extractShapeComponents(string $shape_component_string) : array
    {
        $result = [];
        foreach (self::extractNameList($shape_component_string) as $shape_component) {
            // Because these can be nested, there may be more than one ':'. Only consider the first.
            $parts = \explode(':', $shape_component, 2);
            if (count($parts) !== 2) {
                continue;
            }
            $field_name = \trim($parts[0]);
            if ($field_name === '') {
                continue;
            }
            $field_value = \trim($parts[1]);
            $result[$field_name] = $field_value;
        }
        return $result;
    }

    /**
     * Extracts the inner parts of a template name list (i.e. within <>) or a shape component list (i.e. within {})
     * @return array<int,string>
     * @suppress PhanPluginUnusedVariable
     */
    private static function extractNameList(string $list_string) : array
    {
        $results = [];
        $prev_parts = [];
        $delta = 0;
        foreach (\explode(',', $list_string) as $result) {
            $result = \trim($result);
            $open_bracket_count = \substr_count($result, '<') + \substr_count($result, '{');
            $close_bracket_count = \substr_count($result, '>') + \substr_count($result, '}');
            if (\count($prev_parts) > 0) {
                $prev_parts[] = $result;
                $delta += $open_bracket_count - $close_bracket_count;
                if ($delta <= 0) {
                    if ($delta === 0) {
                        $results[] = \implode(',', $prev_parts);
                    }  // ignore unparseable data such as "<T,T2>>" or "T, T2{}}"
                    $prev_parts = [];
                    $delta = 0;
                }
                continue;
            }
            if ($open_bracket_count === 0) {
                $results[] = $result;
                continue;
            }
            $delta = $open_bracket_count - $close_bracket_count;
            if ($delta === 0) {
                $results[] = $result;
            } elseif ($delta > 0) {
                $prev_parts[] = $result;
            }  // otherwise ignore unparseable data such as ">" (should be impossible)

            // e.g. we're breaking up T1<T2<X,Y>> into "T1<T2<X" and "Y>>"
        }
        return $results;
    }

    /**
     * Helper function for internal use by UnionType.
     * Overridden by subclasses.
     */
    public function getNormalizationFlags() : int
    {
        return $this->is_nullable ? self::_bit_nullable : 0;
    }

    public function hasArrayShapeTypeInstances() : bool
    {
        return false;
    }

    /**
     * @internal - Used to check for quick mode
     */
    public function shouldBeReplacedBySpecificTypes()
    {
        // Could check for final classes such as stdClass here, but not much of a reason to.
        return true;
    }

    /**
     * @return Type[]
     */
    public function withFlattenedArrayShapeTypeInstances() : array
    {
        return [$this];
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

/**
 * A Fully-Qualified Class Constant Name
 */
class FullyQualifiedClassConstantName extends FullyQualifiedClassElement implements FullyQualifiedConstantName
{
    /**
     * @return string
     * The canonical representation of the name of the object,
     * for use in array key lookups for singletons, namespace maps, etc.
     * This should not be used directly or indirectly in issue output
     * If an FQSEN is case sensitive, this should return $name
     */
    public static function canonicalLookupKey(string $name) : string
    {
        return $name;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

/**
 * A Fully-Qualified Property Name
 */
class FullyQualifiedPropertyName extends FullyQualifiedClassElement
{
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

use Phan\Language\Context;
use Phan\Language\FQSEN;
use Serializable;

/**
 * A Fully-Qualified Name
 *
 * Serialization and cloning are forbidden.
 */
abstract class AbstractFQSEN implements FQSEN, Serializable
{

    /**
     * @var string
     * The name of this structural element
     */
    private $name = '';

    /**
     * @param string $name
     * The name of this structural element
     */
    protected function __construct(string $name)
    {
        $this->name = $name;
    }

    /**
     * @param string $fully_qualified_string
     * An FQSEN string like '\Namespace\Class::method' or
     * 'Class' or 'Class::method'.
     *
     * @return FQSEN
     */
    abstract public static function fromFullyQualifiedString(
        string $fully_qualified_string
    );

    /**
     * @param string $fqsen_string
     * An FQSEN string like '\Namespace\Class::method' or
     * 'Class' or 'Class::method'.
     *
     * @param Context $context
     * The context in which the FQSEN string was found
     *
     * @return FQSEN
     */
    abstract public static function fromStringInContext(
        string $fqsen_string,
        Context $context
    );

    /**
     * @return string
     * The class associated with this FQSEN
     */
    public function getName() : string
    {
        return $this->name;
    }

    /**
     * @return string
     * The canonical representation of the name of the object. Functions
     * and Methods, for instance, lowercase their names.
     */
    public static function canonicalName(string $name) : string
    {
        return $name;
    }

    /**
     * @return string
     * The canonical representation of the name of the object,
     * for use in array key lookups for singletons, namespace maps, etc.
     * This should not be used directly or indirectly in issue output
     * If an FQSEN is case sensitive, this should return $name
     */
    public static function canonicalLookupKey(string $name) : string
    {
        return \strtolower($name);
    }

    /**
     * @return string
     * A string representation of this fully-qualified
     * structural element name.
     */
    abstract public function __toString() : string;

    public function __clone()
    {
        // We compare and look up FQSENs by their identity
        throw new \Error("cloning an FQSEN (" . (string)$this . ") is forbidden\n");
    }

    public function serialize()
    {
        // We compare and look up FQSENs by their identity
        throw new \Error("serializing an FQSEN (" . (string)$this . ") is forbidden\n");
    }

    public function unserialize($unused_serialized)
    {
        // We compare and look up FQSENs by their identity
        throw new \Error("unserializing an FQSEN ($unused_serialized) is forbidden\n");
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

/**
 * A Fully-Qualified Method Name
 */
interface FullyQualifiedFunctionLikeName
{
    /**
     * @return bool
     * True if this FQSEN represents a closure
     */
    public function isClosure() : bool;
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

use Phan\Exception\EmptyFQSENException;
use Phan\Language\Context;
use Phan\Language\Type;

/**
 * A Fully-Qualified Global Structural Element
 */
abstract class FullyQualifiedGlobalStructuralElement extends AbstractFQSEN
{
    use \Phan\Language\FQSEN\Alternatives;
    use \Phan\Memoize;

    /**
     * @var string
     * The namespace in this elements scope
     */
    private $namespace = '\\';

    /**
     * @param string $namespace
     * The namespace in this element's scope
     *
     * @param string $name
     * The name of this structural element
     *
     * @param int $alternate_id
     * An alternate ID for the element for use when
     * there are multiple definitions of the element
     */
    protected function __construct(
        string $namespace,
        string $name,
        int $alternate_id = 0
    ) {
        if ($name === '') {
            throw new EmptyFQSENException("The name of an FQSEN cannot be empty", rtrim($namespace, '\\') . '\\');
        }

        \assert($namespace !== '', "The namespace cannot be empty");

        \assert(
            $namespace[0] === '\\',
            "The first character of a namespace must be \\"
        );

        parent::__construct($name);
        $this->namespace = $namespace;
        $this->alternate_id = $alternate_id;
    }

    /**
     * @param string $name
     * The name of this structural element, may contain a namespace.
     *
     * @return static
     */
    public static function makeFromExtractedNamespaceAndName(string $name)
    {
        $name = \ltrim($name, '\\');
        $i = \stripos($name, '\\');
        if ($i === false) {
            // Common case: no namespace
            return self::make('\\', $name);
        }
        return self::make('\\' . \substr($name, 0, $i), \substr($name, $i+1));
    }

    /**
     * @param string $namespace
     * The namespace in this element's scope
     *
     * @param string $name
     * The name of this structural element
     *
     * @param int $alternate_id
     * An alternate ID for the element for use when
     * there are multiple definitions of the element
     *
     * @return static
     */
    public static function make(
        string $namespace,
        string $name,
        int $alternate_id = 0
    ) {

        // Transfer any relative namespace stuff from the
        // name to the namespace.
        $name_parts = \explode('\\', $name);
        $name = \array_pop($name_parts);
        foreach ($name_parts as $part) {
            $namespace .= '\\' . $part;
        }
        $namespace = self::cleanNamespace($namespace);

        // use the canonicalName for $name instead of strtolower - Some subclasses(constants) are case sensitive.
        $key = static::class . '|' .
            static::toString(\strtolower($namespace), static::canonicalLookupKey($name), $alternate_id);

        $fqsen = self::memoizeStatic($key, function () use ($namespace, $name, $alternate_id) {
            return new static(
                $namespace,
                $name,
                $alternate_id
            );
        });

        return $fqsen;
    }

    /**
     * @param $fully_qualified_string
     * An fully qualified string like '\Namespace\Class'
     *
     * @return static
     */
    public static function fromFullyQualifiedString(
        string $fully_qualified_string
    ) {

        $key = static::class . '|' . $fully_qualified_string;

        return self::memoizeStatic($key, function () use ($fully_qualified_string) {

            // Split off the alternate_id
            $parts = \explode(',', $fully_qualified_string);
            $fqsen_string = $parts[0];
            $alternate_id = (int)($parts[1] ?? 0);

            $parts = \explode('\\', $fqsen_string);
            $name = \array_pop($parts);

            \assert(!empty($name), "The name cannot be empty");

            $namespace = '\\' . \implode('\\', \array_filter($parts));

            \assert(!empty($namespace), "The namespace cannot be empty");

            \assert(
                $namespace[0] === '\\',
                "The first character of the namespace must be \\"
            );

            return static::make(
                $namespace,
                $name,
                $alternate_id
            );
        });
    }

    /**
     * @param $fqsen_string
     * An FQSEN string like '\Namespace\Class'
     *
     * @param Context $context
     * The context in which the FQSEN string was found
     *
     * @return static
     */
    public static function fromStringInContext(
        string $fqsen_string,
        Context $context
    ) {
        // Check to see if we're fully qualified
        if (($fqsen_string[0] ?? '') === '\\') {
            return static::fromFullyQualifiedString($fqsen_string);
        }
        $namespace_map_type = static::getNamespaceMapType();
        if ($namespace_map_type === \ast\AST_CONST && Type::fromReservedConstantName($fqsen_string)->isDefined()) {
            return static::fromFullyQualifiedString($fqsen_string);
        }

        // Split off the alternate ID
        $parts = \explode(',', $fqsen_string);
        $fqsen_string = $parts[0];
        $alternate_id = (int)($parts[1] ?? 0);

        $parts = \explode('\\', $fqsen_string);
        // Split the parts into the namespace(0 or more components) and the last name.
        $name = \array_pop($parts);

        \assert(!empty($name), "The name cannot be empty");

        // Check for a name map
        if ($context->hasNamespaceMapFor($namespace_map_type, $fqsen_string)) {
            return $context->getNamespaceMapFor(
                $namespace_map_type,
                $fqsen_string
            );
        }

        $namespace = \implode('\\', \array_filter($parts));

        // n.b.: Functions must override this method because
        //       they don't prefix the namespace for naked
        //       calls
        if (empty($namespace)) {
            $namespace = $context->getNamespace();
        }

        return static::make(
            $namespace,
            $name,
            $alternate_id
        );
    }

    /**
     * @return int
     * The namespace map type such as \ast\flags\USE_NORMAL or \ast\flags\USE_FUNCTION
     */
    abstract protected static function getNamespaceMapType() : int;

    /**
     * @return string
     * The namespace associated with this FQSEN
     */
    public function getNamespace() : string
    {
        return $this->namespace;
    }

    /**
     * @return string
     * The namespace+name associated with this FQSEN.
     * (e.g. 'ast\parse_code')
     */
    public function getNamespacedName() : string
    {
        if ($this->namespace === '\\') {
            return $this->getName();
        }
        return \ltrim($this->namespace, '\\') . '\\' . $this->getName();
    }

    /**
     * @return static
     */
    public function withNamespace(
        string $namespace
    ) {
        return static::make(
            self::cleanNamespace($namespace),
            $this->getName(),
            $this->getAlternateId()
        );
    }

    /**
     * @return static
     * A FQSEN with the given alternate_id set
     */
    public function withAlternateId(
        int $alternate_id
    ) {
        if ($this->getAlternateId() === $alternate_id) {
            return $this;
        }

        \assert(
            $alternate_id < 1000,
            "Your alternate IDs have run away"
        );

        return static::make(
            $this->getNamespace(),
            $this->getName(),
            $alternate_id
        );
    }

    /**
     * @param string $namespace (can be empty)
     *
     * @return string
     * A cleaned version of the given namespace such that
     * its always prefixed with a '\' and never ends in a
     * '\', and is the string "\" if there is no namespace.
     */
    protected static function cleanNamespace(string $namespace) : string
    {
        if (!$namespace
            || $namespace === '\\'
        ) {
            return '\\';
        }

        // Ensure that the first character of the namespace
        // is always a '\'
        if ($namespace[0] !== '\\') {
            $namespace = '\\' . $namespace;
        }

        // Ensure that we don't have a trailing '\' on the
        // namespace
        if ('\\' === \substr($namespace, -1)) {
            $namespace = \substr($namespace, 0, -1);
        }

        return $namespace;
    }

    /**
     * @return string
     * A string representation of this fully-qualified
     * structural element name.
     */
    public static function toString(
        string $namespace,
        string $name,
        int $alternate_id
    ) : string {
        $fqsen_string = $namespace;

        if ($fqsen_string && $fqsen_string !== '\\') {
            $fqsen_string .= '\\';
        }

        $fqsen_string .= static::canonicalName($name);

        // Append an alternate ID if we need to disambiguate
        // multiple definitions
        if ($alternate_id) {
            $fqsen_string .= ',' . $alternate_id;
        }

        return $fqsen_string;
    }

    /** @var string|null */
    private $asString = null;

    /**
     * @return string
     * A string representation of this fully-qualified
     * structural element name.
     */
    public function __toString() : string
    {
        $asString = $this->asString;
        if ($asString === null) {
            $asString = static::toString(
                $this->getNamespace(),
                $this->getName(),
                $this->getAlternateId()
            );
            $this->asString = $asString;
        }
        return $asString;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

/**
 * A Fully-Qualified Method Name
 */
class FullyQualifiedMethodName extends FullyQualifiedClassElement implements FullyQualifiedFunctionLikeName
{
    /**
     * Maps lowercase versions of function names to their canonical names, for magic methods.
     * This makes checks for magic method names more reliable.
     */
    const CANONICAL_NAMES = [
        '__call'        => '__call',
        '__callstatic'  => '__callStatic',
        '__clone'       => '__clone',
        '__construct'   => '__construct',
        '__debuginfo'   => '__debugInfo',
        '__destruct'    => '__destruct',
        '__get'         => '__get',
        '__invoke'      => '__invoke',
        '__isset'       => '__isset',
        '__set'         => '__set',
        '__set_state'   => '__set_state',
        '__sleep'       => '__sleep',
        '__tostring'    => '__toString',
        '__unset'       => '__unset',
        '__wakeup'      => '__wakeup',
    ];

    const MAGIC_METHOD_NAME_SET = [
        '__call'        => true,
        '__callStatic'  => true,
        '__clone'       => true,
        '__construct'   => true,
        '__debugInfo'   => true,
        '__destruct'    => true,
        '__get'         => true,
        '__invoke'      => true,
        '__isset'       => true,
        '__set'         => true,
        '__set_state'   => true,
        '__sleep'       => true,
        '__toString'    => true,
        '__unset'       => true,
        '__wakeup'      => true,
    ];

    /**
     * A list of magic methods which should have a return type of void
     */
    const MAGIC_VOID_METHOD_NAME_SET = [
        '__clone'       => true,
        '__construct'   => true,
        '__destruct'    => true,
        '__set'         => true,
        '__unset'       => true,
        '__wakeup'      => true,
    ];

    /**
     * @return string
     * The canonical representation of the name of the object. Functions
     * and Methods, for instance, lowercase their names.
     */
    public static function canonicalName(string $name) : string
    {
        return self::CANONICAL_NAMES[\strtolower($name)] ?? $name;
    }

    /**
     * @return bool
     * True if this FQSEN represents a closure
     */
    public function isClosure() : bool
    {
        return false;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

use Phan\Language\Type;
use Phan\Language\UnionType;
use Phan\Memoize;

/**
 * A Fully-Qualified Class Name
 */
class FullyQualifiedClassName extends FullyQualifiedGlobalStructuralElement
{
    use Memoize;

    /**
     * @return int
     * The namespace map type such as \ast\flags\USE_NORMAL or \ast\flags\USE_FUNCTION
     */
    protected static function getNamespaceMapType() : int
    {
        return \ast\flags\USE_NORMAL;
    }

    /**
     * @return string
     * The canonical representation of the name of the object. Functions
     * and Methods, for instance, lowercase their names.
     * TODO: Separate the function used to render names in phan errors
     *       from the ones used for generating array keys.
     */
    public static function canonicalName(string $name) : string
    {
        return $name;
    }

    /**
     * @return FullyQualifiedClassName
     * A fully qualified class name from the given type
     */
    public static function fromType(Type $type) : FullyQualifiedClassName
    {
        return self::fromFullyQualifiedString(
            $type->asFQSENString()
        );
    }

    /**
     * @return Type
     * The type of this class
     */
    public function asType() : Type
    {
        return Type::fromFullyQualifiedString(
            $this->__toString()
        );
    }

    /**
     * @return UnionType
     * The union type of just this class type
     */
    public function asUnionType() : UnionType
    {
        return $this->asType()->asUnionType();
    }

    /**
     * @return FullyQualifiedClassName
     * The FQSEN for \stdClass.
     */
    public static function getStdClassFQSEN() : FullyQualifiedClassName
    {
        return self::memoizeStatic(__METHOD__, function () {
            return self::fromFullyQualifiedString("\stdClass");
        });
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

use Phan\Language\FQSEN;

/**
 * This trait allows an FQSEN to have an alternate ID for when
 * there are multiple colliding definitions of the same name.
 * An alternate ID will be appended to a name such as in
 * `\Name\Space\class,1` or `\Name\Space\class::function,1`
 * or when composed as `\Name\Space\class,1::function,1`.
 */
trait Alternatives
{

    /**
     * Implementers must have a getName() method
     */
    abstract public function getName() : string;

    /**
     * Implementers should use the \Phan\Memoize
     * trait
     */
    abstract protected function memoizeFlushAll();

    /**
     * @var int
     * An alternate ID for the element for use when
     * there are multiple definitions of the element
     */
    protected $alternate_id = 0;

    /**
     * @return int
     * An alternate identifier associated with this
     * FQSEN or zero if none if this is not an
     * alternative.
     */
    public function getAlternateId() : int
    {
        return $this->alternate_id;
    }

    /**
     * @return string
     * Get the name of this element with its alternate id
     * attached
     */
    public function getNameWithAlternateId() : string
    {
        if ($this->alternate_id) {
            return "{$this->getName()},{$this->alternate_id}";
        }

        return $this->getName();
    }

    /**
     * @return bool
     * True if this is an alternate
     */
    public function isAlternate() : bool
    {
        return (0 !== $this->alternate_id);
    }

    /**
     * @return static
     * A FQSEN with the given alternate_id set
     * @suppress PhanTypeMismatchDeclaredReturn (static vs FQSEN)
     */
    abstract public function withAlternateId(
        int $alternate_id
    ) : FQSEN;

    /**
     * @return static
     * Get the canonical (non-alternate) FQSEN associated
     * with this FQSEN
     *
     * @suppress PhanTypeMismatchDeclaredReturn (static vs FQSEN)
     * @suppress PhanTypeMismatchReturn (Alternatives is a trait, cannot directly implement the FQSEN interface. Related to #800)
     */
    public function getCanonicalFQSEN() : FQSEN
    {
        if ($this->alternate_id == 0) {
            return $this;
        }

        return $this->withAlternateId(0);
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

use Phan\Language\FQSEN;

/**
 * A Fully-Qualified Constant Name
 */
interface FullyQualifiedConstantName extends FQSEN
{
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

use Phan\Language\Context;
use Phan\Language\FQSEN;

/**
 * A Fully-Qualified Class Name
 */
abstract class FullyQualifiedClassElement extends AbstractFQSEN
{
    use \Phan\Language\FQSEN\Alternatives;
    use \Phan\Memoize;

    /**
     * @var FullyQualifiedClassName
     * A fully qualified class name for the class in
     * which this element exists
     */
    private $fully_qualified_class_name;

    /**
     * @param FullyQualifiedClassName $fully_qualified_class_name
     * The fully qualified class name of the class in which
     * this element exists
     *
     * @param string $name
     * A name if one is in scope or the empty string otherwise.
     *
     * @param int $alternate_id
     * An alternate ID for the element for use when
     * there are multiple definitions of the element
     */
    protected function __construct(
        FullyQualifiedClassName $fully_qualified_class_name,
        string $name,
        int $alternate_id = 0
    ) {
        parent::__construct($name);

        $this->fully_qualified_class_name =
            $fully_qualified_class_name;
        $this->alternate_id = $alternate_id;
    }

    /**
     * @param FullyQualifiedClassName $fully_qualified_class_name
     * The fully qualified class name of the class in which
     * this element exists
     *
     * @param string $name
     * A name if one is in scope or the empty string otherwise.
     *
     * @param int $alternate_id
     * An alternate ID for the element for use when
     * there are multiple definitions of the element
     */
    public static function make(
        FullyQualifiedClassName $fully_qualified_class_name,
        string $name,
        int $alternate_id = 0
    ) {
        $name = static::canonicalName($name);

        $key = $fully_qualified_class_name . '::' . $name . ',' . $alternate_id .
               '|' . static::class;

        static $cache = [];
        return $cache[$key] ?? ($cache[$key] = new static(
            $fully_qualified_class_name,
            $name,
            $alternate_id
        ));
    }

    /**
     * @return static
     * Get the canonical (non-alternate) FQSEN associated
     * with this FQSEN
     * @suppress PhanPartialTypeMismatchArgument
     */
    public function getCanonicalFQSEN() : FQSEN
    {
        $old_fully_qualified_class_name = $this->getFullyQualifiedClassName();
        $fully_qualified_class_name = $old_fully_qualified_class_name->getCanonicalFQSEN();
        if ($this->alternate_id == 0 && $fully_qualified_class_name === $old_fully_qualified_class_name) {
            return $this;
        }
        return static::make(
            $fully_qualified_class_name,
            $this->getName(),
            0
        );
    }

    /**
     * @param $fully_qualified_string
     * An FQSEN string like '\Namespace\Class::methodName'
     */
    public static function fromFullyQualifiedString(
        string $fully_qualified_string
    ) {
        \assert(
            false !== \strpos(
                $fully_qualified_string,
                '::'
            ),
            "Fully qualified class element lacks '::' delimiter"
        );

        list(
            $fully_qualified_class_name_string,
            $name_string
        ) = explode('::', $fully_qualified_string);

        $fully_qualified_class_name =
            FullyQualifiedClassName::fromFullyQualifiedString(
                $fully_qualified_class_name_string
            );

        // Make sure that we're actually getting a class
        // name reference back
        \assert(
            $fully_qualified_class_name instanceof FullyQualifiedClassName,
            "FQSEN must be an instanceof FullyQualifiedClassName"
        );

        // Split off the alternate ID
        $parts = explode(',', $name_string);
        $name = $parts[0];
        $alternate_id = (int)($parts[1] ?? 0);

        \assert(\is_int($alternate_id), "Alternate must be an integer");

        return static::make(
            $fully_qualified_class_name,
            $name,
            $alternate_id
        );
    }

    /**
     * @param string $fqsen_string
     * An FQSEN string like '\Namespace\Class::methodName'
     *
     * @param Context $context
     * The context in which the FQSEN string was found
     *
     * @return static
     */
    public static function fromStringInContext(
        string $fqsen_string,
        Context $context
    ) {
        // Test to see if we have a class defined
        if (false === \strpos($fqsen_string, '::')) {
            \assert(
                $context->isInClassScope(),
                "Cannot reference class element without class name when not in class scope."
            );

            $fully_qualified_class_name = $context->getClassFQSEN();
        } else {
            \assert(
                false !== \strpos(
                    $fqsen_string,
                    '::'
                ),
                "Fully qualified class element lacks '::' delimiter"
            );

            list(
                $class_name_string,
                $fqsen_string
            ) = \explode('::', $fqsen_string);

            $fully_qualified_class_name =
                FullyQualifiedClassName::fromStringInContext(
                    $class_name_string,
                    $context
                );
        }

        // Split off the alternate ID
        $parts = \explode(',', $fqsen_string);
        $name = $parts[0];
        $alternate_id = (int)($parts[1] ?? 0);

        \assert(
            \is_int($alternate_id),
            "Alternate must be an integer"
        );

        return static::make(
            $fully_qualified_class_name,
            $name,
            $alternate_id
        );
    }

    /**
     * @return FullyQualifiedClassName
     * The fully qualified class name associated with this
     * class element.
     */
    public function getFullyQualifiedClassName() : FullyQualifiedClassName
    {
        return $this->fully_qualified_class_name;
    }

    /**
     * @return static
     * A FQSEN with the given alternate_id set
     */
    public function withAlternateId(
        int $alternate_id
    ) {

        \assert(
            $alternate_id < 1000,
            "Your alternate IDs have run away"
        );

        return static::make(
            $this->getFullyQualifiedClassName(),
            $this->getName(),
            $alternate_id
        );
    }

    /**
     * @return string
     * A string representation of the given values
     */
    public static function toString(
        FullyQualifiedClassName $fqsen,
        string $name,
        int $alternate_id
    ) : string {
        $fqsen_string = ((string)$fqsen) . '::' . $name;

        if ($alternate_id) {
            $fqsen_string .= ",$alternate_id";
        }

        return $fqsen_string;
    }

    /**
     * @return string
     * A string representation of this fully-qualified
     * structural element name.
     */
    public function __toString() : string
    {
        $fqsen_string = $this->memoize(__METHOD__, function () {
            return self::toString(
                $this->getFullyQualifiedClassName(),
                $this->getName(),
                $this->alternate_id
            );
        });

        // print $fqsen_string . '|' . spl_object_hash($this) . "\n";

        return $fqsen_string;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

/**
 * A Fully-Qualified Constant Name
 */
class FullyQualifiedGlobalConstantName extends FullyQualifiedGlobalStructuralElement implements FullyQualifiedConstantName
{
    /**
     * @return int
     * The namespace map type such as \ast\flags\USE_NORMAL or \ast\flags\USE_FUNCTION
     */
    protected static function getNamespaceMapType() : int
    {
        return \ast\flags\USE_CONST;
    }

    /**
     * @return string
     * The canonical representation of the name of the object,
     * for use in array key lookups for singletons, namespace maps, etc.
     * This should not be used directly or indirectly in issue output
     * If an FQSEN is case sensitive, this should return $name
     */
    public static function canonicalLookupKey(string $name) : string
    {
        return $name;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\FQSEN;

use Phan\Exception\EmptyFQSENException;
use Phan\Language\Context;
use ast\Node;

/**
 * A Fully-Qualified Function Name
 */
class FullyQualifiedFunctionName extends FullyQualifiedGlobalStructuralElement implements FullyQualifiedFunctionLikeName
{

    /**
     * @return int
     * The namespace map type such as \ast\flags\USE_NORMAL or \ast\flags\USE_FUNCTION
     */
    protected static function getNamespaceMapType() : int
    {
        return \ast\flags\USE_FUNCTION;
    }

    /**
     * @return string
     * The canonical representation of the name of the object. Functions
     * and Methods, for instance, lowercase their names.
     * TODO: Separate the function used to render names in phan errors
     *       from the ones used for generating array keys.
     */
    public static function canonicalName(string $name) : string
    {
        return $name;
    }

    /**
     * @param string $fqsen_string
     * An FQSEN string like '\Namespace\Class'
     *
     * @param Context $context
     * The context in which the FQSEN string was found
     */
    public static function fromStringInContext(
        string $fqsen_string,
        Context $context
    ) : FullyQualifiedFunctionName {

        // Check to see if we're fully qualified
        if (0 === \strpos($fqsen_string, '\\')) {
            return static::fromFullyQualifiedString($fqsen_string);
        }

        // Split off the alternate ID
        $parts = \explode(',', $fqsen_string);
        $fqsen_string = $parts[0];
        $alternate_id = (int)($parts[1] ?? 0);

        $parts = \explode('\\', $fqsen_string);
        $name = \array_pop($parts);

        if ($name === '') {
            throw new EmptyFQSENException("The name cannot be empty", $fqsen_string);
        }

        // Check for a name map
        if ($context->hasNamespaceMapFor(static::getNamespaceMapType(), $fqsen_string)) {
            return $context->getNamespaceMapFor(
                static::getNamespaceMapType(),
                $fqsen_string
            );
        }

        // For functions we don't use the context's namespace if
        // there is no NS on the call.
        $namespace = \implode('\\', array_filter($parts));

        return static::make(
            $namespace,
            $name,
            $alternate_id
        );
    }

    public static function fromClosureInContext(
        Context $context,
        Node $node
    ) : FullyQualifiedFunctionName {
        $hash_material =
            $context->getFile() . '|' .
            $context->getLineNumberStart() . '|' .
            $node->children['__declId'];

        $name = 'closure_' . substr(md5($hash_material), 0, 12);

        return static::fromStringInContext(
            $name,
            $context
        );
    }

    /**
     * @return bool
     * True if this FQSEN represents a closure
     */
    public function isClosure() : bool
    {
        return (\preg_match('/^closure_/', $this->getName()) === 1);
    }
}
<?php declare(strict_types=1);
namespace Phan\Language;

use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\CodeBaseException;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\AnnotatedUnionType;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\Type\ArrayShapeType;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\BoolType;
use Phan\Language\Type\FalseType;
use Phan\Language\Type\FloatType;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\IntType;
use Phan\Language\Type\MixedType;
use Phan\Language\Type\MultiType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\StaticType;
use Phan\Language\Type\TemplateType;
use Phan\Language\Type\TrueType;
use ast\Node;

if (!\function_exists('spl_object_id')) {
    require_once __DIR__ . '/../../spl_object_id.php';
}

class UnionType implements \Serializable
{
    /**
     * @var string
     * A list of one or more types delimited by the '|'
     * character (e.g. 'int|DateTime|string[]')
     */
    const union_type_regex =
        Type::type_regex
        . '(\|' . Type::type_regex . ')*';

    /**
     * @var string
     * A list of one or more types delimited by the '|'
     * character (e.g. 'int|DateTime|string[]' or 'null|$this')
     * This may be used for return types.
     *
     * TODO: Equivalent variants with no capturing? (May not improve performance much)
     */
    const union_type_regex_or_this =
        Type::type_regex_or_this
        . '(\|' . Type::type_regex_or_this . ')*';

    /**
     * @var array<int,Type> * This is an immutable list of unique types.
     */
    private $type_set;

    /**
     * @param array<int,Type> $type_list
     * @param bool $is_unique - Whether or not this is already unique. Only set to true within UnionSet code.
     *
     * An optional list of types represented by this union
     */
    public function __construct(array $type_list = [], bool $is_unique = false)
    {
        $this->type_set = ($is_unique || \count($type_list) <= 1) ? $type_list : self::getUniqueTypes($type_list);
    }

    /**
     * @param Type[] $type_list
     * @return UnionType
     */
    public static function of(array $type_list)
    {
        $n = \count($type_list);
        if ($n === 0) {
            return self::$empty_instance;
        } elseif ($n === 1) {
            return \reset($type_list)->asUnionType();
        } else {
            return new self($type_list);
        }
    }

    protected static function ofUniqueTypes(array $type_list)
    {
        $n = \count($type_list);
        if ($n === 0) {
            return self::$empty_instance;
        } elseif ($n === 1) {
            return \reset($type_list)->asUnionType();
        } else {
            return new self($type_list, true);
        }
    }

    /** @var EmptyUnionType */
    private static $empty_instance;

    /**
     * @return EmptyUnionType (Real return type omitted for performance)
     */
    public static function empty()
    {
        return self::$empty_instance;
    }

    /**
     * @return void
     * @internal
     */
    public static function init()
    {
        if (is_null(self::$empty_instance)) {
            self::$empty_instance = EmptyUnionType::instance();
        }
    }

    // __clone of $this->type_set would be a no-op due to copy on write semantics.
    // And clone isn't necessary anymore now that type_set is immutable

    /**
     * @param string $fully_qualified_string
     * A '|' delimited string representing a type in the form
     * 'int|string|null|ClassName'.
     *
     * @return UnionType
     */
    public static function fromFullyQualifiedString(
        string $fully_qualified_string
    ) : UnionType {
        if ($fully_qualified_string === '') {
            return self::$empty_instance;
        }

        /** @var array<string,UnionType> annotation not read by phan */
        static $memoize_map = [];
        $union_type = $memoize_map[$fully_qualified_string] ?? null;

        if (is_null($union_type)) {
            $types = \array_map(function (string $type_name) {
                return Type::fromFullyQualifiedString($type_name);
            }, self::extractTypeParts($fully_qualified_string));

            $unique_types = self::getUniqueTypes(self::normalizeMultiTypes($types));
            if (\count($unique_types) === 1) {
                $union_type = \reset($unique_types)->asUnionType();
            } else {
                // TODO: Support brackets, template types within <>, etc.
                $union_type = new UnionType(
                    $unique_types,
                    true
                );
            }
            $memoize_map[$fully_qualified_string] = $union_type;
        }

        return $union_type;
    }

    /**
     * @param array<int,Type> $type_list
     * @return array<int,Type>
     */
    public static function getUniqueTypes(array $type_list) : array
    {
        $new_type_list = [];
        foreach ($type_list as $type) {
            if (!\in_array($type, $new_type_list, true)) {
                $new_type_list[] = $type;
            }
        }
        return $new_type_list;
    }

    /**
     * @param string $type_string
     * A '|' delimited string representing a type in the form
     * 'int|string|null|ClassName'.
     *
     * @param Context $context
     * The context in which the type string was
     * found
     *
     * @param int $source one of the constants in Type::FROM_*
     *
     * @return UnionType
     */
    public static function fromStringInContext(
        string $type_string,
        Context $context,
        int $source
    ) : UnionType {
        if (empty($type_string)) {
            return self::$empty_instance;
        }

        // If our scope has a generic type identifier defined on it
        // that matches the type string, return that UnionType.
        if ($context->getScope()->hasTemplateType($type_string)) {
            return $context->getScope()->getTemplateType(
                $type_string
            )->asUnionType();
        }
        $types = [];
        foreach (self::extractTypePartsForStringInContext($type_string) as $type_name) {
            $types[] = Type::fromStringInContext(
                $type_name,
                $context,
                $source
            );
        }
        return UnionType::of(self::normalizeMultiTypes($types));
    }

    /**
     * @return array<int,string>
     */
    private static function extractTypePartsForStringInContext(string $type_string)
    {
        static $cache = [];
        $parts = $cache[$type_string] ?? null;
        if (\is_array($parts)) {
            return $parts;
        }
        $parts = [];
        foreach (self::extractTypeParts($type_string) as $type_name) {
            // Exclude empty type names
            // Exclude namespaces without type names (e.g. `\`, `\NS\`)
            if ($type_name !== '' && \preg_match('@\\\\[\[\]]*$@', $type_name) === 0) {
                $parts[] = $type_name;
            }
        }
        $cache[$type_string] = $parts;
        return $parts;
    }

    /**
     * @return array<int,string>
     */
    private static function extractTypeParts(string $type_string) : array
    {
        $parts = [];
        foreach (\explode('|', $type_string) as $part) {
            $parts[] = \trim($part);
        }

        if (\count($parts) <= 1) {
            return $parts;
        }
        if (!\preg_match('/[<({]/', $type_string)) {
            return $parts;
        }
        return self::mergeTypeParts($parts);
    }

    /**
     * Expands any GenericMultiArrayType and ScalarRawType instances in $types if necessary.
     *
     * @param array<int,Type> $types
     * @return array<int,Type>
     */
    public static function normalizeMultiTypes(array $types) : array
    {
        foreach ($types as $i => $type) {
            if ($type instanceof MultiType) {
                foreach ($type->asIndividualTypeInstances() as $new_type) {
                    $types[] = $new_type;
                }
                unset($types[$i]);
            }
        }
        return $types;
    }

    /**
     * @param string[] $parts (already trimmed)
     * @return string[]
     * @see Type::extractTemplateParameterTypeNameList (Similar method)
     * @suppress PhanPluginUnusedVariable $prev_parts is used in loop
     */
    private static function mergeTypeParts(array $parts) : array
    {
        $prev_parts = [];
        $delta = 0;
        $results = [];
        foreach ($parts as $part) {
            if (\count($prev_parts) > 0) {
                $prev_parts[] = $part;
                $delta += \substr_count($part, '<') + \substr_count($part, '(') + \substr_count($part, '{') - \substr_count($part, '>') - \substr_count($part, ')') - \substr_count($part, '}');
                if ($delta <= 0) {
                    if ($delta === 0) {
                        $results[] = \implode('|', $prev_parts);
                    }  // ignore unparseable data such as "<T,T2>>"
                    $prev_parts = [];
                    $delta = 0;
                    continue;
                }
                continue;
            }
            $bracket_count = \substr_count($part, '<') + \substr_count($part, '(') + \substr_count($part, '{');
            if ($bracket_count === 0) {
                $results[] = $part;
                continue;
            }
            $delta = $bracket_count - \substr_count($part, '>') - \substr_count($part, ')') - \substr_count($part, '}');
            if ($delta === 0) {
                $results[] = $part;
            } elseif ($delta > 0) {
                $prev_parts[] = $part;
            }  // otherwise ignore unparseable data such as ">" (should be impossible)
        }
        return $results;
    }

    /**
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     *
     * @param CodeBase $code_base
     * The code base within which we're operating
     *
     * @param Node|string|bool|int|float|null $node
     * The node for which we'd like to determine its type
     *
     * @param bool $should_catch_issue_exception
     * Set to true to cause loggable issues to be thrown
     * instead of emitted as issues to the log.
     *
     * @return UnionType
     *
     * @throws IssueException
     * If $should_catch_issue_exception is false an IssueException may
     * be thrown for optional issues.
     *
     * @deprecated - Use UnionTypeVisitor::unionTypeFromNode
     */
    public static function fromNode(
        Context $context,
        CodeBase $code_base,
        $node,
        bool $should_catch_issue_exception = true
    ) : UnionType {
        return UnionTypeVisitor::unionTypeFromNode(
            $code_base,
            $context,
            $node,
            $should_catch_issue_exception
        );
    }

    /**
     * @param ?\ReflectionType $reflection_type
     *
     * @return UnionType
     * A UnionType with 0 or 1 nullable/non-nullable Types
     */
    public static function fromReflectionType($reflection_type) : UnionType
    {
        if ($reflection_type !== null) {
            return Type::fromReflectionType($reflection_type)->asUnionType();
        }
        return self::$empty_instance;
    }

    /**
     * @return string[]
     * Get a map from property name to its type for the given
     * class name.
     */
    public static function internalPropertyMapForClassName(
        string $class_name
    ) : array {
        $map = self::internalPropertyMap();

        $canonical_class_name = \strtolower($class_name);

        return $map[$canonical_class_name] ?? [];
    }

    /**
     * @return array
     * A map from builtin class properties to type information
     *
     * @see \Phan\Language\Internal\PropertyMap
     */
    private static function internalPropertyMap() : array
    {
        static $map = [];

        if (!$map) {
            $map_raw = require(__DIR__.'/Internal/PropertyMap.php');
            foreach ($map_raw as $key => $value) {
                $map[\strtolower($key)] = $value;
            }

            // Merge in an empty type for dynamic properties on any
            // classes listed as supporting them.
            foreach (require(__DIR__.'/Internal/DynamicPropertyMap.php') as $class_name) {
                $map[\strtolower($class_name)]['*'] = '';
            }
        }

        return $map;
    }

    /**
     * A list of types for parameters associated with the
     * given builtin function with the given name
     *
     * @param FullyQualifiedMethodName|FullyQualifiedFunctionName $function_fqsen
     *
     * @see internal_varargs_check
     * Formerly `function internal_varargs_check`
     */
    public static function internalFunctionSignatureMapForFQSEN(
        $function_fqsen
    ) : array {
        $map = self::internalFunctionSignatureMap(Config::get_closest_target_php_version_id());

        if ($function_fqsen instanceof FullyQualifiedMethodName) {
            $class_fqsen =
                $function_fqsen->getFullyQualifiedClassName();
            $class_name = $class_fqsen->getNamespacedName();
            $function_name =
                $class_name . '::' . $function_fqsen->getName();
        } else {
            $function_name = $function_fqsen->getNamespacedName();
        }

        $function_name = \strtolower($function_name);

        $function_name_original = $function_name;
        $alternate_id = 0;

        /**
         * @param string|null $type_name
         * @return UnionType|null
         */
        $get_for_global_context = function ($type_name) {
            if (!$type_name) {
                return null;
            }

            static $internal_fn_cache = [];


            $result = $internal_fn_cache[$type_name] ?? null;
            if ($result === null) {
                $context = new Context;
                $result = UnionType::fromStringInContext($type_name, $context, Type::FROM_PHPDOC);
                $internal_fn_cache[$type_name] = $result;
            }
            return $result;
        };

        $configurations = [];
        while (isset($map[$function_name])) {
            // Get some static data about the function
            $type_name_struct = $map[$function_name];

            // Figure out the return type
            $return_type_name = $type_name_struct[0];
            $return_type = $get_for_global_context($return_type_name);

            $parameter_name_type_map = [];

            foreach ($type_name_struct as $name => $type_name) {
                if (\is_int($name)) {
                    // Integer key names are reserved for metadata in the future.
                    continue;
                }
                $parameter_name_type_map[$name] = $get_for_global_context($type_name) ?? self::$empty_instance;
            }

            $configurations[] = [
                'return_type' => $return_type,
                'parameter_name_type_map' => $parameter_name_type_map,
            ];

            $function_name =
                $function_name_original . '\'' . (++$alternate_id);
        }

        return $configurations;
    }

    /**
     * @return array<int,Type>
     * The list of simple types associated with this
     * union type. Keys are consecutive.
     */
    public function getTypeSet() : array
    {
        return $this->type_set;
    }

    /**
     * Add a type name to the list of types
     *
     * @return UnionType
     */
    public function withType(Type $type)
    {
        $type_set = $this->type_set;
        if (\count($type_set) === 0) {
            return $type->asUnionType();
        }
        if (\in_array($type, $type_set, true)) {
            return $this;
        }
        // 2 or more types in type_set
        $type_set[] = $type;
        return new UnionType($type_set, true);
    }

    /**
     * Returns a new union type
     * which removes this type from the list of types,
     * keeping the keys in a consecutive order.
     *
     * Each type in $this->type_set occurs exactly once.
     *
     * @return UnionType
     */
    public function withoutType(Type $type)
    {
        // Copy the array $this->type_set
        $type_set = $this->type_set;
        foreach ($type_set as $key => $other_type) {
            if ($type === $other_type) {
                // Remove the only instance of $type from the copy.
                unset($type_set[$key]);
                return self::ofUniqueTypes($type_set);
            }
        }
        // We did not find $type in type_set. The resulting union type is unchanged.
        return $this;
    }

    /**
     * @return bool
     * True if this union type contains the given named
     * type.
     */
    public function hasType(Type $type) : bool
    {
        return \in_array($type, $this->type_set, true);
    }

    /**
     * Returns a union type which add the given types to this type
     *
     * @return UnionType
     */
    public function withUnionType(UnionType $union_type)
    {
        // Precondition: Both UnionTypes have lists of unique types.
        $type_set = $this->type_set;
        if (\count($type_set) === 0) {
            return $union_type;
        }
        $other_type_set = $union_type->type_set;

        if (\count($other_type_set) === 0) {
            return $this;
        }
        $new_type_set = $type_set;
        foreach ($other_type_set as $type) {
            if (!\in_array($type, $type_set, true)) {
                $new_type_set[] = $type;
            }
        }
        return new UnionType($new_type_set, true);
    }

    /**
     * @return bool
     * True if this type has a type referencing the
     * class context in which it exists such as 'self'
     * or '$this'
     */
    public function hasSelfType() : bool
    {
        foreach ($this->type_set as $type) {
            if ($type->isSelfType()) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return bool
     * True if this union type has any types that are bool/false/true types
     */
    public function hasTypeInBoolFamily() : bool
    {
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type->getIsInBoolFamily();
        });
    }

    /**
     * @return array<int,UnionType>
     * A map from template type identifiers to the UnionType
     * to replace it with
     * TODO: Is anything using this? This makes sense for Type but not UnionType.
     */
    public function getTemplateParameterTypeList() : array
    {
        return \array_reduce(
            $this->type_set,
            function (array $map, Type $type) {
                return \array_merge(
                    $type->getTemplateParameterTypeList(),
                    $map
                );
            },
            []
        );
    }


    /**
     * @param CodeBase $code_base
     * The code base to look up classes against
     *
     * TODO: Defer resolving the template parameters until parse ends. Low priority.
     *
     * @return array<string,UnionType>
     * A map from template type identifiers to the UnionType
     * to replace it with
     */
    public function getTemplateParameterTypeMap(
        CodeBase $code_base
    ) : array {
        if ($this->isEmpty()) {
            return [];
        }

        return \array_reduce(
            $this->type_set,
            function (array $map, Type $type) use ($code_base) {
                return \array_merge(
                    $type->getTemplateParameterTypeMap($code_base),
                    $map
                );
            },
            []
        );
    }


    /**
     * @param array<string,UnionType> $template_parameter_type_map
     * A map from template type identifiers to concrete types
     *
     * @return UnionType
     * This UnionType with any template types contained herein
     * mapped to concrete types defined in the given map.
     */
    public function withTemplateParameterTypeMap(
        array $template_parameter_type_map
    ) : UnionType {
        $has_template = false;
        $concrete_type_list = [];
        foreach ($this->type_set as $type) {
            // TODO: This should check GenericArray and ArrayShape as well!
            if ($type instanceof TemplateType
                && isset($template_parameter_type_map[$type->getName()])
            ) {
                $has_template = true;
                $union_type =
                    $template_parameter_type_map[$type->getName()];

                foreach ($union_type->type_set as $concrete_type) {
                    $concrete_type_list[] = $concrete_type;
                }
            } else {
                $concrete_type_list[] = $type;
            }
        }

        return $has_template ? UnionType::of($concrete_type_list) : $this;
    }

    /**
     * @return bool
     * True if this union type has any types that are generic
     * types
     */
    public function hasTemplateType() : bool
    {
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return ($type instanceof TemplateType);
        });
    }

    /**
     * @return bool
     * True if this union type has any types that have generic
     * types
     */
    public function hasTemplateParameterTypes() : bool
    {
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type->hasTemplateParameterTypes();
        });
    }

    /**
     * @return bool
     * True if this type has a type referencing the
     * class context 'static'.
     */
    public function hasStaticType() : bool
    {
        foreach ($this->type_set as $type) {
            if ($type instanceof StaticType) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return UnionType
     * A new UnionType with any references to 'static' resolved
     * in the given context.
     */
    public function withStaticResolvedInContext(
        Context $context
    ) : UnionType {

        // If the context isn't in a class scope, there's nothing
        // we can do
        if (!$context->isInClassScope()) {
            return $this;
        }

        static $static_type;
        static $static_nullable_type;
        if ($static_type === null) {
            $static_type = StaticType::instance(false);
            $static_nullable_type = StaticType::instance(true);
        }

        $has_static_type = \in_array($static_type, $this->type_set, true);
        $has_static_nullable_type = \in_array($static_nullable_type, $this->type_set, true);

        // If this doesn't reference 'static', there's nothing to do.
        if (!($has_static_type || $has_static_nullable_type)) {
            return $this;
        }

        if ($has_static_type) {
            // Remove the static type and add in the class in scope
            return $this->withoutType($static_type)->withType($context->getClassFQSEN()->asType());
        } else {
            return $this->withoutType($static_type)->withType($context->getClassFQSEN()->asType()->withIsNullable(true));
        }
    }

    /**
     * @return bool
     * True if and only if this UnionType contains
     * the given type and no others.
     */
    public function isType(Type $type) : bool
    {
        $type_set = $this->type_set;
        if (\count($type_set) !== 1) {
            return false;
        }

        return \reset($type_set) === $type;
    }

    /**
     * @return bool
     * True if this UnionType is exclusively native
     * types
     */
    public function isNativeType() : bool
    {
        if ($this->isEmpty()) {
            return false;
        }

        return !$this->hasTypeMatchingCallback(function (Type $type) : bool {
            return !$type->isNativeType();
        });
    }

    /**
     * @return bool
     * True iff this union contains the exact set of types
     * represented in the given union type.
     */
    public function isEqualTo(UnionType $union_type) : bool
    {
        $type_set = $this->type_set;
        $other_type_set = $union_type->type_set;
        if (\count($type_set) !== \count($other_type_set)) {
            return false;
        }
        foreach ($type_set as $type) {
            if (!\in_array($type, $other_type_set, true)) {
                return false;
            }
        }
        return true;
    }

    /**
     * @return bool
     * True iff this union contains a type that's also in
     * the other union type.
     */
    public function hasCommonType(UnionType $union_type) : bool
    {
        $other_type_set = $union_type->type_set;
        foreach ($this->type_set as $type) {
            if (\in_array($type, $other_type_set, true)) {
                return true;
            }
        }
        return false;
    }

    public function withoutSubclassesOf(CodeBase $code_base, Type $object_type) : UnionType
    {
        $is_nullable = $this->containsNullable();
        $new_variable_type = $this;

        foreach ($this->type_set as $type) {
            if ($type->asExpandedTypes($code_base)->hasType($object_type)) {
                $new_variable_type = $new_variable_type->withoutType($type);
            }
        }
        if ($is_nullable) {
            if ($new_variable_type->isEmpty()) {
                // There was a null somewhere in the old union type.
                return NullType::instance(false)->asUnionType();
            }
            return $new_variable_type->nullableClone();
        }
        return $new_variable_type;
    }

    /**
     * @return bool - True if not empty and at least one type is NullType or nullable.
     */
    public function containsNullable() : bool
    {
        foreach ($this->type_set as $type) {
            if ($type->getIsNullable()) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return bool - True if not empty, not possibly undefined, and at least one type is NullType or nullable.
     */
    public function containsNullableOrUndefined() : bool
    {
        return $this->containsNullable();
    }

    public function nonNullableClone() : UnionType
    {
        $builder = new UnionTypeBuilder();
        $did_change = false;
        foreach ($this->type_set as $type) {
            if (!$type->getIsNullable()) {
                $builder->addType($type);
                continue;
            }
            $did_change = true;
            if ($type === NullType::instance(false)) {
                continue;
            }

            $builder->addType($type->withIsNullable(false));
        }
        return $did_change ? $builder->getUnionType() : $this;
    }

    public function nullableClone() : UnionType
    {
        $builder = new UnionTypeBuilder();
        $did_change = false;
        foreach ($this->type_set as $type) {
            if ($type->getIsNullable()) {
                $builder->addType($type);
                continue;
            }
            $did_change = true;
            $builder->addType($type->withIsNullable(true));
        }
        return $did_change ? $builder->getUnionType() : $this;
    }

    /**
     * @return bool - True if type set is not empty and at least one type is NullType or nullable or FalseType or BoolType.
     * (I.e. the type is always falsey, or both sometimes falsey with a non-falsey type it can be narrowed down to)
     * This does not include values such as `IntType`, since there is currently no `NonZeroIntType`.
     */
    public function containsFalsey() : bool
    {
        foreach ($this->type_set as $type) {
            if ($type->getIsPossiblyFalsey()) {
                return true;
            }
        }
        return false;
    }

    public function nonFalseyClone() : UnionType
    {
        $builder = new UnionTypeBuilder();
        $did_change = false;
        foreach ($this->type_set as $type) {
            if (!$type->getIsPossiblyFalsey()) {
                $builder->addType($type);
                continue;
            }
            $did_change = true;
            if ($type->getIsAlwaysFalsey()) {
                // don't add null/false to the resulting type
                continue;
            }

            // add non-nullable equivalents, and replace BoolType with non-nullable TrueType
            $builder->addType($type->asNonFalseyType());
        }
        return $did_change ? $builder->getUnionType() : $this;
    }

    /**
     * @return bool - True if type set is not empty and at least one type is NullType or nullable or FalseType or BoolType.
     * (I.e. the type is always falsey, or both sometimes falsey with a non-falsey type it can be narrowed down to)
     * This does not include values such as `IntType`, since there is currently no `NonZeroIntType`.
     */
    public function containsTruthy() : bool
    {
        foreach ($this->type_set as $type) {
            if ($type->getIsPossiblyTruthy()) {
                return true;
            }
        }
        return false;
    }

    public function nonTruthyClone() : UnionType
    {
        $builder = new UnionTypeBuilder();
        $did_change = false;
        foreach ($this->type_set as $type) {
            if (!$type->getIsPossiblyTruthy()) {
                $builder->addType($type);
                continue;
            }
            $did_change = true;
            if ($type->getIsAlwaysTruthy()) {
                // don't add null/false to the resulting type
                continue;
            }

            // add non-nullable equivalents, and replace BoolType with non-nullable TrueType
            $builder->addType($type->asNonTruthyType());
        }
        return $did_change ? $builder->getUnionType() : $this;
    }

    /**
     * @return bool - True if type set is not empty and at least one type is BoolType or FalseType
     */
    public function containsFalse() : bool
    {
        foreach ($this->type_set as $type) {
            if ($type->getIsPossiblyFalse()) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return bool - True if type set is not empty and at least one type is BoolType or TrueType
     */
    public function containsTrue() : bool
    {
        foreach ($this->type_set as $type) {
            if ($type->getIsPossiblyTrue()) {
                return true;
            }
        }
        return false;
    }

    public function nonFalseClone() : UnionType
    {
        $builder = new UnionTypeBuilder();
        $did_change = false;
        foreach ($this->type_set as $type) {
            if (!$type->getIsPossiblyFalse()) {
                $builder->addType($type);
                continue;
            }
            $did_change = true;
            if ($type->getIsAlwaysFalse()) {
                // don't add null/false to the resulting type
                continue;
            }

            // add non-nullable equivalents, and replace BoolType with non-nullable TrueType
            $builder->addType($type->asNonFalseType());
        }
        return $did_change ? $builder->getUnionType() : $this;
    }

    public function nonTrueClone() : UnionType
    {
        $builder = new UnionTypeBuilder();
        $did_change = false;
        foreach ($this->type_set as $type) {
            if (!$type->getIsPossiblyTrue()) {
                $builder->addType($type);
                continue;
            }
            $did_change = true;
            if ($type->getIsAlwaysTrue()) {
                // don't add null/false to the resulting type
                continue;
            }

            // add non-nullable equivalents, and replace BoolType with non-nullable TrueType
            $builder->addType($type->asNonTrueType());
        }
        return $did_change ? $builder->getUnionType() : $this;
    }

    /**
     * @param UnionType $union_type
     * A union type to compare against
     *
     * @param Context $context
     * The context in which this type exists.
     *
     * @param CodeBase $code_base
     * The code base in which both this and the given union
     * types exist.
     *
     * @return bool
     * True if each type within this union type can cast
     * to the given union type.
     */
    // Currently unused and buggy, commenting this out.
    /**
    public function isExclusivelyNarrowedFormOrEquivalentTo(
        UnionType $union_type,
        Context $context,
        CodeBase $code_base
    ) : bool {

        // Special rule: anything can cast to nothing
        // and nothing can cast to anything
        if ($union_type->isEmpty() || $this->isEmpty()) {
            return true;
        }

        // Check to see if the types are equivalent
        if ($this->isEqualTo($union_type)) {
            return true;
        }
        // TODO: Allow casting MyClass<TemplateType> to MyClass (Without the template?

        // Resolve 'static' for the given context to
        // determine whats actually being referred
        // to in concrete terms.
        $other_resolved_type =
            $union_type->withStaticResolvedInContext($context);
        $other_resolved_type_set = $other_resolved_type->type_set;

        // Convert this type to a set of resolved types to iterate over.
        $this_resolved_type_set =
            $this->withStaticResolvedInContext($context)->type_set;

        // TODO: Need to resolve expanded union types (parents, interfaces) of classes *before* this is called.

        // Test to see if every single type in this union
        // type can cast to the given union type.
        foreach ($this_resolved_type_set as $type) {
            // First check if this contains the type as an optimization.
            if ($other_resolved_type_set->contains($type)) {
                continue;
            }
            $expanded_types = $type->asExpandedTypes($code_base);
            if ($other_resolved_type->canCastToUnionType(
                $expanded_types
            )) {
                continue;
            }
        }
        return true;
    }
     */

    /**
     * @param Type[] $type_list
     * A list of types
     *
     * @return bool
     * True if this union type contains any of the given
     * named types
     */
    public function hasAnyType(array $type_list) : bool
    {
        $type_set = $this->type_set;
        if (\count($type_set) === 0) {
            return false;
        }
        foreach ($type_list as $type) {
            if (\in_array($type, $type_set, true)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return bool
     * True if this type has any subtype of `iterable` type (e.g. Traversable, Array).
     */
    public function hasIterable() : bool
    {
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type->isIterable();
        });
    }

    /**
     * @return int
     * The number of types in this union type
     */
    public function typeCount() : int
    {
        return \count($this->type_set);
    }

    /**
     * @return bool
     * True if this Union has no types
     */
    public function isEmpty() : bool
    {
        return \count($this->type_set) === 0;
    }

    /**
     * @param UnionType $target
     * The type we'd like to see if this type can cast
     * to
     *
     * @param CodeBase $code_base
     * The code base used to expand types
     *
     * @return bool
     * Test to see if this type can be cast to the
     * given type after expanding both union types
     * to include all ancestor types
     *
     * TODO: ensure that this is only called after the parse phase is over.
     */
    public function canCastToExpandedUnionType(
        UnionType $target,
        CodeBase $code_base
    ) : bool {

        $this_expanded =
            $this->asExpandedTypes($code_base);

        $target_expanded =
            $target->asExpandedTypes($code_base);

        return
            $this_expanded->canCastToUnionType(
                $target_expanded
            );
    }

    /**
     * @param UnionType $target
     * A type to check to see if this can cast to it
     *
     * @return bool
     * True if this type is allowed to cast to the given type
     * i.e. int->float is allowed  while float->int is not.
     */
    public function canCastToUnionType(
        UnionType $target
    ) : bool {
        // Fast-track most common cases first
        $type_set = $this->type_set;
        // If either type is unknown, we can't call it
        // a success
        if (\count($type_set) === 0) {
            return true;
        }
        $target_type_set = $target->type_set;
        if (\count($target_type_set) === 0) {
            return true;
        }

        // T overlaps with T, a future call to Type->canCastToType will pass.
        if ($this->hasCommonType($target)) {
            return true;
        }
        static $float_type;
        static $int_type;
        static $mixed_type;
        static $null_type;
        if ($null_type === null) {
            $int_type   = IntType::instance(false);
            $float_type = FloatType::instance(false);
            $mixed_type = MixedType::instance(false);
            $null_type  = NullType::instance(false);
        }

        if (Config::get_null_casts_as_any_type()) {
            // null <-> null
            if ($this->isType($null_type)
                || $target->isType($null_type)
            ) {
                return true;
            }
        } else {
            // If null_casts_as_any_type isn't set, then try the other two fallbacks.
            if (Config::get_null_casts_as_array() && $this->isType($null_type) && $target->hasArrayLike()) {
                return true;
            } elseif (Config::get_array_casts_as_null() && $target->isType($null_type) && $this->hasArrayLike()) {
                return true;
            }
        }

        // mixed <-> mixed
        if (\in_array($mixed_type, $type_set, true)
            || \in_array($mixed_type, $target_type_set, true)
        ) {
            return true;
        }

        // int -> float
        if (\in_array($int_type, $type_set, true)
            && \in_array($float_type, $target_type_set, true)
        ) {
            return true;
        }

        // Check conversion on the cross product of all
        // type combinations and see if any can cast to
        // any.
        foreach ($type_set as $source_type) {
            foreach ($target_type_set as $target_type) {
                if ($source_type->canCastToType($target_type)) {
                    return true;
                }
            }
        }

        // Allow casting ?T to T|null for any type T. Check if null is part of this type first.
        if (\in_array($null_type, $target_type_set, true)) {
            foreach ($type_set as $source_type) {
                // Only redo this check for the nullable types, we already failed the checks for non-nullable types.
                if ($source_type->getIsNullable()) {
                    $non_null_source_type = $source_type->withIsNullable(false);
                    foreach ($target_type_set as $target_type) {
                        if ($non_null_source_type->canCastToType($target_type)) {
                            return true;
                        }
                    }
                }
            }
        }

        // Only if no source types can be cast to any target
        // types do we say that we cannot perform the cast
        return false;
    }

    /**
     * @param UnionType $target
     * A type to check to see if this can cast to it.
     *
     * Every single type in this type must be able to cast to a type in $target (Empty types can cast to empty)
     *
     * @return bool
     * True if this type is allowed to cast to the given type
     * i.e. int->float is allowed  while float->int is not.
     *
     * @suppress PhanUnreferencedPublicMethod may be used elsewhere in the future
     */
    public function canStrictCastToUnionType(UnionType $target) : bool
    {
        // Fast-track most common cases first
        $type_set = $this->type_set;
        // If either type is unknown, we can't call it
        // a success
        if (\count($type_set) === 0) {
            return true;
        }
        $target_type_set = $target->type_set;
        if (\count($target_type_set) === 0) {
            return true;
        }

        // every single type in T overlaps with T, a future call to Type->canCastToType will pass.
        $matches = true;
        foreach ($type_set as $type) {
            if (!\in_array($type, $target_type_set)) {
                $matches = false;
                break;
            }
        }
        if ($matches) {
            return true;
        }
        static $null_type;
        if ($null_type === null) {
            $null_type  = NullType::instance(false);
        }

        // Check conversion on the cross product of all
        // type combinations and see if any can cast to
        // any.
        $matches = true;
        foreach ($type_set as $source_type) {
            foreach ($target_type_set as $target_type) {
                if (!$source_type->canCastToType($target_type)) {
                    $matches = false;
                    break;
                }
            }
        }
        if ($matches) {
            return true;
        }

        // Allow casting ?T to T|null for any type T. Check if null is part of this type first.
        if (\in_array($null_type, $target_type_set, true)) {
            foreach ($type_set as $source_type) {
                // Only redo this check for the nullable types, we already failed the checks for non-nullable types.
                $matches = false;
                $non_null_source_type = $source_type->withIsNullable(false);
                foreach ($target_type_set as $target_type) {
                    if (!$non_null_source_type->canCastToType($target_type)) {
                        $matches = true;
                        break;
                    }
                }
                if (!$matches) {
                    return false;
                }
            }
            return true;
        }

        // Only if no source types can be cast to any target
        // types do we say that we cannot perform the cast
        return false;
    }

    /**
     * @return bool
     * True if all types in this union are scalars
     */
    public function isScalar() : bool
    {
        if ($this->isEmpty()) {
            return false;
        }

        return !$this->hasTypeMatchingCallback(function (Type $type) : bool {
            return !$type->isScalar();
        });
    }

    /**
     * @return bool
     * True if any types in this union are a printable scalar, or this is the empty union type
     * @internal
     */
    public function hasPrintableScalar() : bool
    {
        if ($this->isEmpty()) {
            return true;
        }

        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type->isPrintableScalar();
        });
    }

    /**
     * @return bool
     * True if this union has array-like types (is of type array, is
     * a generic array, or implements ArrayAccess).
     */
    public function hasArrayLike() : bool
    {
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type->isArrayLike();
        });
    }

    /**
     * @return bool
     * True if this union has array-like types (is of type array,
     * or is a generic array)
     */
    public function hasArray() : bool
    {
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type instanceof ArrayType;
        });
    }

    /**
     * @return bool
     * True if this union has array-like types (is of type array, is
     * a generic array, is an array shape, or implements ArrayAccess).
     */
    public function hasGenericArray() : bool
    {
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type->isGenericArray();
        });
    }

    /**
     * @return bool
     * True if this union contains the ArrayAccess type.
     * (Call asExpandedTypes() first to check for subclasses of ArrayAccess)
     */
    public function hasArrayAccess() : bool
    {
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type->isArrayAccess();
        });
    }

    /**
     * @return bool
     * True if this union contains the Traversable type.
     * (Call asExpandedTypes() first to check for subclasses of Traversable)
     * @suppress PhanUnreferencedPublicMethod not used right now.
     */
    public function hasTraversable() : bool
    {
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type->isTraversable();
        });
    }

    /**
     * @return bool
     * True if this union type represents types that are
     * array-like, and nothing else (e.g. can't be null).
     * If any of the array-like types are nullable, this returns false.
     */
    public function isExclusivelyArrayLike() : bool
    {
        if ($this->isEmpty()) {
            return false;
        }

        return !$this->hasTypeMatchingCallback(function (Type $type) : bool {
            return !$type->isArrayLike() || $type->getIsNullable();
        });
    }

    /**
     * @return bool
     * True if this union type represents types that are arrays
     * or generic arrays, but nothing else.
     * @suppress PhanUnreferencedPublicMethod
     */
    public function isExclusivelyArray() : bool
    {
        if ($this->isEmpty()) {
            return false;
        }

        return !$this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type !== ArrayType::instance(false) && !$type->isGenericArray();
        });
    }

    /**
     * @return UnionType
     * Get the subset of types which are not native
     */
    public function nonNativeTypes() : UnionType
    {
        return $this->makeFromFilter(function (Type $type) {
            return !$type->isNativeType();
        });
    }

    /**
     * A memory efficient way to create a UnionType from a filter operation.
     * If this the filter preserves everything, returns $this instead
     */
    public function makeFromFilter(\Closure $cb) : UnionType
    {
        $new_type_list = [];
        foreach ($this->type_set as $type) {
            if ($cb($type)) {
                $new_type_list[] = $type;
            }
        }
        if (\count($new_type_list) === \count($this->type_set)) {
            return $this;
        }
        return new UnionType($new_type_list, true);
    }

    /**
     * @param Context $context
     * The context in which we're resolving this union
     * type.
     *
     * @return \Generator
     *
     * A list of class FQSENs representing the non-native types
     * associated with this UnionType
     *
     * @throws CodeBaseException
     * An exception is thrown if a non-native type does not have
     * an associated class
     *
     * @throws IssueException
     * An exception is thrown if static is used as a type outside of an object
     * context
     *
     * TODO: Add a method to ContextNode to directly get FQSEN instead?
     */
    public function asClassFQSENList(
        Context $context
    ) {
        // Iterate over each viable class type to see if any
        // have the constant we're looking for
        foreach ($this->type_set as $class_type) {
            if ($class_type->isNativeType()) {
                continue;
            }
            // Get the class FQSEN
            $class_fqsen = $class_type->asFQSEN();

            if ($class_type->isStaticType()) {
                if (!$context->isInClassScope()) {
                    throw new IssueException(
                        Issue::fromType(Issue::ContextNotObject)(
                            $context->getFile(),
                            $context->getLineNumberStart(),
                            [
                                $class_type->getName()
                            ]
                        )
                    );
                }
                yield $class_fqsen;
            } else {
                yield $class_fqsen;
            }
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which to find classes
     *
     * @param Context $context
     * The context in which we're resolving this union
     * type.
     *
     * @return \Generator
     *
     * A list of classes representing the non-native types
     * associated with this UnionType
     *
     * @throws CodeBaseException
     * An exception is thrown if a non-native type does not have
     * an associated class
     *
     * @throws IssueException
     * An exception is thrown if static is used as a type outside of an object
     * context
     */
    public function asClassList(
        CodeBase $code_base,
        Context $context
    ) {
        // Iterate over each viable class type to see if any
        // have the constant we're looking for
        foreach ($this->type_set as $class_type) {
            if ($class_type->isNativeType()) {
                continue;
            }
            // Get the class FQSEN
            $class_fqsen = FullyQualifiedClassName::fromType($class_type);

            if ($class_type->isStaticType()) {
                if (!$context->isInClassScope()) {
                    throw new IssueException(
                        Issue::fromType(Issue::ContextNotObject)(
                            $context->getFile(),
                            $context->getLineNumberStart(),
                            [
                                $class_type->getName()
                            ]
                        )
                    );
                }
                yield $context->getClassInScope($code_base);
            } else {
                if ($class_type->isSelfType()) {
                    if (!$context->isInClassScope()) {
                        throw new IssueException(
                            Issue::fromType(Issue::ContextNotObject)(
                                $context->getFile(),
                                $context->getLineNumberStart(),
                                [
                                    $class_type->getName()
                                ]
                            )
                        );
                    }
                    if (strcasecmp($class_type->getName(), 'self') === 0) {
                        yield $context->getClassInScope($code_base);
                    } else {
                        yield $class_type;
                    }
                    continue;
                }
                // See if the class exists
                if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
                    throw new CodeBaseException(
                        $class_fqsen,
                        "Cannot find class $class_fqsen"
                    );
                }

                yield $code_base->getClassByFQSEN($class_fqsen);
            }
        }
    }

    /**
     * Takes "a|b[]|c|d[]|e" and returns "a|c|e"
     *
     * @return UnionType
     * A UnionType with generic array types filtered out
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function nonGenericArrayTypes() : UnionType
    {
        return $this->makeFromFilter(function (Type $type) : bool {
            return !$type->isGenericArray();
        });
    }

    /**
     * Takes "a|b[]|c|d[]|e" and returns "b[]|d[]"
     *
     * @return UnionType
     * A UnionType with generic array types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function genericArrayTypes() : UnionType
    {
        return $this->makeFromFilter(function (Type $type) : bool {
            return $type->isGenericArray();
        });
    }

    /**
     * Takes "MyClass|int|array|?object" and returns "MyClass|?object"
     *
     * @return UnionType
     * A UnionType with known object types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function objectTypes() : UnionType
    {
        return $this->makeFromFilter(function (Type $type) : bool {
            return $type->isObject();
        });
    }

    /**
     * Takes "MyClass|int|array|?object" and returns "MyClass"
     *
     * @return UnionType
     * A UnionType with known object types with known FQSENs kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function objectTypesWithKnownFQSENs() : UnionType
    {
        return $this->makeFromFilter(function (Type $type) : bool {
            return $type->isObjectWithKnownFQSEN();
        });
    }

    /**
     * Returns true if objectTypes would be non-empty.
     *
     * @return bool
     */
    public function hasObjectTypes() : bool
    {
        return $this->hasTypeMatchingCallback((function (Type $type) : bool {
            return $type->isObject();
        }));
    }

    /**
     * Returns true if at least one type could possibly be an object.
     * E.g. returns true for iterator.
     * NOTE: this returns false for `mixed`
     *
     * @return bool
     */
    public function hasPossiblyObjectTypes() : bool
    {
        return $this->hasTypeMatchingCallback((function (Type $type) : bool {
            return $type->isPossiblyObject();
        }));
    }

    /**
     * Returns the types for which is_scalar($x) would be true.
     * This means null/nullable is removed.
     * Takes "MyClass|int|?bool|array|?object" and returns "int|bool"
     * Takes "?MyClass" and returns an empty union type.
     *
     * @return UnionType
     * A UnionType with known scalar types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function scalarTypes() : UnionType
    {
        // TODO: is_scalar(null) is false, account for that in analysis.
        return $this->makeFromFilter(function (Type $type) : bool {
            return $type->isScalar() && !($type instanceof NullType);
        });
    }

    /**
     * Returns the types for which is_callable($x) would be true.
     * TODO: Check for __invoke()?
     * Takes "Closure|false" and returns "Closure"
     * Takes "?MyClass" and returns an empty union type.
     *
     * @return UnionType
     * A UnionType with known callable types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function callableTypes() : UnionType
    {
        return $this->makeFromFilter(function (Type $type) : bool {
            return $type->isCallable();
        });
    }

    /**
     * Returns true if this has one or more callable types
     * TODO: Check for __invoke()?
     * Takes "Closure|false" and returns true
     * Takes "?MyClass" and returns false
     *
     * @return bool
     * A UnionType with known callable types kept, other types filtered out.
     *
     * @see $this->callableTypes()
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function hasCallableType() : bool
    {
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type->isCallable();
        });
    }

    /**
     * Returns true if every type in this type is callable.
     * TODO: Check for __invoke()?
     * Takes "callable" and returns true
     * Takes "callable|false" and returns false
     *
     * @return bool
     * A UnionType with known callable types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     * @suppress PhanUnreferencedPublicMethod not used right now.
     */
    public function isExclusivelyCallable() : bool
    {
        return !$this->hasTypeMatchingCallback(function (Type $type) : bool {
            return !$type->isCallable();
        });
    }

    public function isExclusivelyBoolTypes() : bool
    {
        if ($this->isEmpty()) {
            return false;
        }
        foreach ($this->type_set as $type) {
            if (!$type->getIsInBoolFamily() || $type->getIsNullable()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Takes "a|b[]|c|d[]|e|array|ArrayAccess" and returns "a|c|e|ArrayAccess"
     *
     * @return UnionType
     * A UnionType with generic types(as well as the non-generic type "array")
     * filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function nonArrayTypes() : UnionType
    {
        return $this->makeFromFilter(
            function (Type $type) : bool {
                return !$type->isGenericArray()
                    && $type !== ArrayType::instance(false);
            }
        );
    }

    /**
     * @return bool
     * True if this is exclusively generic types
     */
    public function isGenericArray() : bool
    {
        if ($this->isEmpty()) {
            return false;
        }

        return !$this->hasTypeMatchingCallback(function (Type $type) : bool {
            return !$type->isGenericArray();
        });
    }

    /**
     * @return bool
     * True if any of the types in this UnionType made $matcher_callback return true
     */
    public function hasTypeMatchingCallback(\Closure $matcher_callback) : bool
    {
        foreach ($this->type_set as $type) {
            if ($matcher_callback($type)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return Type|false
     * Returns the first type in this UnionType made $matcher_callback return true
     */
    public function findTypeMatchingCallback(\Closure $matcher_callback)
    {
        foreach ($this->type_set as $type) {
            if ($matcher_callback($type)) {
                return $type;
            }
        }
        return false;
    }

    /**
     * Takes "a|b[]|c|d[]|e" and returns "b|d"
     * Takes "array{field:int,other:string}" and returns "int|string"
     *
     * @return UnionType
     * The subset of types in this
     */
    public function genericArrayElementTypes() : UnionType
    {
        // This is frequently called, and has been optimized
        $builder = new UnionTypeBuilder();
        $type_set = $this->type_set;
        foreach ($type_set as $type) {
            if ($type->isGenericArray()) {
                if ($type instanceof ArrayShapeType) {
                    $builder->addUnionType($type->genericArrayElementUnionType());
                } else {
                    $builder->addType($type->genericArrayElementType());
                }
            }
        }

        static $array_type_nonnull = null;
        static $array_type_nullable = null;
        static $mixed_type = null;
        static $null_type = null;
        if ($array_type_nonnull === null) {
            $array_type_nonnull = ArrayType::instance(false);
            $array_type_nullable = ArrayType::instance(true);
            $mixed_type = MixedType::instance(false);
            $null_type = NullType::instance(false);
        }

        // If array is in there, then it can be any type
        if (\in_array($array_type_nonnull, $type_set, true)) {
            $builder->addType($mixed_type);
            $builder->addType($null_type);
        } elseif (\in_array($mixed_type, $type_set, true)
            || \in_array($array_type_nullable, $type_set, true)
        ) {
            // Same for mixed
            $builder->addType($mixed_type);
        }

        return $builder->getUnionType();
    }

    /**
     * Takes "b|d[]" and returns "b[]|d[][]"
     *
     * @param int $key_type
     * Corresponds to the type of the array keys. Set this to a GenericArrayType::KEY_* constant.
     *
     * @return UnionType
     * The subset of types in this
     */
    public function elementTypesToGenericArray(int $key_type) : UnionType
    {
        $parts = \array_map(function (Type $type) use ($key_type) : Type {
            if ($type instanceof MixedType) {
                return ArrayType::instance(false);
            }
            return GenericArrayType::fromElementType($type, false, $key_type);
        }, $this->type_set);
        if (\count($parts) <= 1) {
            return \count($parts) === 1 ? \reset($parts)->asUnionType() : self::$empty_instance;
        }
        return new UnionType($parts);
    }

    /**
     * @param \Closure $closure
     * A closure mapping `Type` to `Type`
     *
     * @return UnionType
     * A new UnionType with each type mapped through the
     * given closure
     */
    public function asMappedUnionType(\Closure $closure) : UnionType
    {
        $parts = \array_map($closure, $this->type_set);
        if (\count($parts) <= 1) {
            return \count($parts) === 1 ? \reset($parts)->asUnionType() : self::$empty_instance;
        }
        return new UnionType($parts);
    }

    /**
     * @param int $key_type
     * Corresponds to the type of the array keys. Set this to a GenericArrayType::KEY_* constant.
     *
     * @return UnionType
     * Get a new type for each type in this union which is
     * the generic array version of this type. For instance,
     * 'int|float' will produce 'int[]|float[]'.
     *
     * If $this is an empty UnionType, this method will produce an empty UnionType
     */
    public function asGenericArrayTypes(int $key_type) : UnionType
    {
        return $this->asMappedUnionType(
            function (Type $type) use ($key_type) : Type {
                return $type->asGenericArrayType($key_type);
            }
        );
    }

    /**
     * @return UnionType
     * Get a new type for each type in this union which is
     * the generic array version of this type. For instance,
     * 'int|float' will produce 'int[]|float[]'.
     *
     * If $this is an empty UnionType, this method will produce 'array'
     */
    public function asNonEmptyGenericArrayTypes(int $key_type) : UnionType
    {
        if (\count($this->type_set) === 0) {
            return ArrayType::instance(false)->asUnionType();
        }
        return $this->asMappedUnionType(
            function (Type $type) use ($key_type) : Type {
                return $type->asGenericArrayType($key_type);
            }
        );
    }
    /**
     * @param CodeBase $code_base
     * The code base to use in order to find super classes, etc.
     *
     * @param $recursion_depth
     * This thing has a tendency to run-away on me. This tracks
     * how bad I messed up by seeing how far the expanded types
     * go
     *
     * @return UnionType
     * Expands all class types to all inherited classes returning
     * a superset of this type.
     */
    public function asExpandedTypes(
        CodeBase $code_base,
        int $recursion_depth = 0
    ) : UnionType {
        \assert(
            $recursion_depth < 12,
            "Recursion has gotten out of hand"
        );

        $type_set = $this->type_set;
        if (\count($type_set) === 0) {
            return self::$empty_instance;
        } elseif (\count($type_set) === 1) {
            return \reset($type_set)->asExpandedTypes(
                $code_base,
                $recursion_depth + 1
            );
        }
        // 2 or more union types to merge

        $builder = new UnionTypeBuilder();
        foreach ($type_set as $type) {
            $builder->addUnionType(
                $type->asExpandedTypes(
                    $code_base,
                    $recursion_depth + 1
                )
            );
        }
        return $builder->getUnionType();
    }

    /**
     * As per the Serializable interface
     *
     * @return string
     * A serialized representation of this type
     *
     * @see \Serializable
     */
    public function serialize() : string
    {
        return (string)$this;
    }

    /**
     * As per the Serializable interface
     *
     * @param string $serialized
     * A serialized UnionType
     *
     * @return void
     *
     * @see \Serializable
     */
    public function unserialize($serialized)
    {
        // NOTE: Potentially need to handle "array{field:int|string}" in the future.
        // TODO: Not going to work with template types
        $this->type_set = UnionType::fromFullyQualifiedString($serialized)->getTypeSet();
    }

    /**
     * @return string
     * A human-readable string representation of this union
     * type
     */
    public function __toString() : string
    {
        // Create a new array containing the string
        // representations of each type
        $types = $this->type_set;
        $type_name_list =
            \array_map(function (Type $type) : string {
                return (string)$type;
            }, $types);

        // Sort the types so that we get a stable
        // representation
        \asort($type_name_list);

        // Join them with a pipe
        return \implode('|', $type_name_list);
    }

    /**
     * @return array<string,array<int|string,string>>
     * A map from builtin function name to type information
     *
     * @see \Phan\Language\Internal\FunctionSignatureMap
     */
    public static function internalFunctionSignatureMap(int $target_php_version) : array
    {
        static $php72_map = [];

        if (!$php72_map) {
            $php72_map = self::computeLatestFunctionSignatureMap();
        }
        if ($target_php_version >= 70200) {
            return $php72_map;
        }
        static $php71_map = [];
        if (!$php71_map) {
            $php71_map = self::computePHP71FunctionSignatureMap($php72_map);
        }
        if ($target_php_version >= 70100) {
            return $php71_map;
        }
        static $php70_map = [];
        if (!$php70_map) {
            $php70_map = self::computePHP70FunctionSignatureMap($php71_map);
        }
        return $php70_map;
    }

    private static function computeLatestFunctionSignatureMap() : array
    {
        $map = [];
        $map_raw = require(__DIR__.'/Internal/FunctionSignatureMap.php');
        foreach ($map_raw as $key => $value) {
            $map[\strtolower($key)] = $value;
        }
        return $map;
    }

    /**
     * @param array<string,array<int|string,string>> $php72_map
     * @return array<string,array<int|string,string>>
     */
    private static function computePHP71FunctionSignatureMap(array $php72_map) : array
    {
        $delta_raw = require(__DIR__.'/Internal/FunctionSignatureMap_php72_delta.php');
        return self::applyDeltaToGetOlderSignatures($php72_map, $delta_raw);
    }

    /**
     * @param array<string,array<int|string,string>> $php71_map
     * @return array<string,array<int|string,string>>
     */
    private static function computePHP70FunctionSignatureMap(array $php71_map) : array
    {
        $delta_raw = require(__DIR__.'/Internal/FunctionSignatureMap_php71_delta.php');
        return self::applyDeltaToGetOlderSignatures($php71_map, $delta_raw);
    }

    /**
     * @param array<string,array<int|string,string>> $newer_map
     * @param array{new:array<string,array<int|string,string>>,old:array<string,array<int|string,string>>} $delta
     * @return array<string,array<int|string,string>>
     */
    private static function applyDeltaToGetOlderSignatures(array $newer_map, array $delta) : array
    {
        foreach ($delta['new'] as $key => $unused_signature) {
            // Would also unset alternates, but that step isn't necessary yet.
            unset($newer_map[\strtolower($key)]);
        }
        foreach ($delta['old'] as $key => $signature) {
            // Would also unset alternates, but that step isn't necessary yet.
            $newer_map[\strtolower($key)] = $signature;
        }

        // Return the newer map after modifying it to become the older map.
        return $newer_map;
    }

    /**
     * @return UnionType - A normalized version of this union type (May or may not be the same object, if no modifications were made)
     *
     * The following normalization rules apply
     *
     * 1. If one of the types is null or nullable, convert all types to nullable and remove "null" from the union type
     * 2. If both "true" and "false" (possibly nullable) coexist, or either coexists with "bool" (possibly nullable),
     *    then remove "true" and "false"
     */
    public function asNormalizedTypes() : UnionType
    {
        $type_set = $this->type_set;
        if (count($type_set) <= 1) {
            // Optimization: can't simplify if there's only one type
            return $this;
        }
        $flags = 0;
        foreach ($type_set as $type) {
            $flags |= $type->getNormalizationFlags();
        }
        if ($flags === 0) {
            // Optimization: nothing to do if no types are null/nullable or booleans
            return $this;
        }
        return self::asNormalizedTypesInner($type_set, $flags);
    }

    /**
     * @param Type[] $type_set
     * @param int $flags non-zero
     */
    public static function asNormalizedTypesInner(array $type_set, int $flags) : UnionType
    {
        $nullable = ($flags & Type::_bit_nullable) !== 0;
        $builder = new UnionTypeBuilder($type_set);
        if ($nullable) {
            if (\count($type_set) > 0) {
                foreach ($type_set as $type) {
                    if (!$type->getIsNullable()) {
                        $builder->removeType($type);
                        $builder->addType($type->withIsNullable(true));
                    }
                }
                static $nullable_type = null;
                if ($nullable_type === null) {
                    $nullable_type = NullType::instance(false);
                }
                $builder->removeType($nullable_type);
            }
        }

        // If this contains both true and false types, filter out both and add "bool" (or "?bool" for nullable)
        if (($flags & Type::_bit_bool_combination) === Type::_bit_bool_combination) {
            if ($nullable) {
                self::convertToTypeSetWithNormalizedNullableBools($builder);
            } else {
                self::convertToTypeSetWithNormalizedNonNullableBools($builder);
            }
        }
        return $builder->getUnionType();
    }

    /**
     * @param UnionType[] $union_types
     * @return UnionType union of these UnionTypes
     */
    public static function merge(array $union_types) : UnionType
    {
        $n = \count($union_types);
        if ($n < 2) {
            return \reset($union_types) ?: UnionType::$empty_instance;
        }
        $new_type_set = [];
        foreach ($union_types as $type) {
            $type_set = $type->type_set;
            if (\count($type_set) === 0) {
                continue;
            }
            if (\count($new_type_set) === 0) {
                $new_type_set = $type_set;
                continue;
            }
            foreach ($type_set as $type) {
                if (!\in_array($type, $new_type_set, true)) {
                    $new_type_set[] = $type;
                }
            }
        }
        return UnionType::ofUniqueTypes($new_type_set);
    }

    /**
     * Must be called after converting nullable to non-nullable.
     * Removes false|true types and adds bool
     *
     * @param UnionTypeBuilder $builder (Containing only non-nullable values)
     * @return void
     * @var int $bool_id
     */
    private static function convertToTypeSetWithNormalizedNonNullableBools(UnionTypeBuilder $builder)
    {
        static $true_type = null;
        static $false_type = null;
        static $bool_type = null;
        if ($bool_type === null) {
            $true_type = TrueType::instance(false);
            $false_type = FalseType::instance(false);
            $bool_type = BoolType::instance(false);
        }
        if (!$builder->isEmpty()) {
            $builder->removeType($true_type);
            $builder->removeType($false_type);
        }

        $builder->addType($bool_type);
    }

    /**
     * Must be called after converting all types to null.
     * Removes ?false|?true types and adds ?bool
     *
     * @param UnionTypeBuilder $builder (Containing only non-nullable values)
     */
    private static function convertToTypeSetWithNormalizedNullableBools(UnionTypeBuilder $builder)
    {
        static $true_type = null;
        static $false_type = null;
        static $bool_type = null;
        if ($bool_type === null) {
            $true_type = TrueType::instance(true);
            $false_type = FalseType::instance(true);
            $bool_type = BoolType::instance(true);
        }
        if (!$builder->isEmpty()) {
            $builder->removeType($true_type);
            $builder->removeType($false_type);
        }

        $builder->addType($bool_type);
    }

    /**
     * Generates a variable length string identifier that uniquely identifies the Type instances in this UnionType.
     * int|string will generate the same id as string|int.
     */
    public function generateUniqueId() : string
    {
        /** @var array<int,int> $ids */
        $ids = [];
        foreach ($this->type_set as $type) {
            $ids[] = \spl_object_id($type);
        }
        // Sort the unique identifiers of Type instances so that int|string generates the same id as string|int
        \sort($ids);
        return \implode(',', $ids);
    }

    public function hasTopLevelArrayShapeTypeInstances() : bool
    {
        foreach ($this->type_set as $type) {
            if ($type instanceof ArrayShapeType) {
                return true;
            }
        }
        return false;
    }

    public function hasTopLevelNonArrayShapeTypeInstances() : bool
    {
        foreach ($this->type_set as $type) {
            if (!($type instanceof ArrayShapeType)) {
                return true;
            }
        }
        return false;
    }

    public function hasArrayShapeTypeInstances() : bool
    {
        foreach ($this->type_set as $type) {
            if ($type->hasArrayShapeTypeInstances()) {
                return true;
            }
        }
        return false;
    }

    public function hasMixedType() : bool
    {
        foreach ($this->type_set as $type) {
            if ($type instanceof MixedType) {
                return true;
            }
        }
        return false;
    }

    public function withFlattenedArrayShapeTypeInstances() : UnionType
    {
        if (!$this->hasArrayShapeTypeInstances()) {
            return $this;
        }

        $result = new UnionTypeBuilder();
        foreach ($this->type_set as $type) {
            if ($type->hasArrayShapeTypeInstances()) {
                foreach ($type->withFlattenedArrayShapeTypeInstances() as $type_part) {
                    $result->addType($type_part);
                }
            } else {
                $result->addType($type);
            }
        }
        return $result->getUnionType();
    }

    public function shouldBeReplacedBySpecificTypes() : bool
    {
        if ($this->isEmpty()) {
            return true;
        }
        return $this->hasTypeMatchingCallback(function (Type $type) : bool {
            return $type->shouldBeReplacedBySpecificTypes();
        });
    }

    /**
     * @param int|string|float|bool $field_key
     */
    public function withoutArrayShapeField($field_key) : UnionType
    {
        $types = $this->type_set;
        foreach ($types as $i => $type) {
            if ($type instanceof ArrayShapeType) {
                $types[$i] = $type->withoutField($field_key);
            }
        }
        if ($types === $this->type_set) {
            return $this;
        }
        return UnionType::of($types);
    }

    /**
     * Base implementation. Overridden by AnnotatedUnionType.
     */
    public function withIsPossiblyUndefined(bool $is_possibly_undefined) : UnionType
    {
        if ($is_possibly_undefined === false) {
            return $this;
        }
        $result = new AnnotatedUnionType($this->getTypeSet(), true);
        $result->is_possibly_undefined = $is_possibly_undefined;
        return $result;
    }

    /**
     * Base implementation. Overridden by AnnotatedUnionType.
     * Used for fields of array shapes.
     *
     * This is distinct from null - The array shape offset potentially doesn't exist at all, which is different from existing and being null.
     */
    public function getIsPossiblyUndefined() : bool
    {
        return false;
    }

    // Assumes this was already expanded
    public function hasClassWithToStringMethod(CodeBase $code_base, Context $context) : bool
    {
        try {
            foreach ($this->asClassList($code_base, $context) as $clazz) {
                if ($clazz->hasMethodWithName($code_base, "__toString")) {
                    return true;
                }
            }
        } catch (CodeBaseException $e) {
            // Swallow "Cannot find class", go on to emit issue
        }
        return false;
    }
}

UnionType::init();
<?php
declare(strict_types=1);
namespace Phan\Language;

use Phan\Config;
use Phan\Language\Element\Variable;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\FQSEN\FullyQualifiedPropertyName;
use Phan\Language\Type\TemplateType;

abstract class Scope
{
    /**
     * @var Scope|null
     */
    protected $parent_scope = null;

    /**
     * @var FQSEN|null
     */
    protected $fqsen = null;

    /**
     * @var array<string,Variable>
     */
    protected $variable_map = [];

    /**
     * @var array<string,TemplateType>
     * A map from template type identifiers to the
     * TemplateType that parameterizes the generic class
     * in this scope.
     */
    private $template_type_map = [];

    /**
     * @param ?Scope $parent_scope
     * @param ?FQSEN $fqsen
     */
    public function __construct(
        Scope $parent_scope = null,
        FQSEN $fqsen = null
    ) {
        $this->parent_scope = $parent_scope;
        $this->fqsen = $fqsen;
    }

    /**
     * @return bool
     * True if this scope has a parent scope
     */
    public function hasParentScope() : bool
    {
        return $this->parent_scope !== null;
    }

    /**
     * @return Scope
     * Get the parent scope of this scope
     */
    public function getParentScope() : Scope
    {
        return $this->parent_scope;
    }

    /**
     * @return bool
     * True if this scope has an FQSEN
     * @suppress PhanUnreferencedPublicMethod
     */
    public function hasFQSEN() : bool
    {
        return $this->fqsen !== null;
    }

    /**
     *
     */
    public function getFQSEN()
    {
        return $this->fqsen;
    }

    /**
     * @return bool
     * True if we're in a class scope
     */
    public function isInClassScope() : bool
    {
        return $this->hasParentScope()
            ? $this->getParentScope()->isInClassScope() : false;
    }

    /**
     * @return FullyQualifiedClassName
     * Crawl the scope hierarchy to get a class FQSEN.
     */
    public function getClassFQSEN() : FullyQualifiedClassName
    {
        \assert(
            $this->hasParentScope(),
            "Cannot get class FQSEN on scope"
        );

        return $this->getParentScope()->getClassFQSEN();
    }

    /**
     * @return bool
     * True if we're in a property scope
     */
    public function isInPropertyScope() : bool
    {
        return $this->hasParentScope()
            ? $this->getParentScope()->isInPropertyScope() : false;
    }

    /**
     * @return FullyQualifiedPropertyName
     * Crawl the scope hierarchy to get a class FQSEN.
     */
    public function getPropertyFQSEN() : FullyQualifiedPropertyName
    {
        \assert(
            $this->hasParentScope(),
            "Cannot get class FQSEN on scope"
        );

        return $this->getParentScope()->getPropertyFQSEN();
    }

    /**
     * @return bool
     * True if we're in a method/function/closure scope
     */
    public function isInFunctionLikeScope() : bool
    {
        return $this->hasParentScope()
            ? $this->getParentScope()->isInFunctionLikeScope() : false;
    }

    /**
     * @return FullyQualifiedMethodName|FullyQualifiedFunctionName
     * Crawl the scope hierarchy to get a method FQSEN.
     */
    public function getFunctionLikeFQSEN()
    {
        \assert(
            $this->hasParentScope(),
            "Cannot get method/function/closure FQSEN on scope"
        );

        return $this->getParentScope()->getFunctionLikeFQSEN();
    }

    /**
     * @return bool
     * True if a variable with the given name is defined
     * within this scope
     */
    public function hasVariableWithName(string $name) : bool
    {
        return \array_key_exists($name, $this->variable_map);
    }

    /**
     * @return Variable
     */
    public function getVariableByName(string $name) : Variable
    {
        return $this->variable_map[$name];
    }

    /**
     * @return array<string,Variable>
     * A map from name to Variable in this scope
     */
    public function getVariableMap() : array
    {
        return $this->variable_map;
    }

    /**
     * @param Variable $variable
     * A variable to add to the local scope
     *
     * @return Scope
     */
    public function withVariable(Variable $variable) : Scope
    {
        $scope = clone($this);
        $scope->addVariable($variable);
        return $scope;
    }

    /**
     * @param string $variable_name
     * The name of a variable to unset in the local scope
     *
     * @return Scope
     *
     * TODO: Make this work properly and merge properly when the variable is in a branch
     *
     * @suppress PhanUnreferencedPublicMethod unused, but adding to be consistent with `withVariable`
     */
    public function withUnsetVariable(string $variable_name) : Scope
    {
        $scope = clone($this);
        $scope->unsetVariable($variable_name);
        return $scope;
    }

    /**
     * @param string $variable_name
     * The name of a variable to unset in the local scope
     *
     * @return void
     *
     * TODO: Make this work properly and merge properly when the variable is in a branch (BranchScope)
     */
    public function unsetVariable(string $variable_name)
    {
        unset($this->variable_map[$variable_name]);
    }

    /**
     * @return void
     */
    public function addVariable(Variable $variable)
    {
        // uncomment to debug issues with variadics
        /*
        if ($variable->isVariadic() && !$variable->isCloneOfVariadic()) {
            throw new \Error("Bad variable {$variable->getName()}\n");
        }
         */
        $this->variable_map[$variable->getName()] = $variable;
    }

    /**
     * @param Variable $variable
     * A variable to add to the set of global variables
     *
     * @return void
     */
    public function addGlobalVariable(Variable $variable)
    {
        \assert(
            $this->hasParentScope(),
            "No global scope available. This should not happen."
        );

        $this->getParentScope()->addGlobalVariable($variable);
    }

    /**
     * @return bool
     * True if a global variable with the given name exists
     */
    public function hasGlobalVariableWithName(string $name) : bool
    {
        \assert(
            $this->hasParentScope(),
            "No global scope available. This should not happen."
        );

        return $this->getParentScope()->hasGlobalVariableWithName(
            $name
        );
    }

    /**
     * @return Variable
     * The global variable with the given name
     */
    public function getGlobalVariableByName(string $name) : Variable
    {
        \assert(
            $this->hasParentScope(),
            "No global scope available. This should not happen."
        );

        return $this->getParentScope()->getGlobalVariableByName($name);
    }

    /**
     * @return bool
     * True if there are any template types parameterizing a
     * generic class in this scope.
     */
    public function hasAnyTemplateType() : bool
    {
        if (!Config::getValue('generic_types_enabled')) {
            return false;
        }

        return !empty($this->template_type_map)
            || ($this->hasParentScope() && $this->getParentScope()->hasAnyTemplateType());
    }

    /**
     * @return array<string,TemplateType>
     * The set of all template types parameterizing this generic
     * class
     */
    public function getTemplateTypeMap() : array
    {
        return \array_merge(
            $this->template_type_map,
            $this->hasParentScope()
                ? $this->getParentScope()->getTemplateTypeMap()
                : []
        );
    }

    /**
     * @return bool
     * True if the given template type identifier is defined within
     * this context
     */
    public function hasTemplateType(
        string $template_type_identifier
    ) : bool {

        return isset(
            $this->template_type_map[$template_type_identifier]
        ) || ($this->hasParentScope() ? $this->getParentScope()->hasTemplateType(
            $template_type_identifier
        ) : false);
    }

    /**
     * @param TemplateType $template_type
     * A template type parameterizing the generic class in scope
     *
     * @return void
     */
    public function addTemplateType(TemplateType $template_type)
    {
        $this->template_type_map[$template_type->getName()] = $template_type;
    }

    /**
     * @param string $template_type_identifier
     * The identifier for a generic type
     *
     * @return TemplateType
     * A TemplateType parameterizing the generic class in scope
     */
    public function getTemplateType(
        string $template_type_identifier
    ) : TemplateType {

        \assert(
            $this->hasTemplateType($template_type_identifier),
            "Cannot get template type with identifier $template_type_identifier"
        );

        return $this->template_type_map[$template_type_identifier]
            ?? $this->getParentScope()->getTemplateType(
                $template_type_identifier
            );
    }

    /**
     * @return string
     * A string representation of this scope
     */
    public function __toString() : string
    {
        return $this->getFQSEN() . "\t" . implode(',', $this->getVariableMap());
    }
}
<?php declare(strict_types=1);
namespace Phan\Language;

/**
 * A Fully-Qualified Name
 */
interface FQSEN
{

    /**
     * @param string $fully_qualified_string
     * An FQSEN string like '\Namespace\Class::method' or
     * 'Class' or 'Class::method'.
     *
     * @return static
     */
    public static function fromFullyQualifiedString(
        string $fully_qualified_string
    );

    /**
     * @param string $fqsen_string
     * An FQSEN string like '\Namespace\Class::method' or
     * 'Class' or 'Class::method'.
     *
     * @param Context $context
     * The context in which the FQSEN string was found
     *
     * @return static
     */
    public static function fromStringInContext(
        string $fqsen_string,
        Context $context
    );

    /**
     * @return string
     * The class associated with this FQSEN or
     * null if not defined
     */
    public function getName() : string;

    /**
     * @return string
     * The canonical representation of the name of the object. Functions
     * and Methods, for instance, lowercase their names.
     */
    public static function canonicalName(string $name) : string;

    /**
     * @return static
     * Get the canonical (non-alternate) FQSEN associated
     * with this FQSEN
     */
    public function getCanonicalFQSEN();

    /**
     * @return string
     * A string representation of this fully-qualified
     * structural element name.
     */
    public function __toString() : string;
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\FileRef;
use Phan\Language\UnionType;

/**
 * Any PHP structural element that also has a type and is
 * addressable such as a class, method, closure, property,
 * constant, variable, ...
 */
abstract class TypedElement implements TypedElementInterface
{
    /**
     * @var string
     * The name of the typed structural element
     */
    private $name;

    /**
     * @var UnionType|null
     * A set of types satisfied by this typed structural
     * element.
     */
    private $type = null;

    /**
     * @var int
     * The flags property contains node specific flags. It is
     * always defined, but for most nodes it is always zero.
     * ast\kind_uses_flags() can be used to determine whether
     * a certain kind has a meaningful flags value.
     */
    private $flags = 0;

    /**
     * @var int
     * The Phan flags property contains node specific flags that
     * are internal to Phan.
     */
    private $phan_flags = 0;

    /**
     * @var Context|null
     * The context in which the structural element lives
     */
    private $context = null;

    /**
     * @var array<string,int>
     * A set of issues types to be suppressed.
     * Maps to the number of times an issue type was suppressed.
     */
    private $suppress_issue_list = [];

    /**
     * @param Context $context
     * The context in which the structural element lives
     *
     * @param string $name
     * The name of the typed structural element
     *
     * @param UnionType $type
     * A '|' delimited set of types satisfied by this
     * typed structural element.
     *
     * @param int $flags
     * The flags property contains node specific flags. It is
     * always defined, but for most nodes it is always zero.
     * ast\kind_uses_flags() can be used to determine whether
     * a certain kind has a meaningful flags value.
     */
    public function __construct(
        Context $context,
        string $name,
        UnionType $type,
        int $flags
    ) {
        $this->context = clone($context);
        $this->name = $name;
        $this->type = $type;
        $this->flags = $flags;
        $this->setIsPHPInternal($context->isPHPInternal());
    }

    /**
     * After a clone is called on this object, clone our
     * type and fqsen so that they survive copies intact
     *
     * @return null
     */
    public function __clone()
    {
        $this->context = $this->context
            ? clone($this->context)
            : $this->context;
    }

    /**
     * @return string
     * The (not fully-qualified) name of this element.
     */
    public function getName() : string
    {
        return $this->name;
    }

    /**
     * @return UnionType
     * Get the type of this structural element
     */
    public function getUnionType() : UnionType
    {
        return $this->type;
    }

    /**
     * @param UnionType $type
     * Set the type of this element
     *
     * TODO: A helper addUnionType(), accounting for variadic
     *
     * @return void
     */
    public function setUnionType(UnionType $type)
    {
        $this->type = $type;
    }

    /**
     * Variables can't be variadic. This is the same as getUnionType for
     * variables, but not necessarily for subclasses. Method will return
     * the element type (such as `DateTime`) for variadic parameters.
     */
    public function getNonVariadicUnionType() : UnionType
    {
        return $this->getUnionType();
    }

    /**
     * @return int
     */
    public function getFlags() : int
    {
        return $this->flags;
    }

    /**
     * @param int $flag
     * The flag we'd like to get the state for
     *
     * @return bool
     * True if all bits in the flag are enabled in the bit
     * vector, else false.
     */
    public function getFlagsHasState(int $flag) : bool
    {
        return ($this->flags & $flag) === $flag;
    }


    /**
     * @param int $flags
     *
     * @return void
     */
    public function setFlags(int $flags)
    {
        $this->flags = $flags;
    }

    /**
     * @return int
     */
    public function getPhanFlags() : int
    {
        return $this->phan_flags;
    }

    /**
     * @param int $flag
     * The flag we'd like to get the state for
     *
     * @return bool
     * True if all bits in the flag are enabled in the bit
     * vector, else false.
     */
    public function getPhanFlagsHasState(int $flag) : bool
    {
        return ($this->phan_flags & $flag) === $flag;
    }

    /**
     * @param int $phan_flags
     *
     * @return void
     */
    public function setPhanFlags(int $phan_flags)
    {
        $this->phan_flags = $phan_flags;
    }

    /**
     * @param int $bits combination of flags from Flags::* constants to enable
     *
     * @return void
     */
    public function enablePhanFlagBits(int $bits)
    {
        $this->phan_flags |= $bits;
    }

    /**
     * @param int $bits combination of flags from Flags::* constants to disable
     *
     * @return void
     * @suppress PhanUnreferencedPublicMethod keeping this for consistency
     */
    public function disablePhanFlagBits(int $bits)
    {
        $this->phan_flags &= (~$bits);
    }

    /**
     * @return Context
     * The context in which this structural element exists
     */
    public function getContext() : Context
    {
        return $this->context;
    }

    /**
     * @return FileRef
     * A reference to where this element was found
     */
    public function getFileRef() : FileRef
    {
        // TODO: Kill the context and make this a pure
        //       FileRef.
        return $this->context;
    }

    /**
     * @return bool
     * True if this element is marked as deprecated
     */
    public function isDeprecated() : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_DEPRECATED);
    }

    /**
     * @param bool $is_deprecated
     * Set this element as deprecated
     *
     * @return void
     */
    public function setIsDeprecated(bool $is_deprecated)
    {
        $this->setPhanFlags(Flags::bitVectorWithState(
            $this->getPhanFlags(),
            Flags::IS_DEPRECATED,
            $is_deprecated
        ));
    }

    /**
     * @param string[] $suppress_issue_list
     * Set the set of issue names to suppress
     *
     * @return void
     */
    public function setSuppressIssueList(array $suppress_issue_list)
    {
        $this->suppress_issue_list = [];
        foreach ($suppress_issue_list as $issue_name) {
            $this->suppress_issue_list[$issue_name] = 0;
        }
    }

    /**
     * @return array<string,int>
     */
    public function getSuppressIssueList() : array
    {
        return $this->suppress_issue_list ?: [];
    }

    /**
     * Increments the number of times $issue_name was suppressed.
     * @return void
     */
    public function incrementSuppressIssueCount(string $issue_name)
    {
        ++$this->suppress_issue_list[$issue_name];
    }

    /**
     * return bool
     * True if this element would like to suppress the given
     * issue name
     */
    public function hasSuppressIssue(string $issue_name) : bool
    {
        return isset($this->suppress_issue_list[$issue_name]);
    }

    /**
     * @return bool
     * True if this was an internal PHP object
     */
    public function isPHPInternal() : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_PHP_INTERNAL);
    }

    /**
     * @return void
     */
    private function setIsPHPInternal(bool $is_internal)
    {
        $this->setPhanFlags(
            Flags::bitVectorWithState(
                $this->getPhanFlags(),
                Flags::IS_PHP_INTERNAL,
                $is_internal
            )
        );
    }

    /**
     * This method must be called before analysis
     * begins.
     *
     * @return void
     */
    public function hydrate(CodeBase $unused_code_base)
    {
        // Do nothing unless overridden
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

/**
 * This contains info for the source method of a trait alias.
 */
class TraitAliasSource
{
    /**
     * @var int line number where this trait method alias was created
     * (in the class using traits).
     */
    private $alias_lineno;

    /**
     * @var string source method name
     */
    private $source_method_name;

    /**
     * @var int the overridden visibility modifier, or 0 if the visibility didn't change
     */
    private $alias_visibility_flags;

    public function __construct(string $source_method_name, int $alias_lineno, int $alias_visibility_flags)
    {
        $this->source_method_name = $source_method_name;
        $this->alias_lineno = $alias_lineno;
        $this->alias_visibility_flags = $alias_visibility_flags;
    }

    public function getSourceMethodName() : string
    {
        return $this->source_method_name;
    }

    public function getAliasLineno() : int
    {
        return $this->alias_lineno;
    }

    public function getAliasVisibilityFlags() : int
    {
        return $this->alias_visibility_flags;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\FileRef;
use Phan\Language\UnionType;

/**
 * Any PHP structural element that also has a type and is
 * does not store a reference to its context (such as a variable).
 */
abstract class UnaddressableTypedElement
{
    /**
     * @var FileRef
     */
    private $file_ref;

    /**
     * @var string
     * The name of the typed structural element
     */
    private $name;

    /**
     * @var UnionType|null
     * A set of types satisfied by this typed structural
     * element.
     * Prefer using getUnionType() - getUnionType() is overridden by VariadicParameter
     */
    protected $type = null;

    /**
     * @var int
     * The flags property contains node specific flags. It is
     * always defined, but for most nodes it is always zero.
     * ast\kind_uses_flags() can be used to determine whether
     * a certain kind has a meaningful flags value.
     */
    private $flags = 0;

    /**
     * @var int
     * The Phan flags property contains node specific flags that
     * are internal to Phan.
     */
    private $phan_flags = 0;

    /**
     * @param FileRef $file_ref
     * The Context or FileRef in which the structural element lives
     * (Will be converted to FileRef, to avoid creating a reference
     * cycle that can't be garbage collected)
     *
     * @param string $name
     * The name of the typed structural element
     *
     * @param UnionType $type
     * A '|' delimited set of types satisfied by this
     * typed structural element.
     *
     * @param int $flags
     * The flags property contains node specific flags. It is
     * always defined, but for most nodes it is always zero.
     * ast\kind_uses_flags() can be used to determine whether
     * a certain kind has a meaningful flags value.
     */
    public function __construct(
        FileRef $file_ref,
        string $name,
        UnionType $type,
        int $flags
    ) {
        $this->file_ref = FileRef::copyFileRef($file_ref);
        $this->name = $name;
        $this->type = $type;
        $this->flags = $flags;
    }

    /**
     * @return string
     * The (not fully-qualified) name of this element.
     */
    public function getName() : string
    {
        return $this->name;
    }

    /**
     * @return UnionType
     * Get the type of this structural element
     */
    public function getUnionType() : UnionType
    {
        return $this->type;
    }

    /**
     * @param UnionType $type
     * Set the type of this element
     *
     * @return void
     */
    public function setUnionType(UnionType $type)
    {
        $this->type = $type;
    }

    /**
     * @return void
     */
    protected function convertToNullable()
    {
        // Avoid a redundant clone of nonNullableClone()
        $type = $this->type;
        if ($type->isEmpty() || $type->containsNullable()) {
            return;
        }
        $this->type = $type->nullableClone();
    }

    /**
     * @return int
     */
    public function getFlags() : int
    {
        return $this->flags;
    }

    /**
     * @param int $flag
     * The flag we'd like to get the state for
     *
     * @return bool
     * True if all bits in the flag are enabled in the bit
     * vector, else false.
     */
    public function getFlagsHasState(int $flag) : bool
    {
        return ($this->flags & $flag) === $flag;
    }


    /**
     * @param int $flags
     *
     * @return void
     * @suppress PhanUnreferencedPublicMethod unused, other modifiers are used by Phan right now
     */
    public function setFlags(int $flags)
    {
        $this->flags = $flags;
    }

    /**
     * @return int
     */
    public function getPhanFlags() : int
    {
        return $this->phan_flags;
    }

    /**
     * @param int $flag
     * The flag we'd like to get the state for
     *
     * @return bool
     * True if all bits in the flag are enabled in the bit
     * vector, else false.
     */
    public function getPhanFlagsHasState(int $flag) : bool
    {
        return ($this->phan_flags & $flag) === $flag;
    }

    /**
     * @param int $phan_flags
     *
     * @return void
     * @suppress PhanUnreferencedPublicMethod potentially used in the future
     */
    public function setPhanFlags(int $phan_flags)
    {
        $this->phan_flags = $phan_flags;
    }

    /**
     * @return void
     */
    public function enablePhanFlagBits(int $new_bits)
    {
        $this->phan_flags |= $new_bits;
    }

    /**
     * @return void
     */
    public function disablePhanFlagBits(int $new_bits)
    {
        $this->phan_flags &= (~$new_bits);
    }

    /**
     * @return FileRef
     * A reference to where this element was found
     */
    public function getFileRef() : FileRef
    {
        return $this->file_ref;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Analysis\AbstractMethodAnalyzer;
use Phan\Analysis\CompositionAnalyzer;
use Phan\Analysis\DuplicateClassAnalyzer;
use Phan\Analysis\ClassInheritanceAnalyzer;
use Phan\Analysis\ParentConstructorCalledAnalyzer;
use Phan\Analysis\PropertyTypesAnalyzer;
use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\CodeBaseException;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\FQSEN;
use Phan\Language\FQSEN\FullyQualifiedClassConstantName;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\FQSEN\FullyQualifiedPropertyName;
use Phan\Language\Scope\ClassScope;
use Phan\Language\Scope\GlobalScope;
use Phan\Language\Type;
use Phan\Language\Type\IterableType;
use Phan\Language\Type\StringType;
use Phan\Language\Type\TemplateType;
use Phan\Language\UnionType;
use Phan\Library\None;
use Phan\Library\Option;
use Phan\Library\Some;
use Phan\Plugin\ConfigPluginSet;

use Closure;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class Clazz extends AddressableElement
{
    use \Phan\Memoize;
    use ClosedScopeElement;

    /**
     * @var Type|null
     * The type of the parent of this class if it extends
     * anything, else null.
     */
    private $parent_type = null;

    /**
     * @var array<int,FullyQualifiedClassName>
     * A possibly empty list of interfaces implemented
     * by this class
     */
    private $interface_fqsen_list = [];

    /**
     * @var array<int,FullyQualifiedClassName>
     * A possibly empty list of traits used by this class
     */
    private $trait_fqsen_list = [];

    /**
     * @var array<int,TraitAdaptations>
     * Maps lowercase fqsen of a method to the trait names which are hidden
     * and the trait aliasing info
     */
    private $trait_adaptations_map = [];

    /**
     * @var bool - hydrate() will check for this to avoid prematurely hydrating while looking for values of class constants.
     */
    private $did_finish_parsing = true;

    /**
     * @var ?UnionType for Type->asExpandedTypes()
     *
     * TODO: This won't reverse in daemon mode?
     */
    private $additional_union_types = null;

    /**
     * @param Context $context
     * The context in which the structural element lives
     *
     * @param string $name
     * The name of the typed structural element
     *
     * @param UnionType $type
     * A '|' delimited set of types satisfied by this
     * typed structural element.
     *
     * @param int $flags
     * The flags property contains node specific flags. It is
     * always defined, but for most nodes it is always zero.
     * ast\kind_uses_flags() can be used to determine whether
     * a certain kind has a meaningful flags value.
     *
     * @param FullyQualifiedClassName $fqsen
     * A fully qualified name for this class
     *
     * @param Type|null $parent_type
     * @param array<int,FullyQualifiedClassName> $interface_fqsen_list
     * @param array<int,FullyQualifiedClassName> $trait_fqsen_list
     */
    public function __construct(
        Context $context,
        string $name,
        UnionType $type,
        int $flags,
        FullyQualifiedClassName $fqsen,
        Type $parent_type = null,
        array $interface_fqsen_list = [],
        array $trait_fqsen_list = []
    ) {
        parent::__construct(
            $context,
            $name,
            $type,
            $flags,
            $fqsen
        );

        $this->parent_type = $parent_type;
        $this->interface_fqsen_list = $interface_fqsen_list;
        $this->trait_fqsen_list = $trait_fqsen_list;

        $this->setInternalScope(new ClassScope(
            $context->getScope(),
            $fqsen
        ));
    }

    /**
     * @param CodeBase $code_base
     * A reference to the entire code base in which this
     * context exists
     *
     * @param \ReflectionClass $class
     * A reflection class representing a builtin class.
     *
     * @return Clazz
     * A Class structural element representing the given named
     * builtin.
     */
    public static function fromReflectionClass(
        CodeBase $code_base,
        \ReflectionClass $class
    ) : Clazz {
        // Build a set of flags based on the constitution
        // of the built-in class
        $flags = 0;
        if ($class->isFinal()) {
            $flags = \ast\flags\CLASS_FINAL;
        } elseif ($class->isInterface()) {
            $flags = \ast\flags\CLASS_INTERFACE;
        } elseif ($class->isTrait()) {
            $flags = \ast\flags\CLASS_TRAIT;
        }
        if ($class->isAbstract()) {
            $flags |= \ast\flags\CLASS_ABSTRACT;
        }

        $context = new Context;

        $class_name = $class->getName();
        $class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString($class_name);

        // Build a base class element
        $clazz = new Clazz(
            $context,
            $class_name,
            UnionType::fromFullyQualifiedString('\\' . $class_name),
            $flags,
            $class_fqsen
        );

        // If this class has a parent class, add it to the
        // class info
        if (($parent_class = $class->getParentClass())) {
            $parent_class_fqsen =
                FullyQualifiedClassName::fromFullyQualifiedString(
                    '\\' . $parent_class->getName()
                );

            $parent_type = $parent_class_fqsen->asType();

            $clazz->setParentType($parent_type);
        }

        if ($class_name === "Traversable") {
            // Make sure that canCastToExpandedUnionType() works as expected for Traversable and its subclasses
            $clazz->addAdditionalType(IterableType::instance(false));
        }

        // Note: If there are multiple calls to Clazz->addProperty(),
        // the UnionType from the first one will be used, subsequent calls to addProperty()
        // will have no effect.
        // As a result, we set the types from phan's documented internal property types first,
        // preferring them over the default values (which may be null, etc.).
        foreach (UnionType::internalPropertyMapForClassName(
            $clazz->getName()
        ) as $property_name => $property_type_string) {
            // An asterisk indicates that the class supports
            // dynamic properties
            if ($property_name === '*') {
                $clazz->setHasDynamicProperties(true);
                continue;
            }

            $property_context = $context->withScope(
                new ClassScope(new GlobalScope, $clazz->getFQSEN())
            );

            $property_type =
                UnionType::fromStringInContext(
                    $property_type_string,
                    new Context,
                    Type::FROM_TYPE
                );

            $property_fqsen = FullyQualifiedPropertyName::make(
                $clazz->getFQSEN(),
                $property_name
            );

            $property = new Property(
                $property_context,
                $property_name,
                $property_type,
                0,
                $property_fqsen
            );

            $clazz->addProperty($code_base, $property, new None);
        }

        // n.b.: public properties on internal classes don't get
        //       listed via reflection until they're set unless
        //       they have a default value. Therefore, we don't
        //       bother iterating over `$class->getProperties()`
        //       `$class->getStaticProperties()`.

        foreach ($class->getDefaultProperties() as $name => $value) {
            $property_context = $context->withScope(
                new ClassScope(new GlobalScope, $clazz->getFQSEN())
            );

            $property_fqsen = FullyQualifiedPropertyName::make(
                $clazz->getFQSEN(),
                $name
            );

            $property = new Property(
                $property_context,
                $name,
                Type::fromObject($value)->asUnionType(),
                0,
                $property_fqsen
            );

            $clazz->addProperty($code_base, $property, new None);
        }

        foreach ($class->getInterfaceNames() as $name) {
            $clazz->addInterfaceClassFQSEN(
                FullyQualifiedClassName::fromFullyQualifiedString(
                    '\\' . $name
                )
            );
        }

        foreach ($class->getTraitNames() as $name) {
            // TODO: optionally, support getTraitAliases()? This is low importance for internal PHP modules,
            // it would be uncommon to see traits in internal PHP modules.
            $clazz->addTraitFQSEN(
                FullyQualifiedClassName::fromFullyQualifiedString(
                    '\\' . $name
                )
            );
        }

        foreach ($class->getConstants() as $name => $value) {
            $constant_fqsen = FullyQualifiedClassConstantName::make(
                $clazz->getFQSEN(),
                $name
            );

            $constant = new ClassConstant(
                $context,
                $name,
                Type::fromObject($value)->asUnionType(),
                0,
                $constant_fqsen
            );
            $constant->setNodeForValue($value);

            $clazz->addConstant($code_base, $constant);
        }

        foreach ($class->getMethods() as $reflection_method) {
            $method_context = $context->withScope(
                new ClassScope(new GlobalScope, $clazz->getFQSEN())
            );

            $method_list =
                FunctionFactory::methodListFromReflectionClassAndMethod(
                    $method_context,
                    $class,
                    $reflection_method
                );

            foreach ($method_list as $method) {
                $clazz->addMethod($code_base, $method, new None);
            }
        }

        return $clazz;
    }

    /**
     * @param Type|null $parent_type
     * The type of the parent (extended) class of this class.
     *
     * @return void
     */
    public function setParentType(Type $parent_type = null)
    {
        if ($this->getInternalScope()->hasAnyTemplateType()) {
            // Get a reference to the local list of templated
            // types. We'll use this to map templated types on the
            // parent to locally templated types.
            $template_type_map =
                $this->getInternalScope()->getTemplateTypeMap();

            // Figure out if the given parent type contains any template
            // types.
            $contains_templated_type = false;
            foreach ($parent_type->getTemplateParameterTypeList() as $union_type) {
                foreach ($union_type->getTypeSet() as $type) {
                    if (isset($template_type_map[$type->getName()])) {
                        $contains_templated_type = true;
                        break 2;
                    }
                }
            }

            // If necessary, map the template parameter type list through the
            // local list of templated types.
            if ($contains_templated_type) {
                $parent_type = Type::fromType(
                    $parent_type,
                    \array_map(function (UnionType $union_type) use ($template_type_map) : UnionType {
                        return UnionType::of(
                            \array_map(function (Type $type) use ($template_type_map) : Type {
                                return $template_type_map[$type->getName()] ?? $type;
                            }, $union_type->getTypeSet())
                        );
                    }, $parent_type->getTemplateParameterTypeList())
                );
            }
        }

        $this->parent_type = $parent_type;

        // Add the parent to the union type of this class
        $this->addAdditionalType($parent_type);
    }

    /**
     * @return bool
     * True if this class has a parent class
     */
    public function hasParentType() : bool
    {
        return !empty($this->parent_type);
    }

    /**
     * @return Option<Type>
     * If a parent type is defined, get Some<Type>, else None.
     */
    public function getParentTypeOption()
    {
        $parent_type = $this->parent_type;
        if ($parent_type !== null) {
            return new Some($parent_type);
        }

        return new None;
    }

    /**
     * @return FullyQualifiedClassName
     * The parent class of this class if one exists
     *
     * @throws \Exception
     * An exception is thrown if this class has no parent
     */
    public function getParentClassFQSEN() : FullyQualifiedClassName
    {
        $parent_type_option = $this->getParentTypeOption();

        if (!$parent_type_option->isDefined()) {
            throw new \Exception("Class $this has no parent");
        }

        return $parent_type_option->get()->asFQSEN();
    }

    /**
     * @return Clazz
     * The parent class of this class if defined
     *
     * @throws \Exception
     * An exception is thrown if this class has no parent
     */
    public function getParentClass(CodeBase $code_base) : Clazz
    {
        $parent_type_option = $this->getParentTypeOption();

        if (!$parent_type_option->isDefined()) {
            throw new \Exception("Class $this has no parent");
        }

        $parent_fqsen = $parent_type_option->get()->asFQSEN();
        \assert($parent_fqsen instanceof FullyQualifiedClassName);

        // invoking hasClassWithFQSEN also has the side effect of lazily loading the parent class definition.
        if (!$code_base->hasClassWithFQSEN($parent_fqsen)) {
            throw new \Exception("Failed to load parent Class $parent_fqsen of Class $this");
        }

        return $code_base->getClassByFQSEN(
            $parent_fqsen
        );
    }

    /**
     * @return Clazz
     * The parent class of this class if defined
     *
     * @throws \Exception
     * An exception is thrown if this class has no parent
     */
    private function getParentClassWithoutHydrating(CodeBase $code_base) : Clazz
    {
        $parent_type_option = $this->getParentTypeOption();

        if (!$parent_type_option->isDefined()) {
            throw new \Exception("Class $this has no parent");
        }

        $parent_fqsen = $parent_type_option->get()->asFQSEN();
        \assert($parent_fqsen instanceof FullyQualifiedClassName);

        // invoking hasClassWithFQSEN also has the side effect of lazily loading the parent class definition.
        if (!$code_base->hasClassWithFQSEN($parent_fqsen)) {
            throw new \Exception("Failed to load parent Class $parent_fqsen of Class $this");
        }

        return $code_base->getClassByFQSENWithoutHydrating(
            $parent_fqsen
        );
    }

    public function isSubclassOf(CodeBase $code_base, Clazz $other) : bool
    {
        if (!$this->hasParentType()) {
            return false;
        }

        if (!$code_base->hasClassWithFQSEN(
            $this->getParentClassFQSEN()
        )) {
            // Let this emit an issue elsewhere for the
            // parent not existing
            return false;
        }

        // Get the parent class
        $parent = $this->getParentClass($code_base);

        if ($parent === $other) {
            return true;
        }

        return $parent->isSubclassOf($code_base, $other);
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return int
     * This class's depth in the class hierarchy
     */
    public function getHierarchyDepth(CodeBase $code_base) : int
    {
        if (!$this->hasParentType()) {
            return 0;
        }

        if (!$code_base->hasClassWithFQSEN(
            $this->getParentClassFQSEN()
        )) {
            // Let this emit an issue elsewhere for the
            // parent not existing
            return 0;
        }

        // Get the parent class
        $parent = $this->getParentClass($code_base);

        // Prevent infinite loops
        if ($parent === $this) {
            return 0;
        }

        return (1 + $parent->getHierarchyDepth($code_base));
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return FullyQualifiedClassName
     * The FQSEN of the root class on this class's hierarchy
     */
    public function getHierarchyRootFQSEN(
        CodeBase $code_base
    ) : FullyQualifiedClassName {
        if (!$this->hasParentType()) {
            return $this->getFQSEN();
        }

        if (!$code_base->hasClassWithFQSEN(
            $this->getParentClassFQSEN()
        )) {
            // Let this emit an issue elsewhere for the
            // parent not existing
            return $this->getFQSEN();
        }

        // Get the parent class
        $parent = $this->getParentClass($code_base);

        // Prevent infinite loops
        if ($parent === $this) {
            return $this->getFQSEN();
        }

        return $parent->getHierarchyRootFQSEN($code_base);
    }

    /**
     * @param FQSEN $fqsen
     * Add the given FQSEN to the list of implemented
     * interfaces for this class
     *
     * @return void
     */
    public function addInterfaceClassFQSEN(FQSEN $fqsen)
    {
        $this->interface_fqsen_list[] = $fqsen;

        // Add the interface to the union type of this
        // class
        $this->addAdditionalType(
            Type::fromFullyQualifiedString($fqsen->__toString())
        );
    }

    /**
     * @return array<int,FullyQualifiedClassName>
     * Get the list of interfaces implemented by this class
     * @suppress PhanPartialTypeMismatchReturn
     */
    public function getInterfaceFQSENList() : array
    {
        return $this->interface_fqsen_list;
    }

    /**
     * Add a property to this class
     *
     * @param CodeBase $code_base
     * A reference to the code base in which the ancestor exists
     *
     * @param Property $property
     * The property to copy onto this class
     *
     * @param Option<Type>|None $type_option
     * A possibly defined type used to define template
     * parameter types when importing the property
     *
     * @param bool $from_trait
     *
     * @return void
     */
    public function addProperty(
        CodeBase $code_base,
        Property $property,
        $type_option,
        bool $from_trait = false
    ) {
        // Ignore properties we already have
        // TODO: warn about private properties in subclass overriding ancestor private property.
        $property_name = $property->getName();
        if ($this->hasPropertyWithName($code_base, $property_name)) {
            // original_property is the one that the class is using.
            // We added $property after that (so it likely in a base class, or a trait's property added after this property was added)
            // $overriding_property = $this->getPropertyMap($code_base)[$property_name];;
            // TODO: implement https://github.com/phan/phan/issues/615 in another PR, see below comment
            /**
            if ($overriding_property->isStatic() != $property->isStatic()) {
                if ($overriding_property->isStatic()) {
                    // emit warning about redefining non-static as static $overriding_property
                } else {
                    // emit warning about redefining static as
                }
            }
             */
            return;
        }

        $property_fqsen = FullyQualifiedPropertyName::make(
            $this->getFQSEN(),
            $property_name
        );

        // TODO: defer template properties until the analysis phase? They might not be parsed or resolved yet.
        $original_property_fqsen = $property->getFQSEN();
        if ($original_property_fqsen !== $property_fqsen) {
            $property = clone($property);
            $property->setFQSEN($property_fqsen);

            // Private properties of traits are accessible from the class that used that trait
            // (as well as from within the trait itself).
            // Also, for inheritance purposes, treat protected properties the same way.
            if ($from_trait && !$property->isPublic()) {
                $property->setDefiningFQSEN($property_fqsen);
            }

            try {
                // If we have a parent type defined, map the property's
                // type through it
                if ($type_option->isDefined()
                    && !$property->hasUnresolvedFutureUnionType()
                    && $property->getUnionType()->hasTemplateType()
                ) {
                    $property->setUnionType(
                        $property->getUnionType()->withTemplateParameterTypeMap(
                            $type_option->get()->getTemplateParameterTypeMap(
                                $code_base
                            )
                        )
                    );
                }
            } catch (IssueException $exception) {
                Issue::maybeEmitInstance(
                    $code_base,
                    $property->getContext(),
                    $exception->getIssueInstance()
                );
            }
        }

        $code_base->addProperty($property);
    }

    /**
     * @param array<string,\Phan\Language\Element\Comment\Parameter> $magic_property_map mapping from property name to property
     * @param CodeBase $code_base
     * @return bool whether or not we defined it.
     */
    public function setMagicPropertyMap(
        array $magic_property_map,
        CodeBase $code_base
    ) : bool {
        if (count($magic_property_map) === 0) {
            return true;  // Vacuously true.
        }
        $class_fqsen = $this->getFQSEN();
        $context = $this->getContext()->withScope(
            $this->getInternalScope()
        );
        foreach ($magic_property_map as $comment_parameter) {
            // $flags is the same as the flags for `public` and non-internal?
            // Or \ast\flags\MODIFIER_PUBLIC.
            $flags = 0;
            $property_name = $comment_parameter->getName();
            $property_fqsen = FullyQualifiedPropertyName::make(
                $class_fqsen,
                $property_name
            );
            $property = new Property(
                $context,
                $property_name,
                $comment_parameter->getUnionType(),
                $flags,
                $property_fqsen
            );

            $this->addProperty($code_base, $property, new None);
        }
        return true;
    }

    /**
     * @param array<string,\Phan\Language\Element\Comment\Method> $magic_method_map mapping from method name to this.
     * @param CodeBase $code_base
     * @return bool whether or not we defined it.
     */
    public function setMagicMethodMap(
        array $magic_method_map,
        CodeBase $code_base
    ) : bool {
        if (count($magic_method_map) === 0) {
            return true;  // Vacuously true.
        }
        $class_fqsen = $this->getFQSEN();
        $context = $this->getContext()->withScope(
            $this->getInternalScope()
        );
        foreach ($magic_method_map as $comment_method) {
            // $flags is the same as the flags for `public` and non-internal?
            // Or \ast\flags\MODIFIER_PUBLIC.
            $flags = \ast\flags\MODIFIER_PUBLIC;
            if ($comment_method->isStatic()) {
                $flags |= \ast\flags\MODIFIER_STATIC;
            }
            $method_name = $comment_method->getName();
            if ($this->hasMethodWithName($code_base, $method_name)) {
                // No point, and this would hurt inference accuracy.
                continue;
            }
            $method_fqsen = FullyQualifiedMethodName::make(
                $class_fqsen,
                $method_name
            );
            $real_parameter_list = \array_map(function (\Phan\Language\Element\Comment\Parameter $parameter) use ($context) : Parameter {
                return $parameter->asRealParameter($context);
            }, $comment_method->getParameterList());
            $method = new Method(
                $context,
                $method_name,
                $comment_method->getUnionType(),
                $flags,
                $method_fqsen,
                $real_parameter_list
            );

            $method->setRealParameterList($real_parameter_list);
            $method->setNumberOfRequiredParameters($comment_method->getNumberOfRequiredParameters());
            $method->setNumberOfOptionalParameters($comment_method->getNumberOfOptionalParameters());
            $method->setIsFromPHPDoc(true);

            $this->addMethod($code_base, $method, new None);
        }
        return true;
    }

    /**
     * @return bool
     */
    public function hasPropertyWithName(
        CodeBase $code_base,
        string $name
    ) : bool {
        return $code_base->hasPropertyWithFQSEN(
            FullyQualifiedPropertyName::make(
                $this->getFQSEN(),
                $name
            )
        );
    }

    public function getPropertyByName(
        CodeBase $code_base,
        string $name
    ) : Property {
        return $code_base->getPropertyByFQSEN(
            FullyQualifiedPropertyName::make(
                $this->getFQSEN(),
                $name
            )
        );
    }

    /**
     * Checks if a given property can be accessed by the class in the current Context
     * (if any)
     *
     * @param CodeBase $code_base
     * A reference to the entire code base in which the
     * property exists.
     *
     * @param Property $property
     * A property with the given name
     *
     * @param Context $context
     * The context of the caller requesting the property
     *
     * @return bool - is $property accessible from $context
     */
    private function isPropertyAccessible(
        CodeBase $code_base,
        Property $property,
        Context $context
    ) : bool {
        if ($property->isPublic()) {
            return true;
        }
        if (!$context->isInClassScope()) {
            return false;
        }
        // NOTE: This gets the **unexpanded** union type (Should be 1 class and no parent classes).
        $type_of_class_of_property = $property->getDefiningClassFQSEN()->asType();
        $accessing_class_type = $context->getClassFQSEN()->asType();

        if ($type_of_class_of_property === $accessing_class_type) {
            // Check for common case: Same class
            return true;
        }

        // We are in a class scope, and the property is either private or protected.
        if ($property->isPrivate()) {
            return $accessing_class_type->canCastToType(
                $type_of_class_of_property
            );
        } else {
            // If the definition of the property is protected,
            // then the subclasses of the defining class can access it.
            foreach ($accessing_class_type->asExpandedTypes($code_base)->getTypeSet() as $type) {
                if ($type->canCastToType($type_of_class_of_property)) {
                    return true;
                }
            }
            // and base classes of the defining class can access it
            foreach ($type_of_class_of_property->asExpandedTypes($code_base)->getTypeSet() as $type) {
                if ($type->canCastToType($accessing_class_type)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * @param CodeBase $code_base
     * A reference to the entire code base in which the
     * property exists.
     *
     * @param string $name
     * The name of the property
     *
     * @param Context $context
     * The context of the caller requesting the property
     *
     * @return Property
     * A property with the given name
     *
     * @throws IssueException
     * An exception may be thrown if the caller does not
     * have access to the given property from the given
     * context
     */
    public function getPropertyByNameInContext(
        CodeBase $code_base,
        string $name,
        Context $context,
        bool $is_static
    ) : Property {

        // Get the FQSEN of the property we're looking for
        $property_fqsen = FullyQualifiedPropertyName::make(
            $this->getFQSEN(),
            $name
        );

        $property = null;

        // Figure out if we have the property
        $has_property =
            $code_base->hasPropertyWithFQSEN($property_fqsen);

        // Figure out if the property is accessible
        $is_property_accessible = false;
        if ($has_property) {
            $property = $code_base->getPropertyByFQSEN(
                $property_fqsen
            );
            if ($is_static !== $property->isStatic()) {
                if ($is_static) {
                    throw new IssueException(
                        Issue::fromType(Issue::AccessPropertyNonStaticAsStatic)(
                            $context->getFile(),
                            $context->getLineNumberStart(),
                            [ "{$this->getFQSEN()}->\${$property->getName()}" ]
                        )
                    );
                } else {
                    throw new IssueException(
                        Issue::fromType(Issue::AccessPropertyStaticAsNonStatic)(
                            $context->getFile(),
                            $context->getLineNumberStart(),
                            [ "{$this->getFQSEN()}::\${$property->getName()}" ]
                        )
                    );
                }
            }

            $is_property_accessible = $this->isPropertyAccessible($code_base, $property, $context);
        }

        // If the property exists and is accessible, return it
        if ($is_property_accessible) {
            return $property;
        }

        // Check to see if we can use a __get magic method
        if (!$is_static && $this->hasMethodWithName($code_base, '__get')) {
            $method = $this->getMethodByName($code_base, '__get');

            // Make sure the magic method is accessible
            // TODO: Add defined at %s:%d for the property definition
            if ($method->isPrivate()) {
                throw new IssueException(
                    Issue::fromType(Issue::AccessPropertyPrivate)(
                        $context->getFile(),
                        $context->getLineNumberStart(),
                        [ (string)$property_fqsen ]
                    )
                );
            } elseif ($method->isProtected()) {
                throw new IssueException(
                    Issue::fromType(Issue::AccessPropertyProtected)(
                        $context->getFile(),
                        $context->getLineNumberStart(),
                        [ (string)$property_fqsen ]
                    )
                );
            }

            $property = new Property(
                $context,
                $name,
                $method->getUnionType(),
                0,
                $property_fqsen
            );

            $this->addProperty($code_base, $property, new None);

            return $property;
        } elseif ($has_property) {
            // If we have a property, but its inaccessible, emit
            // an issue
            // TODO: Add defined at %s:%d for the property definition - see https://github.com/phan/phan/issues/1375
            if ($property->isPrivate()) {
                throw new IssueException(
                    Issue::fromType(Issue::AccessPropertyPrivate)(
                        $context->getFile(),
                        $context->getLineNumberStart(),
                        [ "{$this->getFQSEN()}::\${$property->getName()}" ]
                    )
                );
            }
            if ($property->isProtected()) {
                throw new IssueException(
                    Issue::fromType(Issue::AccessPropertyProtected)(
                        $context->getFile(),
                        $context->getLineNumberStart(),
                        [ "{$this->getFQSEN()}::\${$property->getName()}" ]
                    )
                );
            }
        }

        // Check to see if missing properties are allowed
        // or we're working with a class with dynamic
        // properties such as stdclass.
        if (!$is_static && (Config::getValue('allow_missing_properties')
            || $this->getHasDynamicProperties($code_base))
        ) {
            $property = new Property(
                $context,
                $name,
                UnionType::empty(),
                0,
                $property_fqsen
            );

            $this->addProperty($code_base, $property, new None);

            return $property;
        }

        // TODO: should be ->, to be consistent with other uses for instance properties?
        throw new IssueException(
            Issue::fromType(Issue::UndeclaredProperty)(
                $context->getFile(),
                $context->getLineNumberStart(),
                [ "{$this->getFQSEN()}::\$$name}" ]
            )
        );
    }

    /**
     * @return array<string,Property>
     * The list of properties on this class
     */
    public function getPropertyMap(CodeBase $code_base) : array
    {
        return $code_base->getPropertyMapByFullyQualifiedClassName(
            $this->getFQSEN()
        );
    }

    /**
     * Inherit a class constant from an ancestor class
     *
     * @return void
     */
    public function inheritConstant(
        CodeBase $code_base,
        ClassConstant $constant
    ) {
        $constant_fqsen = FullyQualifiedClassConstantName::make(
            $this->getFQSEN(),
            $constant->getName()
        );

        if ($code_base->hasClassConstantWithFQSEN($constant_fqsen)) {
            // If the constant with that name already exists, mark it as an override.
            $overriding_constant = $code_base->getClassConstantByFQSEN($constant_fqsen);
            $overriding_constant->setIsOverride(true);
            return;
        }

        // Update the FQSEN if its not associated with this
        // class yet (always true)
        if ($constant->getFQSEN() !== $constant_fqsen) {
            $constant = clone($constant);
            $constant->setFQSEN($constant_fqsen);
        }

        $code_base->addClassConstant($constant);
    }

    /**
     * Add a class constant
     *
     * @return void
     */
    public function addConstant(
        CodeBase $code_base,
        ClassConstant $constant
    ) {
        $constant_fqsen = FullyQualifiedClassConstantName::make(
            $this->getFQSEN(),
            $constant->getName()
        );

        // Update the FQSEN if its not associated with this
        // class yet
        if ($constant->getFQSEN() !== $constant_fqsen) {
            $constant = clone($constant);
            $constant->setFQSEN($constant_fqsen);
        }

        $code_base->addClassConstant($constant);
    }

    /**
     * @return bool
     * True if a constant with the given name is defined
     * on this class.
     */
    public function hasConstantWithName(
        CodeBase $code_base,
        string $name
    ) : bool {
        if ($code_base->hasClassConstantWithFQSEN(
            FullyQualifiedClassConstantName::make(
                $this->getFQSEN(),
                $name
            )
        )) {
            return true;
        }
        if (!$this->hydrateConstantsIndicatingFirstTime($code_base)) {
            return false;
        }
        return $code_base->hasClassConstantWithFQSEN(
            FullyQualifiedClassConstantName::make(
                $this->getFQSEN(),
                $name
            )
        );
    }

    /**
     * @param CodeBase $code_base
     * A reference to the entire code base in which the
     * property exists.
     *
     * @param string $name
     * The name of the class constant
     *
     * @param Context $context
     * The context of the caller requesting the property
     *
     * @return ClassConstant
     * A constant with the given name
     *
     * @throws IssueException
     * An exception may be thrown if the caller does not
     * have access to the given property from the given
     * context
     */
    public function getConstantByNameInContext(
        CodeBase $code_base,
        string $name,
        Context $context
    ) : ClassConstant {

        $constant_fqsen = FullyQualifiedClassConstantName::make(
            $this->getFQSEN(),
            $name
        );

        if (!$code_base->hasClassConstantWithFQSEN($constant_fqsen)) {
            throw new IssueException(
                Issue::fromType(Issue::UndeclaredClassConstant)(
                    $context->getFile(),
                    $context->getLineNumberStart(),
                    [
                        (string)$constant_fqsen,
                        (string)$this->getFQSEN()
                    ]
                )
            );
        }

        $constant = $code_base->getClassConstantByFQSEN(
            $constant_fqsen
        );

        // Are we within a class referring to the class
        // itself?
        $is_local_access = (
            $context->isInClassScope()
            && $context->getClassInScope($code_base) === $constant->getClass($code_base)
        );

        // Are we within a class or an extending sub-class
        // referring to the class?
        $is_local_or_remote_access = (
            $is_local_access
            || (
                $context->isInClassScope()
                && $context->getClassInScope($code_base)
                ->getUnionType()->canCastToExpandedUnionType(
                    $this->getUnionType(),
                    $code_base
                )
            )
        );

        // If we have the constant, but its inaccessible, emit
        // an issue
        if (!$is_local_access && $constant->isPrivate()) {
            throw new IssueException(
                Issue::fromType(Issue::AccessClassConstantPrivate)(
                    $context->getFile(),
                    $context->getLineNumberStart(),
                    [
                        (string)$constant_fqsen,
                        $constant->getContext()->getFile(),
                        $constant->getContext()->getLineNumberStart()
                    ]
                )
            );
        } elseif (!$is_local_or_remote_access && $constant->isProtected()) {
            throw new IssueException(
                Issue::fromType(Issue::AccessClassConstantProtected)(
                    $context->getFile(),
                    $context->getLineNumberStart(),
                    [
                        (string)$constant_fqsen,
                        $constant->getContext()->getFile(),
                        $constant->getContext()->getLineNumberStart()
                    ]
                )
            );
        }

        return $constant;
    }

    /**
     * @return array<string,ClassConstant>
     * The constants associated with this class
     */
    public function getConstantMap(CodeBase $code_base) : array
    {
        return $code_base->getClassConstantMapByFullyQualifiedClassName(
            $this->getFQSEN()
        );
    }

    /**
     * Add a method to this class
     *
     * @param CodeBase $code_base
     * A reference to the code base in which the ancestor exists
     *
     * @param Method $method
     * The method to copy onto this class
     *
     * @param Option<Type>|None $type_option
     * A possibly defined type used to define template
     * parameter types when importing the method
     *
     * @return void
     */
    public function addMethod(
        CodeBase $code_base,
        Method $method,
        $type_option
    ) {
        $method_fqsen = FullyQualifiedMethodName::make(
            $this->getFQSEN(),
            $method->getName(),
            $method->getFQSEN()->getAlternateId()
        );

        $is_override = $code_base->hasMethodWithFQSEN($method_fqsen);
        // Don't overwrite overridden methods with
        // parent methods
        if ($is_override) {
            // Note that we're overriding something
            // (but only do this if it's abstract)
            // TODO: Consider all permutations of abstract and real methods on classes, interfaces, and traits.
            $existing_method =
                $code_base->getMethodByFQSEN($method_fqsen);
            // Note: For private/protected methods, the defining FQSEN is set to the FQSEN of the inheriting class.
            // So, when multiple traits are inherited, they may identical defining FQSENs, but some may be abstract, and others may be implemented.
            if ($method->getDefiningFQSEN() === $existing_method->getDefiningFQSEN() && $method->isAbstract() === $existing_method->isAbstract()) {
                return;
            }

            if ($method->isAbstract() || !$existing_method->isAbstract() || $existing_method->getIsNewConstructor()) {
                // TODO: What if both of these are abstract, and those get combined into an abstract class?
                //       Should phan check compatibility of the abstract methods it inherits?
                $existing_method->setIsOverride(true);

                // Don't add the method
                return;
            }
        }

        if ($method->getFQSEN() !== $method_fqsen) {
            $method = clone($method);
            $method->setFQSEN($method_fqsen);
            // When we inherit it from the ancestor class, it may be an override in the ancestor class,
            // but that doesn't imply it's an override in *this* class.
            $method->setIsOverride($is_override);

            // Clone the parameter list, so that modifying the parameters on the first call won't modify the others.
            $method->cloneParameterList();

            // If we have a parent type defined, map the method's
            // return type and parameter types through it
            if ($type_option->isDefined()) {
                // Map the method's return type
                if ($method->getUnionType()->hasTemplateType()) {
                    $method->setUnionType(
                        $method->getUnionType()->withTemplateParameterTypeMap(
                            $type_option->get()->getTemplateParameterTypeMap(
                                $code_base
                            )
                        )
                    );
                }

                // Map each method parameter
                $method->setParameterList(
                    \array_map(function (Parameter $parameter) use ($type_option, $code_base) : Parameter {

                        if (!$parameter->getUnionType()->hasTemplateType()) {
                            return $parameter;
                        }

                        $mapped_parameter = clone($parameter);

                        $mapped_parameter->setUnionType(
                            $mapped_parameter->getUnionType()->withTemplateParameterTypeMap(
                                $type_option->get()->getTemplateParameterTypeMap(
                                    $code_base
                                )
                            )
                        );

                        return $mapped_parameter;
                    }, $method->getParameterList())
                );
            }
        }
        if ($method->getHasYield()) {
            // There's no phpdoc standard for template types of Generators at the moment.
            $newType = UnionType::fromFullyQualifiedString('\\Generator');
            if (!$newType->canCastToUnionType($method->getUnionType())) {
                $method->setUnionType($newType);
            }
        }

        // Methods defined on interfaces are always abstract, but don't have that flag set.
        // NOTE: __construct is special for the following reasons:
        // 1. We automatically add __construct to class-like definitions (Not sure why it's done for interfaces)
        // 2. If it's abstract, then PHP would enforce that signatures are compatible
        if ($this->isInterface() && !$method->getIsNewConstructor()) {
            $method->setFlags(Flags::bitVectorWithState($method->getFlags(), \ast\flags\MODIFIER_ABSTRACT, true));
        }

        if ($is_override) {
            $method->setIsOverride(true);
        }

        $code_base->addMethod($method);
    }

    /**
     * @return bool
     * True if this class has a method with the given name
     */
    public function hasMethodWithName(
        CodeBase $code_base,
        string $name,
        bool $is_direct_invocation = false
    ) : bool {
        // All classes have a constructor even if it hasn't
        // been declared yet
        if (!$is_direct_invocation && '__construct' === \strtolower($name)) {
            return true;
        }

        $method_fqsen = FullyQualifiedMethodName::make(
            $this->getFQSEN(),
            $name
        );

        if ($code_base->hasMethodWithFQSEN($method_fqsen)) {
            return true;
        }
        if (!$this->hydrateIndicatingFirstTime($code_base)) {
            return false;
        }
        return $code_base->hasMethodWithFQSEN($method_fqsen);
    }

    /**
     * @return Method
     * The method with the given name
     */
    public function getMethodByName(
        CodeBase $code_base,
        string $name
    ) : Method {
        $method_fqsen = FullyQualifiedMethodName::make(
            $this->getFQSEN(),
            $name
        );

        if (!$code_base->hasMethodWithFQSEN($method_fqsen)) {
            if ('__construct' === $name) {
                // Create a default constructor if its requested
                // but doesn't exist yet
                $default_constructor =
                    Method::defaultConstructorForClass(
                        $this,
                        $code_base
                    );

                $this->addMethod($code_base, $default_constructor, $this->getParentTypeOption());

                return $default_constructor;
            }

            throw new CodeBaseException(
                $method_fqsen,
                "Method with name $name does not exist for class {$this->getFQSEN()}."
            );
        }

        return $code_base->getMethodByFQSEN($method_fqsen);
    }

    /**
     * @return array<string,Method>
     * A list of methods on this class
     */
    public function getMethodMap(CodeBase $code_base) : array
    {
        return $code_base->getMethodMapByFullyQualifiedClassName(
            $this->getFQSEN()
        );
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return bool
     * True if this class has a magic '__call' method
     */
    public function hasCallMethod(CodeBase $code_base) : bool
    {
        return $this->hasMethodWithName($code_base, '__call');
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return bool
     * True if this class has a magic '__call' method,
     * and (at)phan-forbid-undeclared-magic-methods doesn't exist on this class or ancestors
     */
    public function allowsCallingUndeclaredInstanceMethod(CodeBase $code_base)
    {
        return $this->hasCallMethod($code_base) &&
            !$this->getForbidUndeclaredMagicMethods($code_base);
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return Method
     * The magic `__call` method
     */
    public function getCallMethod(CodeBase $code_base)
    {
        return $this->getMethodByName($code_base, '__call');
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return bool
     * True if this class has a magic '__callStatic' method
     */
    public function hasCallStaticMethod(CodeBase $code_base) : bool
    {
        return $this->hasMethodWithName($code_base, '__callStatic');
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return bool
     * True if this class has a magic '__callStatic' method,
     * and (at)phan-forbid-undeclared-magic-methods doesn't exist on this class or ancestors.
     */
    public function allowsCallingUndeclaredStaticMethod(CodeBase $code_base)
    {
        return $this->hasCallStaticMethod($code_base) &&
            !$this->getForbidUndeclaredMagicMethods($code_base);
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return Method
     * The magic `__callStatic` method
     */
    public function getCallStaticMethod(CodeBase $code_base)
    {
        return $this->getMethodByName($code_base, '__callStatic');
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return bool
     * True if this class has a magic '__get' method
     */
    public function hasGetMethod(CodeBase $code_base) : bool
    {
        return $this->hasMethodWithName($code_base, '__get');
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return bool
     * True if this class has a magic '__set' method
     */
    public function hasSetMethod(CodeBase $code_base) : bool
    {
        return $this->hasMethodWithName($code_base, '__set');
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return bool
     * True if this class has a magic '__get' or '__set'
     * method
     */
    public function hasGetOrSetMethod(CodeBase $code_base)
    {
        return (
            $this->hasGetMethod($code_base)
            || $this->hasSetMethod($code_base)
        );
    }

    /**
     * @return void
     */
    public function addTraitFQSEN(FQSEN $fqsen)
    {
        $this->trait_fqsen_list[] = $fqsen;

        // Add the trait to the union type of this class
        $this->addAdditionalType(
            Type::fromFullyQualifiedString($fqsen->__toString())
        );
    }

    /**
     * @return void
     */
    public function addTraitAdaptations(TraitAdaptations $trait_adaptations)
    {
        $this->trait_adaptations_map[\strtolower($trait_adaptations->getTraitFQSEN()->__toString())] = $trait_adaptations;
    }

    /**
     * @return array<int,FullyQualifiedClassName>
     * A list of FQSEN's for included traits
     * @suppress PhanPartialTypeMismatchReturn TODO: investigate
     */
    public function getTraitFQSENList() : array
    {
        return $this->trait_fqsen_list;
    }

    /**
     * @return bool
     * True if this class calls its parent constructor
     */
    public function getIsParentConstructorCalled() : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_PARENT_CONSTRUCTOR_CALLED);
    }

    /**
     * @return void
     */
    public function setIsParentConstructorCalled(
        bool $is_parent_constructor_called
    ) {
        $this->setPhanFlags(Flags::bitVectorWithState(
            $this->getPhanFlags(),
            Flags::IS_PARENT_CONSTRUCTOR_CALLED,
            $is_parent_constructor_called
        ));
    }

    /**
     * Check if this class or its ancestors forbids undeclared magic properties.
     */
    public function getForbidUndeclaredMagicProperties(CodeBase $code_base) : bool
    {
        return (
            $this->getPhanFlagsHasState(Flags::CLASS_FORBID_UNDECLARED_MAGIC_PROPERTIES)
            ||
            (
                $this->hasParentType()
                && $code_base->hasClassWithFQSEN($this->getParentClassFQSEN())
                && $this->getParentClass($code_base)->getForbidUndeclaredMagicProperties($code_base)
            )
        );
    }

    /**
     * Set whether undeclared magic properties are forbidden
     * (properties accessed through __get or __set, with no (at)property annotation on parent class)
     * @param bool $forbid_undeclared_dynamic_properties - set to true to forbid.
     * @return void
     */
    public function setForbidUndeclaredMagicProperties(
        bool $forbid_undeclared_dynamic_properties
    ) {
        $this->setPhanFlags(Flags::bitVectorWithState(
            $this->getPhanFlags(),
            Flags::CLASS_FORBID_UNDECLARED_MAGIC_PROPERTIES,
            $forbid_undeclared_dynamic_properties
        ));
    }

    /**
     * Check if this class or its ancestors forbids undeclared magic methods.
     */
    public function getForbidUndeclaredMagicMethods(CodeBase $code_base) : bool
    {
        return (
            $this->getPhanFlagsHasState(Flags::CLASS_FORBID_UNDECLARED_MAGIC_METHODS)
            ||
            (
                $this->hasParentType()
                && $code_base->hasClassWithFQSEN($this->getParentClassFQSEN())
                && $this->getParentClass($code_base)->getForbidUndeclaredMagicMethods($code_base)
            )
        );
    }

    /**
     * Set whether undeclared magic methods are forbidden
     * (methods accessed through __call or __callStatic, with no (at)method annotation on class)
     * @param bool $forbid_undeclared_magic_methods - set to true to forbid.
     * @return void
     */
    public function setForbidUndeclaredMagicMethods(
        bool $forbid_undeclared_magic_methods
    ) {
        $this->setPhanFlags(Flags::bitVectorWithState(
            $this->getPhanFlags(),
            Flags::CLASS_FORBID_UNDECLARED_MAGIC_METHODS,
            $forbid_undeclared_magic_methods
        ));
    }

    /**
     * @return bool
     * True if this class has dynamic properties. (e.g. stdClass)
     */
    public function getHasDynamicProperties(CodeBase $code_base) : bool
    {
        return (
            $this->getPhanFlagsHasState(Flags::CLASS_HAS_DYNAMIC_PROPERTIES)
            ||
            (
                $this->hasParentType()
                && $code_base->hasClassWithFQSEN($this->getParentClassFQSEN())
                && $this->getParentClass($code_base)->getHasDynamicProperties($code_base)
            )
        );
    }

    /**
     * @return void
     */
    public function setHasDynamicProperties(
        bool $has_dynamic_properties
    ) {
        $this->setPhanFlags(Flags::bitVectorWithState(
            $this->getPhanFlags(),
            Flags::CLASS_HAS_DYNAMIC_PROPERTIES,
            $has_dynamic_properties
        ));
    }


    /**
     * @return bool
     * True if this is a final class
     */
    public function isFinal() : bool
    {
        return $this->getFlagsHasState(\ast\flags\CLASS_FINAL);
    }

    /**
     * @return bool
     * True if this is an abstract class
     */
    public function isAbstract() : bool
    {
        return $this->getFlagsHasState(\ast\flags\CLASS_ABSTRACT);
    }

    /**
     * @return bool
     * True if this is an interface
     */
    public function isInterface() : bool
    {
        return $this->getFlagsHasState(\ast\flags\CLASS_INTERFACE);
    }

    /**
     * @return bool
     * True if this class is a trait
     */
    public function isTrait() : bool
    {
        return $this->getFlagsHasState(\ast\flags\CLASS_TRAIT);
    }

    /**
     * @return FullyQualifiedClassName
     */
    public function getFQSEN() : FullyQualifiedClassName
    {
        return $this->fqsen;
    }

    /**
     * @return array<int,FullyQualifiedClassName>
     */
    public function getNonParentAncestorFQSENList()
    {
        return \array_merge(
            $this->getInterfaceFQSENList(),
            $this->getTraitFQSENList()
        );
    }

    /**
     * @return array<int,FullyQualifiedClassName>
     */
    public function getAncestorFQSENList()
    {
        $ancestor_list = $this->getNonParentAncestorFQSENList();

        if ($this->hasParentType()) {
            $ancestor_list[] = $this->getParentClassFQSEN();
        }

        return $ancestor_list;
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @param array<int,FullyQualifiedClassName> $fqsen_list
     * A list of class FQSENs to turn into a list of
     * Clazz objects
     *
     * @return array<int,Clazz>
     */
    private function getClassListFromFQSENList(
        CodeBase $code_base,
        array $fqsen_list
    ) : array {
        $class_list = [];
        foreach ($fqsen_list as $fqsen) {
            if ($code_base->hasClassWithFQSEN($fqsen)) {
                $class_list[] = $code_base->getClassByFQSEN($fqsen);
            }
        }
        return $class_list;
    }

    /**
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return array<int,Clazz>
     */
    public function getAncestorClassList(CodeBase $code_base)
    {
        return $this->getClassListFromFQSENList(
            $code_base,
            $this->getAncestorFQSENList()
        );
    }

    /**
     * Add properties, constants and methods from all
     * ancestors (parents, traits, ...) to this class
     *
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return void
     */
    public function importConstantsFromAncestorClasses(CodeBase $code_base)
    {
        if (!$this->isFirstExecution(__METHOD__)) {
            return;
        }

        foreach ($this->getInterfaceFQSENList() as $fqsen) {
            if (!$code_base->hasClassWithFQSEN($fqsen)) {
                continue;
            }

            $ancestor = $code_base->getClassByFQSENWithoutHydrating($fqsen);
            $this->importConstantsFromAncestorClass(
                $code_base,
                $ancestor
            );
        }

        foreach ($this->getTraitFQSENList() as $fqsen) {
            if (!$code_base->hasClassWithFQSEN($fqsen)) {
                continue;
            }

            $ancestor = $code_base->getClassByFQSENWithoutHydrating($fqsen);
            $this->importConstantsFromAncestorClass(
                $code_base,
                $ancestor
            );
        }

        // Copy information from the parent(s)
        $this->importConstantsFromParentClass($code_base);
    }

    /**
     * Add properties, constants and methods from all
     * ancestors (parents, traits, ...) to this class
     *
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return void
     */
    public function importAncestorClasses(CodeBase $code_base)
    {
        if (!$this->isFirstExecution(__METHOD__)) {
            return;
        }
        $this->importConstantsFromAncestorClasses($code_base);

        foreach ($this->getInterfaceFQSENList() as $fqsen) {
            if (!$code_base->hasClassWithFQSEN($fqsen)) {
                continue;
            }

            $ancestor = $code_base->getClassByFQSEN($fqsen);

            if (!$ancestor->isInterface()) {
                $this->emitWrongInheritanceCategoryWarning($code_base, $ancestor, 'Interface');
            }

            $this->importAncestorClass(
                $code_base,
                $ancestor,
                new None
            );
        }

        foreach ($this->getTraitFQSENList() as $fqsen) {
            if (!$code_base->hasClassWithFQSEN($fqsen)) {
                continue;
            }

            $ancestor = $code_base->getClassByFQSEN($fqsen);
            if (!$ancestor->isTrait()) {
                $this->emitWrongInheritanceCategoryWarning($code_base, $ancestor, 'Trait');
            }

            $this->importAncestorClass(
                $code_base,
                $ancestor,
                new None
            );
        }

        // Copy information from the parent(s)
        $this->importParentClass($code_base);
    }

    /*
     * Add properties, constants and methods from the
     * parent of this class
     *
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return void
     */
    private function importConstantsFromParentClass(CodeBase $code_base)
    {
        if (!$this->isFirstExecution(__METHOD__)) {
            return;
        }

        if (!$this->hasParentType()) {
            return;
        }

        if ($this->getParentClassFQSEN() === $this->getFQSEN()) {
            return;
        }

        // Let the parent class finder worry about this
        if (!$code_base->hasClassWithFQSEN(
            $this->getParentClassFQSEN()
        )) {
            return;
        }

        // Get the parent class
        $parent = $this->getParentClassWithoutHydrating($code_base);

        // import constants from that class
        $this->importConstantsFromAncestorClass($code_base, $parent);
    }

    /*
     * Add properties, constants and methods from the
     * parent of this class
     *
     * @param CodeBase $code_base
     * The entire code base from which we'll find ancestor
     * details
     *
     * @return void
     */
    private function importParentClass(CodeBase $code_base)
    {
        if (!$this->isFirstExecution(__METHOD__)) {
            return;
        }

        if (!$this->hasParentType()) {
            return;
        }

        if ($this->getParentClassFQSEN() === $this->getFQSEN()) {
            return;
        }

        // Let the parent class finder worry about this
        if (!$code_base->hasClassWithFQSEN(
            $this->getParentClassFQSEN()
        )) {
            return;
        }

        // Get the parent class
        $parent = $this->getParentClass($code_base);

        if ($parent->isTrait() || $parent->isInterface()) {
            $this->emitWrongInheritanceCategoryWarning($code_base, $parent, 'Class');
        }
        if ($parent->isFinal()) {
            $this->emitExtendsFinalClassWarning($code_base, $parent);
        }

        // Tell the parent to import its own parents first

        // Import elements from the parent
        $this->importAncestorClass(
            $code_base,
            $parent,
            $this->getParentTypeOption()
        );
    }

    /**
     * @return void
     */
    private function emitWrongInheritanceCategoryWarning(
        CodeBase $code_base,
        Clazz $ancestor,
        string $expected_inheritance_category
    ) {
        $context = $this->getContext();
        if ($ancestor->isPHPInternal()) {
            if (!$this->hasSuppressIssue(Issue::AccessWrongInheritanceCategoryInternal)) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::AccessWrongInheritanceCategoryInternal,
                    $context->getLineNumberStart(),
                    (string)$ancestor,
                    $expected_inheritance_category
                );
            }
        } else {
            if (!$this->hasSuppressIssue(Issue::AccessWrongInheritanceCategory)) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::AccessWrongInheritanceCategory,
                    $context->getLineNumberStart(),
                    (string)$ancestor,
                    $ancestor->getFileRef()->getFile(),
                    $ancestor->getFileRef()->getLineNumberStart(),
                    $expected_inheritance_category
                );
            }
        }
    }

    /**
     * @return void
     */
    private function emitExtendsFinalClassWarning(
        CodeBase $code_base,
        Clazz $ancestor
    ) {
        $context = $this->getContext();
        if ($ancestor->isPHPInternal()) {
            if (!$this->hasSuppressIssue(Issue::AccessExtendsFinalClassInternal)) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::AccessExtendsFinalClassInternal,
                    $context->getLineNumberStart(),
                    (string)$ancestor->getFQSEN()
                );
            }
        } else {
            if (!$this->hasSuppressIssue(Issue::AccessExtendsFinalClass)) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::AccessExtendsFinalClass,
                    $context->getLineNumberStart(),
                    (string)$ancestor->getFQSEN(),
                    $ancestor->getFileRef()->getFile(),
                    $ancestor->getFileRef()->getLineNumberStart()
                );
            }
        }
    }

    /**
     * Add constants from the given class to this.
     *
     * @param CodeBase $code_base
     * A reference to the code base in which the ancestor exists
     *
     * @param Clazz $class
     * A class to import from
     *
     * @return void
     */
    public function importConstantsFromAncestorClass(
        CodeBase $code_base,
        Clazz $class
    ) {
        $key = \strtolower((string)$class->getFQSEN());
        if (!$this->isFirstExecution(
            __METHOD__ . ':' . $key
        )) {
            return;
        }

        $class->addReference($this->getContext());

        // Make sure that the class imports its parents' constants first
        // (And **only** the constants)
        $class->hydrateConstants($code_base);

        // Copy constants
        foreach ($class->getConstantMap($code_base) as $constant) {
            $this->inheritConstant($code_base, $constant);
        }
    }

    /**
     * Add properties, constants and methods from the given
     * class to this.
     *
     * @param CodeBase $code_base
     * A reference to the code base in which the ancestor exists
     *
     * @param Clazz $class
     * A class to import from
     *
     * @param Option<Type>|None $type_option
     * A possibly defined ancestor type used to define template
     * parameter types when importing ancestor properties and
     * methods
     *
     * @return void
     */
    public function importAncestorClass(
        CodeBase $code_base,
        Clazz $class,
        $type_option
    ) {
        $key = \strtolower((string)$class->getFQSEN());
        if (!$this->isFirstExecution(
            __METHOD__ . ':' . $key
        )) {
            return;
        }

        // Constants should have been imported earlier, but call it again just in case
        $this->importConstantsFromAncestorClass($code_base, $class);

        // Make sure that the class imports its parents first
        // NOTE: We already imported constants from $class in importConstantsFromAncestorClass
        $class->hydrate($code_base);
        $is_trait = $class->isTrait();
        $trait_adaptations = $is_trait ? ($this->trait_adaptations_map[$key] ?? null) : null;

        // Copy properties
        foreach ($class->getPropertyMap($code_base) as $property) {
            // TODO: check for conflicts in visibility and default values for traits.
            // TODO: Check for ancestor classes with the same private property?
            $this->addProperty(
                $code_base,
                $property,
                $type_option,
                $is_trait
            );
        }

        // Copy methods
        foreach ($class->getMethodMap($code_base) as $method) {
            if (!\is_null($trait_adaptations) && count($trait_adaptations->hidden_methods) > 0) {
                $method_name_key = \strtolower($method->getName());
                if (isset($trait_adaptations->hidden_methods[$method_name_key])) {
                    // TODO: Record that the method was hidden, and check later on that all method that were hidden were actually defined?
                    continue;
                }
            }
            // Workaround: For private methods, copy the method with a new defining class.
            // If you import a trait's private method, it becomes private **to the class which used the trait** in PHP code.
            // (But preserving the defining FQSEN is fine for this)
            if ($is_trait) {
                $method_flags = $method->getFlags();
                if (Flags::bitVectorHasState($method_flags, \ast\flags\MODIFIER_PRIVATE)) {
                    $method = $method->createUseAlias($this, $method->getName(), \ast\flags\MODIFIER_PRIVATE);
                } elseif (Flags::bitVectorHasState($method_flags, \ast\flags\MODIFIER_PROTECTED)) {
                    $method = $method->createUseAlias($this, $method->getName(), \ast\flags\MODIFIER_PROTECTED);
                }
            }
            $this->addMethod(
                $code_base,
                $method,
                $type_option
            );
        }

        if (!\is_null($trait_adaptations)) {
            $this->importTraitAdaptations($code_base, $class, $trait_adaptations, $type_option);
        }
    }

    /**
     * @param CodeBase $code_base
     * @param Clazz $class
     * @param TraitAdaptations $trait_adaptations
     * @param Option<Type>|None $type_option
     * A possibly defined ancestor type used to define template
     * parameter types when importing ancestor properties and
     * methods
     *
     * @return void
     */
    private function importTraitAdaptations(
        CodeBase $code_base,
        Clazz $class,
        TraitAdaptations $trait_adaptations,
        $type_option
    ) {
        foreach ($trait_adaptations->alias_methods ?? [] as $alias_method_name => $original_trait_alias_source) {
            $source_method_name = $original_trait_alias_source->getSourceMethodName();
            if (!$class->hasMethodWithName($code_base, $source_method_name)) {
                Issue::maybeEmit(
                    $code_base,
                    $this->getContext(),
                    Issue::UndeclaredAliasedMethodOfTrait,
                    $original_trait_alias_source->getAliasLineno(),  // TODO: Track line number in TraitAdaptation
                    sprintf('%s::%s', (string)$this->getFQSEN(), $alias_method_name),
                    sprintf('%s::%s', (string)$class->getFQSEN(), $source_method_name),
                    $class->getName()
                );
                continue;
            }
            $source_method = $class->getMethodByName($code_base, $source_method_name);
            $alias_method = $source_method->createUseAlias(
                $this,
                $alias_method_name,
                $original_trait_alias_source->getAliasVisibilityFlags()
            );
            $this->addMethod($code_base, $alias_method, $type_option);
        }
    }

    /**
     * @return int
     * The number of references to this typed structural element
     */
    public function getReferenceCount(
        CodeBase $code_base
    ) : int {
        $count = parent::getReferenceCount($code_base);

        // A function that maps a list of elements to the
        // total reference count for all elements
        $list_count = function (array $list) use ($code_base) {
            return \array_reduce($list, function (
                int $count,
                AddressableElement $element
            ) use ($code_base) {
                return (
                    $count
                    + $element->getReferenceCount($code_base)
                );
            }, 0);
        };

        // Sum up counts for all dependent elements
        $count += $list_count($this->getPropertyMap($code_base));
        $count += $list_count($this->getMethodMap($code_base));
        $count += $list_count($this->getConstantMap($code_base));

        return $count;
    }

    /**
     * @return bool
     * True if this class contains generic types
     */
    public function isGeneric() : bool
    {
        return $this->getInternalScope()->hasAnyTemplateType();
    }

    /**
     * @return array<string,TemplateType>
     * The set of all template types parameterizing this generic
     * class
     */
    public function getTemplateTypeMap() : array
    {
        return $this->getInternalScope()->getTemplateTypeMap();
    }

    /**
     * @return string
     * A string describing this class
     */
    public function __toString() : string
    {
        $string = '';

        if ($this->isFinal()) {
            $string .= 'final ';
        }

        if ($this->isAbstract()) {
            $string .= 'abstract ';
        }

        if ($this->isInterface()) {
            $string .= 'Interface ';
        } elseif ($this->isTrait()) {
            $string .= 'Trait ';
        } else {
            $string .= 'Class ';
        }

        $string .= (string)$this->getFQSEN()->getCanonicalFQSEN();

        return $string;
    }

    private function toStubSignature(CodeBase $code_base) : string
    {
        $string = '';

        if ($this->isFinal()) {
            $string .= 'final ';
        }

        if ($this->isAbstract() && !$this->isInterface()) {
            $string .= 'abstract ';
        }

        if ($this->isInterface()) {
            $string .= 'interface ';
        } elseif ($this->isTrait()) {
            $string .= 'trait ';
        } else {
            $string .= 'class ';
        }

        $string .= (string)$this->getFQSEN()->getName();

        $extend_types = [];
        $implements_types = [];
        $parent_implements_types = [];

        $parent_type = $this->parent_type;
        if ($parent_type) {
            $extend_types[] = $parent_type->asFQSEN();
            $parent_class = $this->getParentClass($code_base);
            $parent_implements_types = $parent_class->interface_fqsen_list;
        }

        if (count($this->interface_fqsen_list) > 0) {
            if ($this->isInterface()) {
                $extend_types = array_merge($extend_types, $this->interface_fqsen_list);
            } else {
                $implements_types = $this->interface_fqsen_list;
                if (count($parent_implements_types) > 0) {
                    $implements_types = array_diff($implements_types, $parent_implements_types);
                }
            }
        }
        if (count($extend_types) > 0) {
            $string .= ' extends ' . implode(', ', $extend_types);
        }
        if (count($implements_types) > 0) {
            $string .= ' implements ' . implode(', ', $implements_types);
        }
        return $string;
    }

    /**
     * @suppress PhanUnreferencedPublicMethod (toStubInfo is used by callers for more flexibility)
     */
    public function toStub(CodeBase $code_base) : string
    {
        list($namespace, $string) = $this->toStubInfo($code_base);
        $namespace_text = $namespace === '' ? '' : "$namespace ";
        $string = sprintf("namespace %s{\n%s}\n", $namespace_text, $string);
        return $string;
    }

    /** @return array{0:string,1:string} [string $namespace, string $text] */
    public function toStubInfo(CodeBase $code_base) : array
    {
        $signature = $this->toStubSignature($code_base);

        $stub = $signature;

        $stub .= " {";

        $constant_map = $this->getConstantMap($code_base);
        if (count($constant_map) > 0) {
            $stub .= "\n\n    // constants\n";
            $stub .= implode("\n", array_map(function (ClassConstant $constant) {
                return $constant->toStub();
            }, $constant_map));
        }

        $property_map = $this->getPropertyMap($code_base);
        if (count($property_map) > 0) {
            $stub .= "\n\n    // properties\n";

            $stub .= implode("\n", array_map(function (Property $property) {
                return $property->toStub();
            }, $property_map));
        }
        $reflection_class = new \ReflectionClass((string)$this->getFQSEN());
        $method_map = array_filter($this->getMethodMap($code_base), function (Method $method) use ($reflection_class) : bool {
            if ($method->getFQSEN()->isAlternate()) {
                return false;
            }
            $reflection_method = $reflection_class->getMethod($method->getName());
            if ($reflection_method->getDeclaringClass()->getName() !== $reflection_class->getName()) {
                return false;
            }
            return true;
        });
        if (count($method_map) > 0) {
            $stub .= "\n\n    // methods\n";

            $is_interface = $this->isInterface();
            $stub .= implode("\n", array_map(function (Method $method) use ($is_interface) {
                return $method->toStub($is_interface);
            }, $method_map));
        }

        $stub .= "\n}\n\n";
        $namespace = ltrim($this->getFQSEN()->getNamespace(), '\\');
        return [$namespace, $stub];
    }

    protected function hydrateConstantsOnce(CodeBase $code_base)
    {
        foreach ($this->getAncestorFQSENList() as $fqsen) {
            if ($code_base->hasClassWithFQSEN($fqsen)) {
                $code_base->getClassByFQSENWithoutHydrating(
                    $fqsen
                )->hydrateConstants($code_base);
            }
        }

        // Create the 'class' constant
        $class_constant = new ClassConstant(
            $this->getContext(),
            'class',
            StringType::instance(false)->asUnionType(),
            0,
            FullyQualifiedClassConstantName::make(
                $this->getFQSEN(),
                'class'
            )
        );
        $class_constant->setNodeForValue((string)$this->getFQSEN());
        $this->addConstant($code_base, $class_constant);

        // Add variable '$this' to the scope
        $this->getInternalScope()->addVariable(
            new Variable(
                $this->getContext(),
                'this',
                $this->getUnionType(),
                0
            )
        );

        // Fetch the constants declared within the class, to check if they have override annotations later.
        $original_declared_class_constants = $this->getConstantMap($code_base);

        // Load parent methods, properties, constants
        $this->importConstantsFromAncestorClasses($code_base);

        self::analyzeClassConstantOverrides($code_base, $original_declared_class_constants);
    }

    /**
     * This method must be called before analysis
     * begins.
     *
     * @return void
     */
    protected function hydrateOnce(CodeBase $code_base)
    {
        // Ensure that we hydrate constants before hydrating properties and methods
        $this->hydrateConstants($code_base);

        foreach ($this->getAncestorFQSENList() as $fqsen) {
            if ($code_base->hasClassWithFQSEN($fqsen)) {
                $code_base->getClassByFQSENWithoutHydrating(
                    $fqsen
                )->hydrate($code_base);
            }
        }

        $this->importAncestorClasses($code_base);

        // Make sure there are no abstract methods on non-abstract classes
        AbstractMethodAnalyzer::analyzeAbstractMethodsAreImplemented(
            $code_base,
            $this
        );
    }

    /**
     * @param ClassConstant[] $original_declared_class_constants
     * @return void
     */
    private function analyzeClassConstantOverrides(CodeBase $code_base, array $original_declared_class_constants)
    {
        foreach ($original_declared_class_constants as $constant) {
            if ($constant->isOverrideIntended() && !$constant->getIsOverride()) {
                if ($constant->hasSuppressIssue(Issue::CommentOverrideOnNonOverrideConstant)) {
                    continue;
                }
                $context = $constant->getContext();
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::CommentOverrideOnNonOverrideConstant,
                    $context->getLineNumberStart(),
                    (string)$constant->getFQSEN()
                );
            }
        }
    }

    /**
     * This method should be called after hydration
     *
     * @return void
     */
    final public function analyze(CodeBase $code_base)
    {
        if ($this->isPHPInternal()) {
            return;
        }

        // Make sure the parent classes exist
        ClassInheritanceAnalyzer::analyzeClassInheritance(
            $code_base,
            $this
        );

        DuplicateClassAnalyzer::analyzeDuplicateClass(
            $code_base,
            $this
        );

        ParentConstructorCalledAnalyzer::analyzeParentConstructorCalled(
            $code_base,
            $this
        );

        PropertyTypesAnalyzer::analyzePropertyTypes(
            $code_base,
            $this
        );

        // Analyze this class to make sure that we don't have conflicting
        // types between similar inherited methods.
        CompositionAnalyzer::analyzeComposition(
            $code_base,
            $this
        );

        // Let any configured plugins analyze the class
        ConfigPluginSet::instance()->analyzeClass(
            $code_base,
            $this
        );
    }

    /**
     * @return void
     */
    public function setDidFinishParsing(bool $did_finish_parsing)
    {
        $this->did_finish_parsing = $did_finish_parsing;
    }

    protected $are_constants_hydrated;

    /**
     * This method must be called before analysis
     * begins. It hydrates constants, but not properties/methods.
     *
     * @return void
     */
    protected function hydrateConstants(CodeBase $code_base)
    {
        if (!$this->did_finish_parsing) {
            return;
        }
        if ($this->are_constants_hydrated) {  // Same as isFirstExecution(), inlined due to being called frequently.
            return;
        }
        $this->are_constants_hydrated = true;

        $this->hydrateConstantsOnce($code_base);
    }

    /**
     * This method must be called before analysis begins.
     * This is identical to hydrateConstants(),
     * but returns true only if this is the first time the element was hydrated.
     * (i.e. true if there may be newly added constants)
     */
    public function hydrateConstantsIndicatingFirstTime(CodeBase $code_base) : bool
    {
        if (!$this->did_finish_parsing) {
            return false;
        }
        if ($this->are_constants_hydrated) {  // Same as isFirstExecution(), inlined due to being called frequently.
            return false;
        }
        $this->are_constants_hydrated = true;

        $this->hydrateConstantsOnce($code_base);
        return true;
    }

    /**
     * This method must be called before analysis
     * begins.
     *
     * @return void
     * @override
     */
    public function hydrate(CodeBase $code_base)
    {
        if (!$this->did_finish_parsing) {
            return;
        }
        if ($this->is_hydrated) {  // Same as isFirstExecution(), inlined due to being called frequently.
            return;
        }
        $this->is_hydrated = true;

        $this->hydrateOnce($code_base);
    }

    /**
     * This method must be called before analysis begins.
     * This is identical to hydrate(), but returns true only if this is the first time the element was hydrated.
     */
    private function hydrateIndicatingFirstTime(CodeBase $code_base) : bool
    {
        if (!$this->did_finish_parsing) {
            return false;
        }
        if ($this->is_hydrated) {  // Same as isFirstExecution(), inlined due to being called frequently.
            return false;
        }
        $this->is_hydrated = true;

        $this->hydrateOnce($code_base);
        return true;
    }

    /**
     * @internal - Used by daemon mode to restore an element to the state it had before parsing.
     * @return Closure
     */
    public function createRestoreCallback()
    {
        // NOTE: Properties, Methods, and closures are restored separately.
        $are_constants_hydrated = $this->are_constants_hydrated;
        $is_hydrated = $this->is_hydrated;
        $original_union_type = $this->getUnionType();
        $additional_union_types = $this->additional_union_types;

        return function () use ($original_union_type, $is_hydrated, $are_constants_hydrated, $additional_union_types) {
            $this->memoizeFlushAll();
            $this->are_constants_hydrated = $are_constants_hydrated;
            $this->is_hydrated = $is_hydrated;
            $this->setUnionType($original_union_type);
            $this->additional_union_types = $additional_union_types;
        };
    }

    /**
     * @return void
     */
    public function addAdditionalType(Type $type)
    {
        $this->additional_union_types = ($this->additional_union_types ?? UnionType::empty())->withType($type);
    }

    /**
     * @return ?UnionType
     */
    public function getAdditionalTypes()
    {
        return $this->additional_union_types;
    }

    /**
     * @param array<string,UnionType> $template_parameter_type_map
     */
    public function resolveParentTemplateType(array $template_parameter_type_map) : UnionType
    {
        if (\count($template_parameter_type_map) === 0) {
            return UnionType::empty();
        }
        $parent_type = $this->parent_type;
        if ($parent_type === null) {
            return UnionType::empty();
        }
        if (!$parent_type->hasTemplateParameterTypes()) {
            return UnionType::empty();
        }
        $parent_template_parameter_type_list = $parent_type->getTemplateParameterTypeList();
        $changed = false;
        foreach ($parent_template_parameter_type_list as $i => $template_type) {
            $new_template_type = $template_type->withTemplateParameterTypeMap($template_parameter_type_map);
            if ($template_type === $new_template_type) {
                continue;
            }
            $parent_template_parameter_type_list[$i] = $new_template_type;
            $changed = true;
        }
        if (!$changed) {
            return UnionType::empty();
        }
        return Type::fromType($parent_type, $parent_template_parameter_type_list)->asUnionType();
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Issue;
use Phan\Language\Scope\ClosureScope;
use Phan\Language\Scope\FunctionLikeScope;
use Phan\Language\Context;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\Type;
use Phan\Language\UnionType;
use ast\Node;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class Func extends AddressableElement implements FunctionInterface
{
    use \Phan\Analysis\Analyzable;
    use \Phan\Memoize;
    use FunctionTrait;
    use ClosedScopeElement;

    /**
     * @param Context $context
     * The context in which the structural element lives
     *
     * @param string $name
     * The name of the typed structural element
     *
     * @param UnionType $type
     * A '|' delimited set of types satisfied by this
     * typed structural element.
     *
     * @param int $flags
     * The flags property contains node specific flags. It is
     * always defined, but for most nodes it is always zero.
     * ast\kind_uses_flags() can be used to determine whether
     * a certain kind has a meaningful flags value.
     *
     * @param ?array<int,Parameter> $parameter_list
     * A list of parameters to set on this method
     */
    public function __construct(
        Context $context,
        string $name,
        UnionType $type,
        int $flags,
        FullyQualifiedFunctionName $fqsen,
        $parameter_list
    ) {
        parent::__construct(
            $context,
            $name,
            $type,
            $flags,
            $fqsen
        );

        if ($fqsen->isClosure()) {
            $this->setInternalScope(new ClosureScope(
                $context->getScope(),
                $fqsen
            ));
        } else {
            $this->setInternalScope(new FunctionLikeScope(
                $context->getScope(),
                $fqsen
            ));
        }
        if ($parameter_list !== null) {
            $this->setParameterList($parameter_list);
        }
    }

    /**
     * If a Closure overrides the scope(class) it will be executed in (via doc comment)
     * then return a context with the new scope instead.
     *
     * @param CodeBase $code_base
     * @param Context $context - The outer context in which the closure was declared.
     *                           Either this (or a new context for the other class) will be returned.
     * @return ?FullyQualifiedClassName
     *
     * Postcondition: if return value !== null, then $Type is the type of a class which exists in the codebase.
     */
    private static function getClosureOverrideFQSEN(
        CodeBase $code_base,
        Context $context,
        Type $closure_scope,
        Node $node
    ) {
        if ($node->kind !== \ast\AST_CLOSURE) {
            return null;
        }
        if ($closure_scope->isNativeType()) {
            // TODO: Handle final internal classes (Can't call bindTo on those)
            // TODO: What about 'null' (for code planning to bindTo(null))
            // Emit an error
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::TypeInvalidClosureScope,
                $node->lineno ?? 0,
                (string)$closure_scope
            );
            return null;
        } else {
            // TODO: handle 'parent'?
            // TODO: Check if isInClassScope
            if ($closure_scope->isSelfType() || $closure_scope->isStaticType()) {
                // nothing to do.
                return null;
            }
        }

        $class_fqsen = $closure_scope->asFQSEN();
        if (!($class_fqsen instanceof FullyQualifiedClassName)) {
            // shouldn't happen
            return null;
        }

        return $class_fqsen;
    }


    /**
     * @param Context $context
     * The context in which the node appears
     *
     * @param CodeBase $code_base
     *
     * @param Node $node
     * An AST node representing a function
     *
     * @param FullyQualifiedFunctionName $fqsen
     * A fully qualified name for the function
     *
     * @return Func
     * A Func representing the AST node in the
     * given context
     */
    public static function fromNode(
        Context $context,
        CodeBase $code_base,
        Node $node,
        FullyQualifiedFunctionName $fqsen
    ) : Func {
        // @var array<int,Parameter>
        // The list of parameters specified on the
        // function
        $parameter_list = Parameter::listFromNode(
            $context,
            $code_base,
            $node->children['params']
        );

        // Create the skeleton function object from what
        // we know so far
        $func = new Func(
            $context,
            (string)$node->children['name'],
            UnionType::empty(),
            $node->flags ?? 0,
            $fqsen,
            $parameter_list
        );

        // Parse the comment above the function to get
        // extra meta information about the function.
        $comment = Comment::fromStringInContext(
            (string)$node->children['docComment'],
            $code_base,
            $context,
            $node->lineno ?? 0,
            Comment::ON_FUNCTION
        );

        // Redefine the function's internal scope to point to the new class before adding any variables to the scope.

        $closure_scope_option = $comment->getClosureScopeOption();
        if ($closure_scope_option->isDefined()) {
            $override_class_fqsen = self::getClosureOverrideFQSEN($code_base, $context, $closure_scope_option->get(), $node);
            if ($override_class_fqsen !== null) {
                // TODO: Allow Null?
                $scope = $func->getInternalScope();
                assert($scope instanceof ClosureScope);
                $scope->overrideClassFQSEN($override_class_fqsen);
                $func->getContext()->setScope($scope);
            }
        }

        // Add each parameter to the scope of the function
        // NOTE: it's important to clone this,
        // because we don't want anything to modify the original Parameter
        foreach ($parameter_list as $parameter) {
            $func->getInternalScope()->addVariable(
                $parameter->cloneAsNonVariadic()
            );
        }

        if (!$func->isPHPInternal()) {
            // If the function is Analyzable, set the node so that
            // we can come back to it whenever we like and
            // rescan it
            $func->setNode($node);
        }

        // Keep an copy of the original parameter list, to check for fatal errors later on.
        $func->setRealParameterList($parameter_list);

        $func->setNumberOfRequiredParameters(\array_reduce(
            $parameter_list,
            function (int $carry, Parameter $parameter) : int {
                return ($carry + ($parameter->isRequired() ? 1 : 0));
            },
            0
        ));

        $func->setNumberOfOptionalParameters(\array_reduce(
            $parameter_list,
            function (int $carry, Parameter $parameter) : int {
                return ($carry + ($parameter->isOptional() ? 1 : 0));
            },
            0
        ));

        // Check to see if the comment specifies that the
        // function is deprecated
        $func->setIsDeprecated($comment->isDeprecated());

        // Set whether or not the element is internal to
        // the namespace.
        $func->setIsNSInternal($comment->isNSInternal());

        $func->setSuppressIssueList(
            $comment->getSuppressIssueList()
        );

        // Take a look at function return types
        if ($node->children['returnType'] !== null) {
            // Get the type of the parameter
            $union_type = UnionTypeVisitor::unionTypeFromNode(
                $code_base,
                $context,
                $node->children['returnType']
            );
            $func->setRealReturnType($union_type);

            $func->setUnionType($func->getUnionType()->withUnionType($union_type));
        }

        if ($comment->hasReturnUnionType()) {
            // See if we have a return type specified in the comment
            $union_type = $comment->getReturnType();

            \assert(
                !$union_type->hasSelfType(),
                "Function referencing self in $context"
            );

            $func->setUnionType($func->getUnionType()->withUnionType($union_type));
            $func->setPHPDocReturnType($union_type);
        }

        // Defer adding params to the local scope for user functions. (FunctionTrait::addParamsToScopeOfFunctionOrMethod)
        // See PreOrderAnalysisVisitor->visitFuncDecl and visitClosure
        $func->setComment($comment);

        return $func;
    }

    /**
     * @return FullyQualifiedFunctionName
     */
    public function getFQSEN() : FullyQualifiedFunctionName
    {
        return $this->fqsen;
    }

    /**
     * @return \Generator
     * The set of all alternates to this function
     */
    public function alternateGenerator(CodeBase $code_base) : \Generator
    {
        $alternate_id = 0;
        $fqsen = $this->getFQSEN();

        while ($code_base->hasFunctionWithFQSEN($fqsen)) {
            yield $code_base->getFunctionByFQSEN($fqsen);
            $fqsen = $fqsen->withAlternateId(++$alternate_id);
        }
    }

    /**
     * @return string
     * A string representation of this function signature
     */
    public function __toString() : string
    {
        $string = '';

        $string .= 'function ' . $this->getName();

        $string .= '(' . implode(', ', $this->getParameterList()) . ')';

        if (!$this->getUnionType()->isEmpty()) {
            $string .= ' : ' . (string)$this->getUnionType();
        }

        $string .= ';';

        return $string;
    }

    /**
     * @return bool
     * True if this function returns a reference
     */
    public function returnsRef() : bool
    {
        return $this->getFlagsHasState(\ast\flags\RETURNS_REF);
    }

    /**
     * @return bool Always false for global functions.
     */
    public function isFromPHPDoc() : bool
    {
        return false;
    }

    /**
     * @suppress PhanUnreferencedPublicMethod (toStubInfo is used by callers for more flexibility)
     */
    public function toStub() : string
    {
        list($namespace, $string) = $this->toStubInfo();
        $namespace_text = $namespace === '' ? '' : "$namespace ";
        $string = sprintf("namespace %s{\n%s}\n", $namespace_text, $string);
        return $string;
    }

    /** @return array{0:string,1:string} [string $namespace, string $text] */
    public function toStubInfo() : array
    {
        $fqsen = $this->getFQSEN();
        $stub = 'function ';
        if ($this->returnsRef()) {
            $stub .= '&';
        }
        $stub .= $fqsen->getName();
        $stub .= '(' . implode(', ', array_map(function (Parameter $parameter) : string {
            return $parameter->toStubString();
        }, $this->getRealParameterList())) . ')';
        if ($this->real_return_type && !$this->getRealReturnType()->isEmpty()) {
            $stub .= ' : ' . (string)$this->getRealReturnType();
        }

        $stub .= ' {}' . "\n";

        $namespace = ltrim($fqsen->getNamespace(), '\\');
        return [$namespace, $stub];
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\CodeBase;
use Phan\Language\FQSEN;
use Phan\Language\FileRef;

interface AddressableElementInterface extends TypedElementInterface
{
    /**
     * @return FQSEN
     * The fully-qualified structural element name of this
     * structural element
     */
    public function getFQSEN();

    /**
     * @param FQSEN $fqsen
     * A fully qualified structural element name to set on
     * this element
     *
     * @return void
     */
    public function setFQSEN(FQSEN $fqsen);

    /**
     * @return bool
     * True if this is a public property
     */
    public function isPublic() : bool;

    /**
     * @return bool
     * True if this is a protected property
     */
    public function isProtected() : bool;

    /**
     * @return bool
     * True if this is a private property
     */
    public function isPrivate() : bool;

    /**
     * @param FileRef $file_ref
     * A reference to a location in which this typed structural
     * element is referenced.
     *
     * @return void
     */
    public function addReference(FileRef $file_ref);

    /**
     * @return FileRef[]
     * A list of references to this typed structural element.
     */
    public function getReferenceList() : array;

    /**
     * @param CodeBase $code_base
     * Some elements may need access to the code base to
     * figure out their total reference count.
     *
     * @return int
     * The number of references to this typed structural element
     */
    public function getReferenceCount(
        CodeBase $code_base
    ) : int;
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use ast\Node;
use Closure;

trait ConstantTrait
{
    /** @var Node|string|float|int */
    protected $defining_node;

    use ElementFutureUnionType;

    /**
     * @return string
     * The (not fully-qualified) name of this element.
     */
    abstract public function getName() : string;

    public function __toString() : string
    {
        return 'const ' . $this->getName();
    }

    /**
     * Sets the node with the AST representing the value of this constant.
     *
     * @param Node|string|float|int $node Either a node or a constant to be used as the value of the constant.
     * @return void
     */
    public function setNodeForValue($node)
    {
        $this->defining_node = $node;
    }

    /**
     * Gets the node with the AST representing the value of this constant.
     *
     * @return Node|string|float|int
     */
    public function getNodeForValue()
    {
        return $this->defining_node;
    }

    /**
     * @internal - Used by daemon mode to restore an element to the state it had before parsing.
     * @return ?Closure
     */
    public function createRestoreCallback()
    {
        $future_union_type = $this->future_union_type;
        if ($future_union_type === null) {
            // We already inferred the type for this class constant/global constant.
            // Nothing to do.
            return null;
        }
        // If this refers to a class constant in another file,
        // the resolved union type might change if that file changes.
        return function () use ($future_union_type) {
            $this->future_union_type = $future_union_type;
            // Probably don't need to call setUnionType(mixed) again...
        };
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\Context;
use Phan\Language\FQSEN\FullyQualifiedGlobalConstantName;
use Phan\Language\Type;
use Phan\Language\UnionType;

class GlobalConstant extends AddressableElement implements ConstantInterface
{
    use ConstantTrait;

    /**
     * Override the default getter to fill in a future
     * union type if available.
     *
     * @return UnionType
     */
    public function getUnionType() : UnionType
    {
        if (null !== ($union_type = $this->getFutureUnionType())) {
            $this->setUnionType($this->getUnionType()->withUnionType($union_type));
        }

        return parent::getUnionType();
    }

    /**
     * @return FullyQualifiedGlobalConstantName
     * The fully-qualified structural element name of this
     * structural element
     */
    public function getFQSEN() : FullyQualifiedGlobalConstantName
    {
        \assert(!empty($this->fqsen), "FQSEN must be defined");
        return $this->fqsen;
    }

    /**
     * @param string $name
     * The name of a builtin constant to build a new GlobalConstant structural
     * element from.
     *
     * @return GlobalConstant
     * A GlobalConstant structural element representing the given named
     * builtin constant.
     */
    public static function fromGlobalConstantName(
        string $name
    ) : GlobalConstant {
        if (!\defined($name)) {
            throw new \InvalidArgumentException(sprintf("This should not happen, defined(%s) is false, but the constant was returned by get_defined_constants()", var_export($name, true)));
        }
        $value = \constant($name);
        $constant_fqsen = FullyQualifiedGlobalConstantName::fromFullyQualifiedString(
            '\\' . $name
        );
        $result = new self(
            new Context(),
            $name,
            Type::fromObject($value)->asUnionType(),
            0,
            $constant_fqsen
        );
        $result->setNodeForValue($value);
        return $result;
    }

    /**
     * @suppress PhanUnreferencedPublicMethod toStubInfo is used by callers instead
     */
    public function toStub() : string
    {
        list($namespace, $string) = $this->toStubInfo();
        $namespace_text = $namespace === '' ? '' : "$namespace ";
        $string = sprintf("namespace %s{\n%s}\n", $namespace_text, $string);
        return $string;
    }

    /** @return array{0:string,1:string} [string $namespace, string $text] */
    public function toStubInfo() : array
    {
        $fqsen = (string)$this->getFQSEN();
        $pos = \strrpos($fqsen, '\\');
        if ($pos !== false) {
            $name = \substr($fqsen, $pos + 1);
            $namespace = \substr($fqsen, 0, $pos);
        } else {
            $name = $fqsen;
            $namespace = '';
        }

        $is_defined = \defined($fqsen);
        if ($is_defined) {
            $repr = \var_export(constant($fqsen), true);
            $comment = '';
        } else {
            $repr = 'null';
            $comment = '  // could not find';
        }
        $namespace = \ltrim($this->getFQSEN()->getNamespace(), '\\');
        if (\preg_match('@[a-zA-Z_\x7f-\xff\\\][a-zA-Z0-9_\x7f-\xff\\\]*@', $name)) {
            $string = "const $name = $repr;$comment\n";
        } else {
            // Internal extension defined a constant with an invalid identifier.
            $string = \sprintf("define(%s, %s);%s\n", var_export($name, true), $repr, $comment);
        }
        return [$namespace, $string];
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\Context;
use Phan\Language\FQSEN\FullyQualifiedPropertyName;
use Phan\Language\Scope\PropertyScope;
use Phan\Language\UnionType;

use Closure;

class Property extends ClassElement
{
    use ElementFutureUnionType;
    use ClosedScopeElement;

    /**
     * @var ?FullyQualifiedPropertyName If this was originally defined in a trait, this is the trait's defining fqsen.
     * This is tracked separately from getDefiningFQSEN() in order to not break access checks on protected/private properties.
     * Used for dead code detection.
     */
    private $real_defining_fqsen;

    /**
     * @param Context $context
     * The context in which the structural element lives
     *
     * @param string $name
     * The name of the typed structural element
     *
     * @param UnionType $type
     * A '|' delimited set of types satisfied by this
     * typed structural element.
     *
     * @param int $flags
     * The flags property contains node specific flags. It is
     * always defined, but for most nodes it is always zero.
     * ast\kind_uses_flags() can be used to determine whether
     * a certain kind has a meaningful flags value.
     */
    public function __construct(
        Context $context,
        string $name,
        UnionType $type,
        int $flags,
        FullyQualifiedPropertyName $fqsen
    ) {
        parent::__construct(
            $context,
            $name,
            $type,
            $flags,
            $fqsen
        );

        // Presume that this is the original definition
        // of this property, and let it be overwritten
        // if it isn't.
        $this->setDefiningFQSEN($fqsen);
        $this->real_defining_fqsen = $fqsen;

        // Set an internal scope, so that issue suppressions can be placed on property doc comments.
        // (plugins acting on properties would then pick those up).
        // $fqsen is used to locate this property.
        $this->setInternalScope(new PropertyScope(
            $context->getScope(),
            $fqsen
        ));
    }

    /**
     * @return FullyQualifiedPropertyName the FQSEN with the original definition (Even if this is private/protected and inherited from a trait). Used for dead code detection.
     *                                    Inheritance tests use getDefiningFQSEN() so that access checks won't break.
     *
     * @suppress PhanUnreferencedPublicMethod this is used, but the invocation could be one of multiple classes.
     * @suppress PhanPartialTypeMismatchReturn TODO: Allow subclasses to make property types more specific
     */
    public function getRealDefiningFQSEN() : FullyQualifiedPropertyName
    {
        return $this->real_defining_fqsen ?? $this->getDefiningFQSEN();
    }

    public function __toString() : string
    {
        $string = '';

        if ($this->isPublic()) {
            $string .= 'public ';
        } elseif ($this->isProtected()) {
            $string .= 'protected ';
        } elseif ($this->isPrivate()) {
            $string .= 'private ';
        }

        if ($this->isStatic()) {
            $string .= 'static ';
        }

        // Since the UnionType can be a future, and that
        // can throw an exception, we catch it and ignore it
        try {
            $union_type = $this->getUnionType();
        } catch (\Exception $exception) {
            $union_type = UnionType::empty();
        }

        $string .= "$union_type \${$this->getName()}";


        return $string;
    }

    /**
     * Override the default getter to fill in a future
     * union type if available.
     */
    public function getUnionType() : UnionType
    {
        if (null !== ($union_type = $this->getFutureUnionType())) {
            $this->setUnionType(parent::getUnionType()->withUnionType($union_type));
        }

        return parent::getUnionType();
    }

    /**
     * @return FullyQualifiedPropertyName
     * The fully-qualified structural element name of this
     * structural element
     */
    public function getFQSEN() : FullyQualifiedPropertyName
    {
        \assert(!empty($this->fqsen), "FQSEN must be defined");
        return $this->fqsen;
    }

    public function toStub()
    {
        $string = '    ';

        if ($this->isPublic()) {
            $string .= 'public ';
        } elseif ($this->isProtected()) {
            $string .= 'protected ';
        } elseif ($this->isPrivate()) {
            $string .= 'private ';
        }

        if ($this->isStatic()) {
            $string .= 'static ';
        }

        $string .= "\${$this->getName()}";
        $string .= ';';

        return $string;
    }

    /**
     * @internal - Used by daemon mode to restore an element to the state it had before parsing.
     * @return ?Closure
     */
    public function createRestoreCallback()
    {
        $future_union_type = $this->future_union_type;
        if ($future_union_type === null) {
            // We already inferred the type for this class constant/global constant.
            // Nothing to do.
            return null;
        }
        // If this refers to a class constant in another file,
        // the resolved union type might change if that file changes.
        return function () use ($future_union_type) {
            $this->future_union_type = $future_union_type;
            // Probably don't need to call setUnionType(mixed) again...
        };
    }

    /**
     * Returns true if at least one of the references to this property was **reading** the property
     *
     * Precondition: Config::get_track_references() === true
     */
    public function hasReadReference() : bool
    {
        return $this->getPhanFlagsHasState(Flags::WAS_PROPERTY_READ);
    }

    /**
     * @return void
     */
    public function setHasReadReference()
    {
        $this->enablePhanFlagBits(Flags::WAS_PROPERTY_READ);
    }

    /**
     * Copy addressable references from an element of the same subclass
     * @override
     * @return void
     */
    public function copyReferencesFrom(AddressableElement $element)
    {
        if ($this === $element) {
            // Should be impossible
            return;
        }
        \assert($element instanceof Property);
        foreach ($element->reference_list as $key => $file_ref) {
            $this->reference_list[$key] = $file_ref;
        }
        if ($element->hasReadReference()) {
            $this->setHasReadReference();
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\AST\ContextNode;
use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\IssueException;
use Phan\Language\Context;
use Phan\Language\Type;
use Phan\Language\UnionType;
use ast\Node;

class Variable extends UnaddressableTypedElement
{
    /**
     * @access private
     * @var array<string,string> - Maps from a built in superglobal name to a UnionType spec string.
     * The string array keys **can** be integers in edge cases, but those should be rare.
     * (e.g. ${4} = 'x'; adds 4 to $GLOBALS.
     * And user-provided input of integer representations of strings as keys would also become integers.
     */
    const _BUILTIN_SUPERGLOBAL_TYPES = [
        '_GET' => 'array<string,string|string[]>',
        '_POST' => 'array<string,string|string[]>',
        '_COOKIE' => 'array<string,string|string[]>',
        '_REQUEST' => 'array<string,string|string[]>',
        '_SERVER' => 'array<string,mixed>',
        '_ENV' => 'array<string,string>',
        '_FILES' => 'array<string,array<string,int|string|array<string,int|string>>>',  // Can have multiple files with the same name.
        '_SESSION' => 'array<string,mixed>',
        'GLOBALS' => 'array<string,mixed>',
        'http_response_header' => 'array<int,string>|null', // Revisit when we implement sub-block type refining
    ];

    /**
     * @var array<string,string>
     *
     * NOTE: The string array keys of superglobals **can** be integers in edge cases, but those should be rare.
     * (e.g. ${4} = 'x'; adds 4 to $GLOBALS.
     * And user-provided input of integer representations of strings as keys would also become integers.
     */
    const _BUILTIN_GLOBAL_TYPES = [
        '_GET' => 'array<string,string|string[]>',
        '_POST' => 'array<string,string|string[]>',
        '_COOKIE' => 'array<string,string|string[]>',
        '_REQUEST' => 'array<string,string|string[]>',
        '_SERVER' => 'array<string,mixed>',
        '_ENV' => 'array<string,string>',
        '_FILES' => 'array<string,array<string,int>>|array<string,array<string,string>>|array<string,array<string,array<int,int>>>|array<string,array<string,array<int,string>>>',  // Can have multiple files with the same name.
        '_SESSION' => 'array<string,mixed>',
        'GLOBALS' => 'array<string,mixed>',
        'http_response_header' => 'array<int,string>|null', // Revisit when we implement sub-block type refining
        'argv' => 'array<int,string>',
        'argc' => 'int',
    ];

    /**
    public function __construct(
        FileRef $file_ref,
        string $name,
        UnionType $type,
        int $flags
    ) {
        parent::__construct(
            $file_ref,
            $name,
            $type,
            $flags
        );
    }
     */

    /**
     * @return bool
     * This will always return false in so far as variables
     * cannot be passed by reference.
     */
    public function isPassByReference()
    {
        return false;
    }

    /**
     * @return bool
     * This will always return false in so far as variables
     * cannot be variadic
     */
    public function isVariadic()
    {
        return false;
    }

    /**
     * @param Node $node
     * An AST_VAR node
     *
     * @param Context $context
     * The context in which the variable is found
     *
     * @param CodeBase $code_base
     *
     * @return Variable
     * A variable begotten from a node
     *
     * @throws IssueException
     */
    public static function fromNodeInContext(
        Node $node,
        Context $context,
        CodeBase $code_base,
        bool $should_check_type = true
    ) : Variable {

        $variable_name = (new ContextNode(
            $code_base,
            $context,
            $node
        ))->getVariableName();


        // Get the type of the assignment
        $union_type = $should_check_type
            ? UnionTypeVisitor::unionTypeFromNode($code_base, $context, $node)
            : UnionType::empty();

        $variable = new Variable(
            $context
                ->withLineNumberStart($node->lineno ?? 0),
            $variable_name,
            $union_type,
            $node->flags ?? 0
        );

        return $variable;
    }

    /**
     * @return bool
     * True if the variable with the given name is a
     * superglobal
     * Implies Variable::isHardcodedGlobalVariableWithName($name) is true
     */
    public static function isSuperglobalVariableWithName(
        string $name
    ) : bool {
        if (\array_key_exists($name, self::_BUILTIN_SUPERGLOBAL_TYPES)) {
            return true;
        }
        return \in_array($name, Config::getValue('runkit_superglobals'));
    }

    /**
     * Returns true for all superglobals and variables in globals_type_map.
     */
    public static function isHardcodedGlobalVariableWithName(
        string $name
    ) : bool {
        return self::isSuperglobalVariableWithName($name) ||
            \array_key_exists($name, self::_BUILTIN_GLOBAL_TYPES) ||
            \array_key_exists($name, Config::getValue('globals_type_map'));
    }

    /**
     * Returns true for all superglobals (if is_in_global_scope, also for variables in globals_type_map/built in globals)
     */
    public static function isHardcodedVariableInScopeWithName(
        string $name,
        bool $is_in_global_scope
    ) : bool {
        return $is_in_global_scope ? self::isHardcodedGlobalVariableWithName($name)
                                   : self::isSuperglobalVariableWithName($name);
    }

    /**
     * @return ?UnionType
     * Returns UnionType (Possible with empty set) if and only
     * if isHardcodedGlobalVariableWithName is true. Returns null
     * otherwise.
     */
    public static function getUnionTypeOfHardcodedGlobalVariableWithName(
        string $name
    ) {
        if (\array_key_exists($name, self::_BUILTIN_GLOBAL_TYPES)) {
            // More efficient than using context.
            return UnionType::fromFullyQualifiedString(self::_BUILTIN_GLOBAL_TYPES[$name]);
        }

        if (\array_key_exists($name, Config::getValue('globals_type_map'))
            || \in_array($name, Config::getValue('runkit_superglobals'))
        ) {
            $type_string = Config::getValue('globals_type_map')[$name] ?? '';
            // Want to allow 'resource' or 'mixed' as a type here,
            return UnionType::fromStringInContext($type_string, new Context(), Type::FROM_PHPDOC);
        }

        return null;
    }

    /**
     * Variables can't be variadic. This is the same as
     * getUnionType for variables, but not necessarily
     * for subclasses. Method will return the element
     * type (such as `DateTime`) for variadic parameters.
     */
    public function getNonVariadicUnionType() : UnionType
    {
        return parent::getUnionType();
    }

    /**
     * @return static - A clone of this object, where isVariadic() is false
     * Used for analyzing the context **inside** of this method
     */
    public function cloneAsNonVariadic()
    {
        return clone($this);
    }

    public function __toString() : string
    {
        $string = '';

        if (!$this->getUnionType()->isEmpty()) {
            $string .= "{$this->getUnionType()} ";
        }

        return "$string\${$this->getName()}";
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\Scope\ClosedScope;

/**
 * A trait for closed scope elements (classes, functions, methods,
 * closures).
 */
trait ClosedScopeElement
{

    /**
     * @var ClosedScope
     */
    private $internal_scope;

    /**
     * @return void
     */
    public function setInternalScope(ClosedScope $internal_scope)
    {
        $this->internal_scope = $internal_scope;
    }

    /**
     * @return ClosedScope
     * The internal scope of this closed scope element
     */
    public function getInternalScope() : ClosedScope
    {
        return $this->internal_scope;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\Context;
use Phan\Language\FQSEN\FullyQualifiedClassConstantName;
use Phan\Language\UnionType;

class ClassConstant extends ClassElement implements ConstantInterface
{
    use ConstantTrait;

    /**
     * @param Context $context
     * The context in which the structural element lives
     *
     * @param string $name
     * The name of the typed structural element
     *
     * @param UnionType $type
     * A '|' delimited set of types satisfied by this
     * typed structural element.
     *
     * @param int $flags
     * The flags property contains node specific flags. It is
     * always defined, but for most nodes it is always zero.
     * ast\kind_uses_flags() can be used to determine whether
     * a certain kind has a meaningful flags value.
     *
     * @param FullyQualifiedClassConstantName $fqsen
     * A fully qualified name for the class constant
     */
    public function __construct(
        Context $context,
        string $name,
        UnionType $type,
        int $flags,
        FullyQualifiedClassConstantName $fqsen
    ) {
        parent::__construct(
            $context,
            $name,
            $type,
            $flags,
            $fqsen
        );

        // Presume that this is the original definition
        // of this class constant, and let it be overwritten
        // if it isn't.
        $this->setDefiningFQSEN($fqsen);
    }

    /**
     * Override the default getter to fill in a future
     * union type if available.
     *
     * @return UnionType
     */
    public function getUnionType() : UnionType
    {
        if (null !== ($union_type = $this->getFutureUnionType())) {
            $this->setUnionType($this->getUnionType()->withUnionType($union_type));
        }

        return parent::getUnionType();
    }

    /**
     * @return FullyQualifiedClassConstantName
     * The fully-qualified structural element name of this
     * structural element
     */
    public function getFQSEN() : FullyQualifiedClassConstantName
    {
        \assert(!empty($this->fqsen), "FQSEN must be defined");
        return $this->fqsen;
    }

    public function __toString() : string
    {
        $string = '';

        if ($this->isPublic()) {
            $string .= 'public ';
        } elseif ($this->isProtected()) {
            $string .= 'protected ';
        } elseif ($this->isPrivate()) {
            $string .= 'private ';
        }

        return $string . 'const ' . $this->getName();
    }

    /**
     * @return bool
     * True if this class constant is intended to be an override of another class constant (contains (at)override)
     */
    public function isOverrideIntended() : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_OVERRIDE_INTENDED);
    }

    /**
     * @param bool $is_override_intended - True if this class constant is intended to be an override of another class constant (contains (at)override)

     * @return void
     */
    public function setIsOverrideIntended(bool $is_override_intended)
    {
        $this->setPhanFlags(
            Flags::bitVectorWithState(
                $this->getPhanFlags(),
                Flags::IS_OVERRIDE_INTENDED,
                $is_override_intended
            )
        );
    }

    public function toStub() : string
    {
        $string = '    ';

        if ($this->isPublic()) {
            $string .= 'public ';
        } elseif ($this->isProtected()) {
            $string .= 'protected ';
        } elseif ($this->isPrivate()) {
            $string .= 'private ';
        }

        $string .= 'const ' . $this->getName() . ' = ';
        $fqsen = (string)$this->getFQSEN();
        if (defined($fqsen)) {
            // TODO: Could start using $this->getNodeForValue()?
            $string .= var_export(constant($fqsen), true) . ';';
        } else {
            $string .= "null;  // could not find";
        }
        return $string;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\CodeBase;
use Phan\Config;
use Phan\Language\Context;
use Phan\Language\FQSEN;
use Phan\Language\FQSEN\FullyQualifiedGlobalStructuralElement;
use Phan\Language\FileRef;
use Phan\Language\UnionType;
use Closure;

abstract class AddressableElement extends TypedElement implements AddressableElementInterface
{
    use \Phan\Memoize;

    /**
     * @var FQSEN
     */
    protected $fqsen;

    /**
     * @var array<string,FileRef>
     * A list of locations in which this typed structural
     * element is referenced from.
     * References from the same file and line are deduplicated to save memory.
     */
    protected $reference_list = [];

    /**
     * @param Context $context
     * The context in which the structural element lives
     *
     * @param string $name
     * The name of the typed structural element
     *
     * @param UnionType $type
     * A '|' delimited set of types satisfied by this
     * typed structural element.
     *
     * @param int $flags
     * The flags property contains node specific flags. It is
     * always defined, but for most nodes it is always zero.
     * ast\kind_uses_flags() can be used to determine whether
     * a certain kind has a meaningful flags value.
     *
     * @param FQSEN $fqsen
     * A fully qualified name for the element
     */
    public function __construct(
        Context $context,
        string $name,
        UnionType $type,
        int $flags,
        FQSEN $fqsen
    ) {
        parent::__construct(
            $context,
            $name,
            $type,
            $flags
        );

        $this->setFQSEN($fqsen);
    }

    /**
     * @return FQSEN
     * The fully-qualified structural element name of this
     * structural element
     */
    public function getFQSEN()
    {
        \assert(!empty($this->fqsen), "FQSEN must be defined");
        return $this->fqsen;
    }

    /**
     * @param FQSEN $fqsen
     * A fully qualified structural element name to set on
     * this element
     *
     * @return void
     */
    public function setFQSEN(FQSEN $fqsen)
    {
        $this->fqsen = $fqsen;
    }

    /**
     * @return bool
     * True if this is a public property
     */
    public function isPublic() : bool
    {
        return !(
            $this->isProtected() || $this->isPrivate()
        );
    }

    /**
     * @return bool
     * True if this is a protected element
     */
    public function isProtected() : bool
    {
        return $this->getFlagsHasState(\ast\flags\MODIFIER_PROTECTED);
    }

    /**
     * @return bool
     * True if this is a private element
     */
    public function isPrivate() : bool
    {
        return $this->getFlagsHasState(\ast\flags\MODIFIER_PRIVATE);
    }

    /**
     * @param CodeBase $code_base (@phan-unused-param, this is used by subclasses)
     * The code base in which this element exists.
     *
     * @return bool
     * True if this is marked as an `(at)internal` element
     */
    public function isNSInternal(CodeBase $code_base) : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_NS_INTERNAL);
    }

    /**
     * Set this element as being `internal`.
     * @return void
     */
    public function setIsNSInternal(bool $is_internal)
    {
        $this->setPhanFlags(Flags::bitVectorWithState(
            $this->getPhanFlags(),
            Flags::IS_NS_INTERNAL,
            $is_internal
        ));
    }

    /**
     * @param CodeBase $code_base (@phan-unused-param, this is used by subclasses)
     * The code base in which this element exists.
     *
     * @return bool
     * True if this element is intern
     */
    public function isNSInternalAccessFromContext(
        CodeBase $code_base,
        Context $context
    ) : bool {
        // Figure out which namespace this element is within
        $element_namespace = $this->getElementNamespace();

        // Get our current namespace from the context
        $context_namespace = $context->getNamespace();

        // Test to see if the context is within the same
        // namespace as where the element is defined
        return (0 === strcasecmp($context_namespace, $element_namespace));
    }

    /**
     * @param FileRef $file_ref
     * A reference to a location in which this typed structural
     * element is referenced.
     *
     * @return void
     */
    public function addReference(FileRef $file_ref)
    {
        if (Config::get_track_references()) {
            // Currently, we don't need to track references to PHP-internal methods/functions/constants
            // such as PHP_VERSION, strlen(), Closure::bind(), etc.
            // This may change in the future.
            if ($this->isPHPInternal()) {
                return;
            }
            $this->reference_list[$file_ref->__toString()] = $file_ref;
        }
    }

    /**
     * Copy addressable references from an element of the same subclass
     * @return void
     */
    public function copyReferencesFrom(AddressableElement $element)
    {
        if ($this === $element) {
            // Should be impossible
            return;
        }
        foreach ($element->reference_list as $key => $file_ref) {
            $this->reference_list[$key] = $file_ref;
        }
    }

    /**
     * @return array<string,FileRef>
     * A list of references to this typed structural element.
     * @suppress PhanPartialTypeMismatchReturn TODO: investigate
     */
    public function getReferenceList() : array
    {
        return $this->reference_list;
    }

    /**
     * @param CodeBase $code_base (@phan-unused-param)
     * Some elements may need access to the code base to
     * figure out their total reference count.
     *
     * @return int
     * The number of references to this typed structural element
     */
    public function getReferenceCount(
        CodeBase $code_base
    ) : int {
        return \count($this->reference_list);
    }

    /** @var bool */
    protected $is_hydrated = false;

    /**
     * This method must be called before analysis
     * begins.
     *
     * @return void
     * @override
     */
    public function hydrate(CodeBase $code_base)
    {
        if ($this->is_hydrated) {  // Same as isFirstExecution(), inlined due to being called frequently.
            return;
        }
        $this->is_hydrated = true;

        $this->hydrateOnce($code_base);
    }

    protected function hydrateOnce(CodeBase $unused_code_base)
    {
        // Do nothing unless overridden
    }

    public function getElementNamespace() : string
    {
        $element_fqsen = $this->getFQSEN();
        \assert($element_fqsen instanceof FullyQualifiedGlobalStructuralElement);

        // Figure out which namespace this element is within
        return $element_fqsen->getNamespace();
    }

    /**
     * @internal - Used by daemon mode to restore an element to the state it had before parsing.
     * @return ?Closure
     */
    abstract public function createRestoreCallback();
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\Type\FunctionLikeDeclarationType;
use Phan\Language\UnionType;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\Scope\ClosedScope;
use ast\Node;

/**
 * Interface defining the behavior of both Methods
 *  and Functions
 */
interface FunctionInterface extends AddressableElementInterface
{
    /**
     * An easy workaround to mark a functionlike as accepting an infinite number of optional parameters
     * TODO: Distinguish between __call and __callStatic invoked manually and via magic (See uses of this constant)
     */
    const INFINITE_PARAMETERS = 999999;

    /**
     * @return FullyQualifiedMethodName|FullyQualifiedFunctionName
     * The fully-qualified structural element name of this
     * structural element
     */
    public function getFQSEN();

    /**
     * @return string
     * The fully-qualified structural element name of this
     * structural element, or a string for FunctionLikeDeclarationType which lacks a real FQSEN
     */
    public function getRepresentationForIssue() : string;

    /**
     * @return void
     */
    public function setInternalScope(ClosedScope $internal_scope);

    /**
     * @return ClosedScope
     * The internal scope of this closed scope element
     */
    public function getInternalScope() : ClosedScope;

    /**
     * @return int
     * The number of optional parameters on this method
     */
    public function getNumberOfOptionalParameters() : int;

    /**
     * The number of optional parameters
     *
     * @return void
     */
    public function setNumberOfOptionalParameters(int $number);

    /**
     * @return int
     * The number of optional real parameters on this function/method.
     * May differ from getNumberOfOptionalParameters()
     * for internal modules lacking proper reflection info,
     * or if the installed module version's API changed from what Phan's stubs used,
     * or if a function/method uses variadics/func_get_arg*()
     */
    public function getNumberOfOptionalRealParameters() : int;

    /**
     * @return int
     * The maximum number of parameters to this method
     */
    public function getNumberOfParameters() : int;

    /**
     * @return int
     * The number of required parameters on this method
     */
    public function getNumberOfRequiredParameters() : int;

    /**
     * The number of required parameters
     *
     * @return void
     */
    public function setNumberOfRequiredParameters(int $number);

    /**
     * @return int
     * The number of required real parameters on this function/method.
     * May differ for internal modules lacking proper reflection info,
     * or if the installed module version's API changed from what Phan's stubs used.
     */
    public function getNumberOfRequiredRealParameters() : int;

    /**
     * @return bool
     * True if this method had no return type defined when it
     * was defined (either in the signature itself or in the
     * docblock).
     */
    public function isReturnTypeUndefined() : bool;

    /**
     * @param bool $is_return_type_undefined
     * True if this method had no return type defined when it
     * was defined (either in the signature itself or in the
     * docblock).
     *
     * @return void
     */
    public function setIsReturnTypeUndefined(
        bool $is_return_type_undefined
    );

    /**
     * @return bool
     * True if this method returns a value
     */
    public function getHasReturn() : bool;
    /**
     * @param bool $has_return
     * Set to true to mark this method as having a
     * return value (Only through `return`)
     *
     * @return void
     */
    public function setHasReturn(bool $has_return);

    /**
     * @param bool $has_yield
     * Set to true to mark this method as having a
     * yield statement (Only through `yield`)
     * This implies that it has a return value of \Generator.
     * (or a parent interface)
     *
     * @return void
     */
    public function setHasYield(bool $has_yield);

    /**
     * @return array<int,Parameter>
     * A list of parameters on the method
     */
    public function getParameterList();

    /**
     * Gets the $ith parameter for the **caller**.
     * In the case of variadic arguments, an infinite number of parameters exist.
     * (The callee would see variadic arguments(T ...$args) as a single variable of type T[],
     * while the caller sees a place expecting an expression of type T.
     *
     * @param int $i - offset of the parameter.
     * @return Parameter|null The parameter type that the **caller** observes.
     */
    public function getParameterForCaller(int $i);

    /**
     * @param Parameter $parameter
     * A parameter to append to the parameter list
     *
     * @return void
     */
    public function appendParameter(Parameter $parameter);

    /**
     * @return void
     *
     * Call this before calling appendParameter, if parameters were already added.
     */
    public function clearParameterList();

    /**
     * Records the fact that $parameter_name is an output-only reference.
     * @param string $parameter_name
     * @return void
     */
    public function recordOutputReferenceParamName(string $parameter_name);

    /**
     * @return array<int,string> list of output references (annotated with (at)phan-output-reference. Usually empty.
     */
    public function getOutputReferenceParamNames() : array;

    /**
     * @return \Generator
     * The set of all alternates to this function
     */
    public function alternateGenerator(CodeBase $code_base) : \Generator;

    /**
     * @param CodeBase $code_base
     * The code base in which this element exists.
     *
     * @return bool
     * True if this is marked as an element internal to
     * its namespace
     */
    public function isNSInternal(CodeBase $code_base) : bool;

    /**
     * @param CodeBase $code_base
     * The code base in which this element exists.
     *
     * @return bool
     * True if this element is internal to the namespace
     */
    public function isNSInternalAccessFromContext(
        CodeBase $code_base,
        Context $context
    ) : bool;

    /**
     * @return Context
     * Analyze the node associated with this object
     * in the given context
     */
    public function analyze(Context $context, CodeBase $code_base) : Context;

    /**
     * @return Context
     * Analyze the node associated with this object
     * in the given context.
     * This function's parameter list may or may not have been modified.
     */
    public function analyzeWithNewParams(Context $context, CodeBase $code_base, array $parameter_list) : Context;

    public function getElementNamespace() : string;

    /**
     * @return UnionType
     * The type of this method in its given context.
     */
    public function getRealReturnType() : UnionType;

    /**
     * @return array<int,Parameter>
     * A list of parameters on the method, with types from the method signature.
     */
    public function getRealParameterList();

    /**
     * @param array<string,UnionType> $parameter_map maps a subset of param names to the unmodified phpdoc parameter types.
     * Will differ from real parameter types (ideally narrower)
     * @return void
     */
    public function setPHPDocParameterTypeMap(array $parameter_map);

    /**
     * @return array<string,UnionType> maps a subset of param names to the unmodified phpdoc parameter types.
     */
    public function getPHPDocParameterTypeMap();

    /**
     * @param ?UnionType $union_type the raw phpdoc union type
     * @return void
     */
    public function setPHPDocReturnType($union_type);

    /**
     * @return ?UnionType the raw phpdoc union type
     */
    public function getPHPDocReturnType();

    /**
     * @return bool
     * True if this function or method returns a reference
     */
    public function returnsRef() : bool;

    /**
     * Returns true if the return type depends on the argument, and a plugin makes Phan aware of that.
     */
    public function hasDependentReturnType() : bool;

    /**
     * Returns a union type based on $args_node and $context
     * @param CodeBase $code_base
     * @param Context $context
     * @param array<int,Node|int|string> $args
     */
    public function getDependentReturnType(CodeBase $code_base, Context $context, array $args) : UnionType;

    /**
     * Make calculation of the return type of this function/method use $closure
     * @return void
     */
    public function setDependentReturnTypeClosure(\Closure $closure);

    /**
     * Returns true if this function or method has additional analysis logic for invocations (From internal and user defined plugins)
     * @see getDependentReturnType
     */
    public function hasFunctionCallAnalyzer() : bool;

    /**
     * Perform additional analysis logic for invocations (From internal and user defined plugins)
     *
     * @param CodeBase $code_base
     * @param Context $context
     * @param array<int,Node|int|string> $args
     * @return void
     */
    public function analyzeFunctionCall(CodeBase $code_base, Context $context, array $args);

    /**
     * If callers need to invoke multiple closures, they should pass in a closure to invoke multiple closures.
     * @return void
     */
    public function setFunctionCallAnalyzer(\Closure $closure);

    /**
     * Initialize the inner scope of this method with variables created from the parameters.
     *
     * Deferred until the parse phase because getting the UnionType of parameter defaults requires having all class constants be known.
     *
     * @return void
     */
    public function ensureScopeInitialized(CodeBase $code_base);

    /** @return Node|null */
    public function getNode();

    /**
     * @return ?Comment - Not set for internal functions/methods
     */
    public function getComment();

    /**
     * @param Comment $comment
     */
    public function setComment(Comment $comment);

    public function getThrowsUnionType() : UnionType;

    /**
     * @return bool
     * True if this is a magic phpdoc method (declared via (at)method on class declaration phpdoc)
     * Always false for global functions(Func).
     */
    public function isFromPHPDoc() : bool;

    /**
     * Clone the parameter list, so that modifying the parameters on the first call won't modify the others.
     * TODO: If they're immutable, they can be shared without cloning with less worry.
     * @internal
     * @return void
     */
    public function cloneParameterList();

    /**
     * @return bool - Does any parameter type possibly require recursive analysis if more specific types are provided?
     *
     * If this returns true, there is at least one parameter and at least one of those can be overridden with a more specific type.
     */
    public function needsRecursiveAnalysis() : bool;

    /**
     * Returns a FunctionLikeDeclarationType based on phpdoc+real types.
     * The return value is used for type casting rule checking.
     */
    public function asFunctionLikeDeclarationType() : FunctionLikeDeclarationType;

    /**
     * @return array<mixed,string> in the same format as FunctionSignatureMap.php
     */
    public function toFunctionSignatureArray() : array;
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\CodeBase;
use Phan\Config;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\Comment;
use Phan\Language\FileRef;
use Phan\Language\FQSEN;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\BoolType;
use Phan\Language\Type\ClosureDeclarationParameter;
use Phan\Language\Type\ClosureDeclarationType;
use Phan\Language\Type\FalseType;
use Phan\Language\Type\FunctionLikeDeclarationType;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\MixedType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\TrueType;
use Phan\Language\UnionType;
use ast\Node;
use Closure;

trait FunctionTrait
{
    /**
     * @var Comment|null This is reused when quick mode is off.
     */
    protected $comment;

    /**
     * Did we initialize the inner scope of this method?
     * Deferred because hydrating parameter defaults requires having all class constants be known
     * @var bool This is set to null immediately after scope initialization is finished.
     */
    protected $is_inner_scope_initialized  = false;

    abstract public function getPhanFlags() : int;

    abstract public function getPhanFlagsHasState(int $bits) : bool;

    /**
     * @param int $phan_flags
     *
     * @return void
     */
    abstract public function setPhanFlags(int $phan_flags);

    /**
     * @return string
     * The (not fully-qualified) name of this element.
     */
    abstract public function getName() : string;

    /**
     * @return FQSEN
     * The fully-qualified structural element name of this
     * structural element
     */
    public abstract function getFQSEN();

    /**
     * @return string
     * The fully-qualified structural element name of this
     * structural element
     */
    public function getRepresentationForIssue() : string
    {
        return $this->getFQSEN()->__toString();
    }

    /**
     * @var int
     * The number of required parameters for the method
     */
    private $number_of_required_parameters = 0;

    /**
     * @var int
     * The number of optional parameters for the method.
     * Note that this is set to a large number in methods using varargs or func_get_arg*()
     */
    private $number_of_optional_parameters = 0;

    /**
     * @var int
     * The number of required (real) parameters for the method declaration.
     * For internal methods, ignores phan's annotations.
     */
    private $number_of_required_real_parameters = 0;

    /**
     * @var int
     * The number of optional (real) parameters for the method declaration.
     * For internal methods, ignores phan's annotations.
     * For user-defined methods, ignores presence of func_get_arg*()
     */
    private $number_of_optional_real_parameters = 0;

    /**
     * @var bool
     */
    private $needs_recursive_analysis = null;

    /**
     * @var array<int,Parameter>
     * The list of parameters for this method
     * This will change while the method is being analyzed when the config quick_mode is false.
     */
    private $parameter_list = [];

    /**
     * @var ?int
     * The hash of the types for the list of parameters for this function/method.
     */
    private $parameter_list_hash = null;

    /**
     * @var ?bool
     * Whether or not this function/method has any pass by reference parameters.
     */
    private $has_pass_by_reference_parameters = null;

    /**
     * @var array<int,int>
     * If the types for a parameter list were checked,
     * this contains the recursion depth for a given integer hash (smaller is earlier in recursion)
     */
    private $checked_parameter_list_hashes = [];

    /**
     * @var array<int,Parameter>
     * The list of *real* (not from phpdoc) parameters for this method.
     * This does not change after initialization.
     */
    private $real_parameter_list = [];

    /**
     * @var array<string,UnionType>
     * The list of unmodified *phpdoc* parameter types for this method.
     * This does not change after initialization.
     */
    private $phpdoc_parameter_type_map = [];

    /**
     * @var array<string,true>
     * A set of parameter names that are output-only references
     */
    private $phpdoc_output_references = [];

    /**
     * @var ?UnionType
     * The unmodified *phpdoc* union type for this method.
     * Will be null without any (at)return statements.
     */
    private $phpdoc_return_type;

    /**
     * @var UnionType
     * The *real* (not from phpdoc) return type from this method.
     * This does not change after initialization.
     */
    private $real_return_type;

    /**
     * @var \Closure|null (CodeBase, Context, Func|Method $func, Node[]|string[]|int[] $arg_list) => UnionType
     */
    private $return_type_callback = null;

    /**
     * @var \Closure|null (CodeBase, Context, Func|Method $func, Node[]|string[]|int[] $arg_list) => void
     */
    private $function_call_analyzer_callback = null;

    /**
     * @var FunctionLikeDeclarationType|null (Lazily generated)
     */
    private $as_closure_declaration_type;

    /**
     * @return int
     * The number of optional real parameters on this function/method.
     * May differ from getNumberOfOptionalParameters()
     * for internal modules lacking proper reflection info,
     * or if the installed module version's API changed from what Phan's stubs used,
     * or if a function/method uses variadics/func_get_arg*()
     */
    public function getNumberOfOptionalRealParameters() : int
    {
        return $this->number_of_optional_real_parameters;
    }

    /**
     * @return int
     * The number of optional parameters on this method
     */
    public function getNumberOfOptionalParameters() : int
    {
        return $this->number_of_optional_parameters;
    }

    /**
     * The number of optional parameters
     *
     * @return void
     */
    public function setNumberOfOptionalParameters(int $number)
    {
        $this->number_of_optional_parameters = $number;
    }

    /**
     * @return int
     * The number of parameters in this function/method declaration.
     * Variadic parameters are counted only once.
     * TODO: Specially handle variadic parameters, either here or in ParameterTypesAnalyzer::analyzeOverrideRealSignature
     */
    public function getNumberOfRealParameters() : int
    {
        return (
            $this->getNumberOfRequiredRealParameters()
            + $this->getNumberOfOptionalRealParameters()
        );
    }

    /**
     * @return int
     * The maximum number of parameters to this function/method
     */
    public function getNumberOfParameters() : int
    {
        return (
            $this->getNumberOfRequiredParameters()
            + $this->getNumberOfOptionalParameters()
        );
    }

    /**
     * @return int
     * The number of required real parameters on this function/method.
     * May differ for internal modules lacking proper reflection info,
     * or if the installed module version's API changed from what Phan's stubs used.
     */
    public function getNumberOfRequiredRealParameters() : int
    {
        return $this->number_of_required_real_parameters;
    }

    /**
     * @return int
     * The number of required parameters on this function/method
     */
    public function getNumberOfRequiredParameters() : int
    {
        return $this->number_of_required_parameters;
    }

    /**
     *
     * The number of required parameters
     *
     * @return void
     */
    public function setNumberOfRequiredParameters(int $number)
    {
        $this->number_of_required_parameters = $number;
    }

    /**
     * @return bool
     * True if this method had no return type defined when it
     * was defined (either in the signature itself or in the
     * docblock).
     */
    public function isReturnTypeUndefined() : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_RETURN_TYPE_UNDEFINED);
    }

    /**
     * @param bool $is_return_type_undefined
     * True if this method had no return type defined when it
     * was defined (either in the signature itself or in the
     * docblock).
     *
     * @return void
     */
    public function setIsReturnTypeUndefined(
        bool $is_return_type_undefined
    ) {
        $this->setPhanFlags(Flags::bitVectorWithState(
            $this->getPhanFlags(),
            Flags::IS_RETURN_TYPE_UNDEFINED,
            $is_return_type_undefined
        ));
    }

    /**
     * @return bool
     * True if this method returns a value
     * (i.e. it has a return with an expression)
     */
    public function getHasReturn() : bool
    {
        return $this->getPhanFlagsHasState(Flags::HAS_RETURN);
    }

    /**
     * @return bool
     * True if this method yields any value(i.e. it is a \Generator)
     */
    public function getHasYield() : bool
    {
        return $this->getPhanFlagsHasState(Flags::HAS_YIELD);
    }

    /**
     * @param bool $has_return
     * Set to true to mark this method as having a
     * return value
     *
     * @return void
     */
    public function setHasReturn(bool $has_return)
    {
        $this->setPhanFlags(Flags::bitVectorWithState(
            $this->getPhanFlags(),
            Flags::HAS_RETURN,
            $has_return
        ));
    }

    /**
     * @param bool $has_yield
     * Set to true to mark this method as having a
     * yield value
     *
     * @return void
     */
    public function setHasYield(bool $has_yield)
    {
        // TODO: In a future release of php-ast, this information will be part of the function node's flags.
        // (PHP 7.1 only, not supported in PHP 7.0)
        $this->setPhanFlags(Flags::bitVectorWithState(
            $this->getPhanFlags(),
            Flags::HAS_YIELD,
            $has_yield
        ));
    }

    /**
     * @return array<int,Parameter>
     * A list of parameters on the method
     */
    public function getParameterList()
    {
        return $this->parameter_list;
    }

    /**
     * @return bool - Does any parameter type possibly require recursive analysis if more specific types are provided?
     *
     * If this returns true, there is at least one parameter and at least one of those can be overridden with a more specific type.
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function needsRecursiveAnalysis() : bool
    {
        return $this->needs_recursive_analysis ?? ($this->needs_recursive_analysis = $this->computeNeedsRecursiveAnalysis());
    }

    private function computeNeedsRecursiveAnalysis() : bool
    {
        if (!$this->getNode()) {
            // E.g. this can be the case for magic methods, internal methods, stubs, etc.
            return false;
        }

        foreach ($this->parameter_list as $parameter) {
            if ($parameter->getNonVariadicUnionType()->shouldBeReplacedBySpecificTypes()) {
                return true;
            }
            if ($parameter->isPassByReference() && $parameter->getReferenceType() !== Flags::IS_WRITE_REFERENCE) {
                return true;
            }
        }
        return false;
    }

    /**
     * Gets the $ith parameter for the **caller**.
     * In the case of variadic arguments, an infinite number of parameters exist.
     * (The callee would see variadic arguments(T ...$args) as a single variable of type T[],
     * while the caller sees a place expecting an expression of type T.
     *
     * @param int $i - offset of the parameter.
     * @return Parameter|null The parameter type that the **caller** observes.
     */
    public function getParameterForCaller(int $i)
    {
        $list = $this->parameter_list;
        if (count($list) === 0) {
            return null;
        }
        $parameter = $list[$i] ?? null;
        if ($parameter) {
            return $parameter->asNonVariadic();
        }
        $lastParameter = $list[count($list) - 1];
        if ($lastParameter->isVariadic()) {
            return $lastParameter->asNonVariadic();
        }
        return null;
    }

    /**
     * @param array<int,Parameter> $parameter_list
     * A list of parameters to set on this method
     * (When quick_mode is false, this is also called to temporarily
     * override parameter types, etc.)
     *
     * @return void
     * @internal
     */
    public function setParameterList(array $parameter_list)
    {
        $this->parameter_list = $parameter_list;
        if ($this->parameter_list_hash === null) {
            $this->initParameterListInfo();
        }
    }

    /**
     * Called to lazily initialize properties of $this derived from $this->parameter_list
     */
    private function initParameterListInfo()
    {
        $parameter_list = $this->parameter_list;
        $this->parameter_list_hash = self::computeParameterListHash($parameter_list);
        $has_pass_by_reference_parameters = false;
        foreach ($parameter_list as $param) {
            if ($param->isPassByReference()) {
                $has_pass_by_reference_parameters = true;
                break;
            }
        }
        $this->has_pass_by_reference_parameters = $has_pass_by_reference_parameters;
    }

    /**
     * Called to generate a hash of a given parameter list, to avoid calling this on the same parameter list twice.
     *
     * @param array<int,Parameter> $parameter_list
     *
     * @return int 32-bit or 64-bit hash. Not likely to collide unless there are around 2^16 possible union types on 32-bit, or around 2^32 on 64-bit.
     *    (Collisions aren't a concern; The memory/runtime would probably be a bigger issue than collisions in non-quick mode.)
     */
    private static function computeParameterListHash(array $parameter_list) : int
    {
        // Choosing a small value to fit inside of a packed array.
        if (\count($parameter_list) === 0) {
            return 0;
        }
        if (Config::get_quick_mode()) {
            return 0;
        }
        $param_repr = '';
        foreach ($parameter_list as $param) {
            $param_repr .= $param->getUnionType()->__toString() . ',';
        }
        $raw_bytes = \md5($param_repr, true);
        return unpack(PHP_INT_SIZE === 8 ? 'q' : 'l', $raw_bytes)[1];
    }

    /**
     * @return array<int,Parameter> $parameter_list
     * A list of parameters (not from phpdoc) that were set on this method. The parameters will be cloned.
     */
    public function getRealParameterList()
    {
        // Excessive cloning, to ensure that this stays immutable.
        return array_map(function (Parameter $param) {
            return clone($param);
        }, $this->real_parameter_list);
    }

    /**
     * @param array<int,Parameter> $parameter_list
     * A list of parameters (not from phpdoc) to set on this method. The parameters will be cloned.
     *
     * @return void
     */
    public function setRealParameterList(array $parameter_list)
    {
        $this->real_parameter_list = array_map(function (Parameter $param) {
            return clone($param);
        }, $parameter_list);

        $required_count = 0;
        $optional_count = 0;
        foreach ($parameter_list as $parameter) {
            if ($parameter->isOptional()) {
                $optional_count++;
            } else {
                $required_count++;
            }
        }
        $this->number_of_required_real_parameters = $required_count;
        $this->number_of_optional_real_parameters = $optional_count;
    }

    /**
     * @param UnionType $union_type
     * The real (non-phpdoc) return type of this method in its given context.
     *
     * @return void
     */
    public function setRealReturnType(UnionType $union_type)
    {
        // TODO: was `self` properly resolved already? What about in subclasses?
        $this->real_return_type = $union_type;
    }

    /**
     * @return UnionType
     * The type of this method in its given context.
     */
    public function getRealReturnType() : UnionType
    {
        if (!$this->real_return_type) {
            // Incomplete patch for https://github.com/phan/phan/issues/670
            return UnionType::empty();
            // throw new \Error(sprintf("Failed to get real return type in %s method %s", (string)$this->getClassFQSEN(), (string)$this));
        }
        // Clone the union type, to be certain it will remain immutable.
        return $this->real_return_type;
    }

    /**
     * @param Parameter $parameter
     * A parameter to append to the parameter list
     *
     * @return void
     * @internal
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function appendParameter(Parameter $parameter)
    {
        $this->parameter_list[] = $parameter;
    }

    /**
     * @return void
     *
     * Call this before calling appendParameter, if parameters were already added.
     * @internal
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function clearParameterList()
    {
        $this->parameter_list = [];
        $this->parameter_list_hash = null;
    }

    /**
     * Adds types from comments to the params of a user-defined function or method.
     * Also adds the types from defaults, and emits warnings for certain violations.
     *
     * Conceptually, Func and Method should have defaults/comments analyzed in the same way.
     *
     * This does nothing if $function is for an internal method.
     *
     * @param Context $context
     * The context in which the node appears
     *
     * @param CodeBase $code_base
     *
     * @param FunctionInterface $function - A Func or Method to add params to the local scope of.
     *
     * @param Comment $comment - processed doc comment of $node, with params
     *
     * @return void
     */
    public static function addParamsToScopeOfFunctionOrMethod(
        Context $context,
        CodeBase $code_base,
        FunctionInterface $function,
        Comment $comment
    ) {
        if ($function->isPHPInternal()) {
            return;
        }
        $parameter_offset = 0;
        $function_parameter_list = $function->getParameterList();
        $real_parameter_name_map = [];
        foreach ($function_parameter_list as $parameter) {
            $real_parameter_name_map[$parameter->getName()] = $parameter;
            self::addParamToScopeOfFunctionOrMethod(
                $context,
                $code_base,
                $function,
                $comment,
                $parameter_offset,
                $parameter
            );
            ++$parameter_offset;
        }

        $valid_comment_parameter_type_map = [];
        foreach ($comment->getParameterMap() as $comment_parameter_name => $comment_parameter) {
            if (!\array_key_exists($comment_parameter_name, $real_parameter_name_map)) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    count($real_parameter_name_map) > 0 ? Issue::CommentParamWithoutRealParam : Issue::CommentParamOnEmptyParamList,
                    $function->getFileRef()->getLineNumberStart(),
                    $comment_parameter_name,
                    (string)$function
                );
                continue;
            }
            // Record phpdoc types to check if they are narrower than real types, later.
            // Only keep non-empty types.
            $comment_parameter_type = $comment_parameter->getUnionType();
            if (!$comment_parameter_type->isEmpty()) {
                $valid_comment_parameter_type_map[$comment_parameter_name] = $comment_parameter_type;
            }
            if ($comment_parameter->isOutputReference()) {
                $real_parameter_name_map[$comment_parameter_name]->setIsOutputReference();
            }
        }
        $function->setPHPDocParameterTypeMap($valid_comment_parameter_type_map);
        // Special, for libraries which use this for to document variadic param lists.
    }

    /**
     * Internally used.
     * @return void
     */
    public static function addParamToScopeOfFunctionOrMethod(
        Context $context,
        CodeBase $code_base,
        FunctionInterface $function,
        Comment $comment,
        int $parameter_offset,
        Parameter $parameter
    ) {
        if ($function->isPHPInternal()) {
            return;
        }
        $parameter_name = $parameter->getName();
        if ($parameter->getUnionType()->isEmpty()) {
            // If there is no type specified in PHP, check
            // for a docComment with @param declarations. We
            // assume order in the docComment matches the
            // parameter order in the code
            if ($comment->hasParameterWithNameOrOffset(
                $parameter_name,
                $parameter_offset
            )) {
                $comment_param = $comment->getParameterWithNameOrOffset(
                    $parameter_name,
                    $parameter_offset
                );
                $comment_param_type = $comment_param->getUnionType();
                if ($parameter->isVariadic() !== $comment_param->isVariadic()) {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        $parameter->isVariadic() ? Issue::TypeMismatchVariadicParam : Issue::TypeMismatchVariadicComment,
                        $function->getFileRef()->getLineNumberStart(),
                        $comment_param->__toString(),
                        $parameter->__toString()
                    );
                }

                // if ($parameter->isCloneOfVariadic()) { throw new \Error("Impossible\n"); }
                $parameter->addUnionType($comment_param_type);
            }
        }

        // If there's a default value on the parameter, check to
        // see if the type of the default is cool with the
        // specified type.
        if ($parameter->hasDefaultValue()) {
            $default_type = $parameter->getDefaultValueType();
            $default_is_null = $default_type->isType(NullType::instance(false));
            // If the default type isn't null and can't cast
            // to the parameter's declared type, emit an
            // issue.
            if (!$default_is_null) {
                if (!$default_type->canCastToUnionType(
                    $parameter->getUnionType()
                )) {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::TypeMismatchDefault,
                        $function->getFileRef()->getLineNumberStart(),
                        (string)$parameter->getUnionType(),
                        $parameter_name,
                        (string)$default_type
                    );
                }
            }

            // If there are no types on the parameter, the
            // default shouldn't be treated as the one
            // and only allowable type.
            $wasEmpty = $parameter->getUnionType()->isEmpty();

            // If we have no other type info about a parameter,
            // just because it has a default value of null
            // doesn't mean that is its type. Any type can default
            // to null
            if ($default_is_null) {
                if ($wasEmpty) {
                    $parameter->addUnionType(MixedType::instance(false)->asUnionType());
                }
                // The parameter constructor or above check for wasEmpty already took care of null default case
            } else {
                $default_type = $default_type->withFlattenedArrayShapeTypeInstances();
                if ($wasEmpty) {
                    $parameter->addUnionType(self::inferNormalizedTypesOfDefault($default_type));
                    if (!Config::getValue('guess_unknown_parameter_type_using_default')) {
                        $parameter->addUnionType(MixedType::instance(false)->asUnionType());
                    }
                } else {
                    // Don't add both `int` and `?int` to the same set.
                    foreach ($default_type->getTypeSet() as $default_type_part) {
                        if (!$parameter->getNonvariadicUnionType()->hasType($default_type_part->withIsNullable(true))) {
                            // if ($parameter->isCloneOfVariadic()) { throw new \Error("Impossible\n"); }
                            $parameter->addType($default_type_part);
                        }
                    }
                }
            }
        }
    }

    private static function inferNormalizedTypesOfDefault(UnionType $default_type) : UnionType
    {
        $type_set = $default_type->getTypeSet();
        if (\count($type_set) === 0) {
            return $default_type;
        }
        $normalized_default_type = new UnionType();
        foreach ($type_set as $type) {
            if ($type instanceof FalseType || $type instanceof NullType) {
                return MixedType::instance(false)->asUnionType();
            } elseif ($type instanceof GenericArrayType) {
                // Ideally should be the **only** type.
                $normalized_default_type = $normalized_default_type->withType(ArrayType::instance(false));
            } elseif ($type instanceof TrueType) {
                // e.g. for `function myFn($x = true) { }, $x is probably of type bool, but we're less sure about the type of $x from `$x = false`
                $normalized_default_type = $normalized_default_type->withType(BoolType::instance(false));
            } else {
                $normalized_default_type = $normalized_default_type->withType($type);
            }
        }
        return $normalized_default_type;
    }

    /**
     * @param array<string,UnionType> $parameter_map maps a subset of param names to the unmodified phpdoc parameter types. May differ from real parameter types
     * @return void
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function setPHPDocParameterTypeMap(array $parameter_map)
    {
        $this->phpdoc_parameter_type_map = $parameter_map;
    }

    /**
     * Records the fact that $parameter_name is an output-only reference.
     * @param string $parameter_name
     * @return void
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function recordOutputReferenceParamName(string $parameter_name)
    {
        $this->phpdoc_output_references[] = $parameter_name;
    }

    /**
     * @return array<int,string> list of output references. Usually empty.
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function getOutputReferenceParamNames() : array
    {
        return $this->phpdoc_output_references;
    }

    /**
     * @return array<string,UnionType> maps a subset of param names to the unmodified phpdoc parameter types.
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function getPHPDocParameterTypeMap()
    {
        return $this->phpdoc_parameter_type_map;
    }

    /**
     * @param ?UnionType $type the raw phpdoc union type
     * @return void
     */
    public function setPHPDocReturnType($type)
    {
        $this->phpdoc_return_type = $type;
    }

    /**
     * @return ?UnionType the raw phpdoc union type
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function getPHPDocReturnType()
    {
        return $this->phpdoc_return_type;
    }

    /**
     * Returns true if the param list has an instance of PassByReferenceVariable
     * If it does, the method has to be analyzed even if the same parameter types were analyzed already
     */
    private function hasPassByReferenceVariable(array $parameter_list) : bool
    {
        // Common case: function doesn't have any references in parameter list
        if ($this->has_pass_by_reference_parameters === false) {
            return false;
        }
        foreach ($parameter_list as $param) {
            if ($param instanceof PassByReferenceVariable) {
                return true;
            }
        }
        return false;
    }

    /**
     * analyzeWithNewParams is called only when the quick_mode config is false.
     * The new types are inferred based on the caller's types.
     * As an optimization, this refrains from re-analyzing the method/function it has already been analyzed for those param types
     * (With an equal or larger remaining recursion depth)
     *
     * @param array<int,Parameter> $parameter_list
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function analyzeWithNewParams(Context $context, CodeBase $code_base, array $parameter_list) : Context
    {
        $hash = $this->computeParameterListHash($parameter_list);
        $has_pass_by_reference_variable = null;
        // Nothing to do, except if PassByReferenceVariable was used
        if ($hash === $this->parameter_list_hash) {
            if (!$this->hasPassByReferenceVariable($parameter_list)) {
                // Have to analyze pass by reference variables anyway
                return $context;
            }
            $has_pass_by_reference_variable = true;
        }
        // Check if we've already analyzed this method with those given types,
        // with as much or even more depth left in the recursion.
        // (getRecursionDepth() increases as the program recurses downward)
        $old_recursion_depth_for_hash = $this->checked_parameter_list_hashes[$hash] ?? null;
        $new_recursion_depth_for_hash = $this->getRecursionDepth();
        if ($old_recursion_depth_for_hash !== null) {
            if ($new_recursion_depth_for_hash >= $old_recursion_depth_for_hash) {
                if (!($has_pass_by_reference_variable ?? $this->hasPassByReferenceVariable($parameter_list))) {
                    return $context;
                }
                // Have to analyze pass by reference variables anyway
                $new_recursion_depth_for_hash = $old_recursion_depth_for_hash;
            }
        }
        // Record the fact that it has already been analyzed,
        // along with the depth of recursion so far.
        $this->checked_parameter_list_hashes[$hash] = $new_recursion_depth_for_hash;
        return $this->analyze($context, $code_base);
    }

    public abstract function analyze(Context $context, CodeBase $code_base) : Context;

    public abstract function getRecursionDepth() : int;

    /** @return Node|null */
    public abstract function getNode();

    /** @return Context */
    public abstract function getContext() : Context;

    public abstract function getFileRef() : FileRef;

    /**
     * Returns true if the return type depends on the argument, and a plugin makes Phan aware of that.
     */
    public function hasDependentReturnType() : bool
    {
        return $this->return_type_callback !== null;
    }

    /**
     * Returns a union type based on $args_node and $context
     *
     * @param CodeBase $code_base
     * @param Context $context
     * @param array<int,Node|int|string|float> $args
     */
    public function getDependentReturnType(CodeBase $code_base, Context $context, array $args) : UnionType
    {
        return ($this->return_type_callback)($code_base, $context, $this, $args);
    }

    /**
     * @return void
     */
    public function setDependentReturnTypeClosure(\Closure $closure)
    {
        $this->return_type_callback = $closure;
    }

    /**
     * Returns true if this function or method has additional analysis logic for invocations (From internal and user defined plugins)
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function hasFunctionCallAnalyzer() : bool
    {
        return $this->function_call_analyzer_callback !== null;
    }

    /**
     * Perform additional analysis logic for invocations (From internal and user defined plugins)
     *
     * @param CodeBase $code_base
     * @param Context $context
     * @param array<int,Node|int|string|float> $args
     * @return void
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function analyzeFunctionCall(CodeBase $code_base, Context $context, array $args)
    {
        ($this->function_call_analyzer_callback)($code_base, $context, $this, $args);
    }

    /**
     * Make additional analysis logic of this function/method use $closure
     * If callers need to invoke multiple closures, they should pass in a closure to invoke multiple closures.
     * @return void
     */
    public function setFunctionCallAnalyzer(\Closure $closure)
    {
        $this->function_call_analyzer_callback = $closure;
    }

    /**
     * @return ?Comment - Not set for internal functions/methods
     */
    public function getComment()
    {
        return $this->comment;
    }

    /**
     * @param Comment $comment
     * @return void
     */
    public function setComment(Comment $comment)
    {
        $this->comment = $comment;
    }

    /**
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function getThrowsUnionType() : UnionType
    {
        $comment = $this->comment;
        return $comment ? $comment->getThrowsUnionType() : UnionType::empty();
    }

    /**
     * Initialize the inner scope of this method with variables created from the parameters.
     *
     * Deferred until the parse phase because getting the UnionType of parameter defaults requires having all class constants be known.
     *
     * @return void
     */
    public function ensureScopeInitialized(CodeBase $code_base)
    {
        if ($this->is_inner_scope_initialized) {
            return;
        }
        $this->is_inner_scope_initialized = true;
        $comment = $this->comment;
        // $comment can be null for magic methods from `@method`
        if ($comment !== null) {
            \assert($this instanceof FunctionInterface);
            FunctionTrait::addParamsToScopeOfFunctionOrMethod($this->getContext(), $code_base, $this, $comment);
        }
    }

    public abstract function memoizeFlushAll();

    public abstract function getUnionType() : UnionType;

    /** @return void */
    public abstract function setUnionType(UnionType $type);

    /**
     * @internal - Used by daemon mode to restore an element to the state it had before parsing.
     * @return Closure
     */
    public function createRestoreCallback()
    {
        // NOTE: Properties, Methods, and closures are restored separately.
        $parameter_list_hash = $this->parameter_list_hash;
        $parameter_list = [];
        foreach ($this->parameter_list as $parameter) {
            $parameter_list[] = clone($parameter);
        }
        $union_type = $this->getUnionType();
        $return_type_callback = $this->return_type_callback;
        $function_call_analyzer_callback = $this->function_call_analyzer_callback;

        return function () use ($parameter_list, $parameter_list_hash, $union_type, $return_type_callback, $function_call_analyzer_callback) {
            $this->memoizeFlushAll();
            $this->parameter_list_hash = $parameter_list_hash;
            $this->parameter_list = $parameter_list;
            $this->setUnionType($union_type);
            $this->checked_parameter_list_hashes = [];
            $this->return_type_callback = $return_type_callback;
            $this->function_call_analyzer_callback = $function_call_analyzer_callback;
        };
    }

    /**
     * Clone the parameter list, so that modifying the parameters on the first call won't modify the others.
     * TODO: If they're immutable, they can be shared without cloning with less worry.
     * @internal
     * @return void
     */
    public function cloneParameterList()
    {
        $this->setParameterList(
            \array_map(
                function (Parameter $parameter) : Parameter {
                    return clone($parameter);
                },
                $this->getParameterList()
            )
        );
    }

    /**
     * Returns a FunctionLikeDeclarationType based on phpdoc+real types.
     * The return value is used for type casting rule checking.
     * @suppress PhanUnreferencedPublicMethod Phan knows FunctionInterface's method is referenced, but can't associate that yet.
     */
    public function asFunctionLikeDeclarationType() : FunctionLikeDeclarationType
    {
        return $this->as_closure_declaration_type ?? ($this->as_closure_declaration_type = $this->createFunctionLikeDeclarationType());
    }

    public abstract function returnsRef() : bool;

    private function createFunctionLikeDeclarationType() : FunctionLikeDeclarationType
    {
        $params = array_map(function (Parameter $parameter) : ClosureDeclarationParameter {
            return $parameter->asClosureDeclarationParameter();
        }, $this->getParameterList());

        $return_type = $this->getUnionType();
        if ($return_type->isEmpty()) {
            $return_type = MixedType::instance(false)->asUnionType();
        }
        return new ClosureDeclarationType(
            $this->getFileRef(),
            $params,
            $return_type,
            $this->returnsRef(),
            false
        );
    }

    /**
     * @return array<mixed,string> in the same format as FunctionSignatureMap.php
     */
    public function toFunctionSignatureArray() : array
    {
        $return_type = $this->getUnionType();
        $stub = [$return_type->__toString()];
        '@phan-var array<mixed,string> $stub';  // TODO: Should not warn about PhanTypeMismatchDimFetch in isset below
        foreach ($this->getParameterList() as $parameter) {
            $name = $parameter->getName();
            if (!$name || isset($stub[$name])) {
                throw new \InvalidArgumentException("Invalid name '$name' for {$this->getFQSEN()}");
            }
            if ($parameter->isOptional()) {
                $name .= '=';
            }
            $type_string = $parameter->getUnionType()->__toString();
            if ($parameter->isVariadic()) {
                $name = '...' . $name;
            }
            if ($parameter->isPassByReference()) {
                $name = '&' . $name;
            }
            $stub[$name] = $type_string;
        }
        return $stub;
    }
}
<?php declare(strict_types=1);

namespace Phan\Language\Element;

use Phan\Language\Context;
use Phan\Language\FQSEN\FullyQualifiedClassName;

class ClassAliasRecord
{

    /** @var FullyQualifiedClassName the FQSEN of the alias that will be created. */
    public $alias_fqsen;

    /** @var Context - the context of the class_alias() call*/
    public $context;

    /** @var int - the line number of the class_alias() call */
    public $lineno;

    public function __construct(FullyQualifiedClassName $alias_fqsen, Context $context, int $lineno)
    {
        $this->alias_fqsen = $alias_fqsen;
        $this->context = $context;
        $this->lineno = $lineno;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\CodeBase;
use Phan\Exception\CodeBaseException;
use Phan\Language\Context;
use Phan\Language\FQSEN;
use Phan\Language\FQSEN\FullyQualifiedClassElement;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\UnionType;

abstract class ClassElement extends AddressableElement
{
    /** @var FullyQualifiedClassName */
    private $class_fqsen;

    public function __construct(
        Context $context,
        string $name,
        UnionType $type,
        int $flags,
        FullyQualifiedClassElement $fqsen
    ) {
        parent::__construct($context, $name, $type, $flags, $fqsen);
        $this->class_fqsen = $fqsen->getFullyQualifiedClassName();
    }

    /**
     * @param FullyQualifiedClassElement $fqsen
     * @return void
     * @override
     * @suppress PhanParamSignatureMismatch deliberately more specific
     */
    public function setFQSEN(FQSEN $fqsen)
    {
        \assert($fqsen instanceof FullyQualifiedClassElement);
        parent::setFQSEN($fqsen);
        $this->class_fqsen = $fqsen->getFullyQualifiedClassName();
    }

    /**
     * @var FullyQualifiedClassElement|null
     * The FQSEN of this element where it is originally
     * defined.
     */
    private $defining_fqsen = null;

    /**
     * @return bool
     * True if this element has a defining FQSEN defined
     */
    public function hasDefiningFQSEN() : bool
    {
        return ($this->defining_fqsen != null);
    }

    /**
     * @return FullyQualifiedClassElement
     * The FQSEN of this class element from where it was
     * originally defined
     */
    public function getDefiningFQSEN() : FullyQualifiedClassElement
    {
        return $this->defining_fqsen;
    }

    /**
     * @return FullyQualifiedClassName
     * The FQSEN of this class element from where it was
     * originally defined
     */
    public function getDefiningClassFQSEN() : FullyQualifiedClassName
    {
        if (\is_null($this->defining_fqsen)) {
            throw new CodeBaseException(
                $this->getFQSEN(),
                "No defining class for {$this->getFQSEN()}"
            );
        }
        return $this->defining_fqsen->getFullyQualifiedClassName();
    }

    /**
     * @param FullyQualifiedClassElement $defining_fqsen
     * The FQSEN of this class element in the location in which
     * it was originally defined
     */
    public function setDefiningFQSEN(
        FullyQualifiedClassElement $defining_fqsen
    ) {
        $this->defining_fqsen = $defining_fqsen;
    }

    /**
     * @return Clazz
     * The class on which this element was originally defined
     */
    public function getDefiningClass(CodeBase $code_base) : Clazz
    {
        $class_fqsen = $this->getDefiningClassFQSEN();

        if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
            throw new CodeBaseException(
                $class_fqsen,
                "Defining class $class_fqsen for {$this->getFQSEN()} not found"
            );
        }

        return $code_base->getClassByFQSEN($class_fqsen);
    }

    /**
     * @return FullyQualifiedClassName
     * The FQSEN of the class on which this element lives
     */
    public function getClassFQSEN() : FullyQualifiedClassName
    {
        return $this->class_fqsen;
    }

    /**
     * @param CodeBase $code_base
     * The code base with which to look for classes
     *
     * @return Clazz
     * The class that defined this element
     *
     * @throws CodeBaseException
     * An exception may be thrown if we can't find the
     * class
     */
    public function getClass(
        CodeBase $code_base
    ) : Clazz {
        $class_fqsen = $this->class_fqsen;

        if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
            throw new CodeBaseException(
                $class_fqsen,
                "Defining class $class_fqsen for {$this->getFQSEN()} not found"
            );
        }

        return $code_base->getClassByFQSEN($class_fqsen);
    }

    /**
     * @return bool
     * True if this method overrides another method
     */
    public function getIsOverride() : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_OVERRIDE);
    }

    /**
     * @param bool $is_override
     * True if this method overrides another method
     *
     * @return void
     */
    public function setIsOverride(bool $is_override)
    {
        $this->setPhanFlags(Flags::bitVectorWithState(
            $this->getPhanFlags(),
            Flags::IS_OVERRIDE,
            $is_override
        ));
    }

    /**
     * @return bool
     * True if this is a static method
     */
    public function isStatic() : bool
    {
        return $this->getFlagsHasState(\ast\flags\MODIFIER_STATIC);
    }

    /**
     * @param CodeBase $code_base
     * The code base in which this element exists.
     *
     * @return bool
     * True if this is an internal element
     */
    public function isNSInternal(CodeBase $code_base) : bool
    {
        return (
            parent::isNSInternal($code_base)
            || $this->getClass($code_base)->isNSInternal($code_base)
        );
    }

    /**
     * @suppress PhanPluginUnusedMethodArgument
     */
    public function getElementNamespace() : string
    {
        // Get the namespace that the class is within
        return $this->getClassFQSEN()->getNamespace() ?: '\\';
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\Context;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\Type;
use Phan\Language\Type\NullType;
use Phan\Language\UnionType;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 * @phan-file-suppress PhanPartialTypeMismatchArgumentInternal
 */
class FunctionFactory
{
    /**
     * @return array<int,Func>
     * One or more (alternate) functions begotten from
     * reflection info and internal functions data
     */
    public static function functionListFromReflectionFunction(
        FullyQualifiedFunctionName $fqsen,
        \ReflectionFunction $reflection_function
    ) : array {

        $context = new Context();

        $function = new Func(
            $context,
            $fqsen->getNamespacedName(),
            UnionType::empty(),
            0,
            $fqsen,
            null
        );

        $function->setNumberOfRequiredParameters(
            $reflection_function->getNumberOfRequiredParameters()
        );

        $function->setNumberOfOptionalParameters(
            $reflection_function->getNumberOfParameters()
            - $reflection_function->getNumberOfRequiredParameters()
        );
        $function->setIsDeprecated($reflection_function->isDeprecated());
        $function->setRealReturnType(UnionType::fromReflectionType($reflection_function->getReturnType()));
        $function->setRealParameterList(Parameter::listFromReflectionParameterList($reflection_function->getParameters()));

        return self::functionListFromFunction($function);
    }

    /**
     * @param string[] $signature
     * @return array<int,Func>
     * One or more (alternate) methods begotten from
     * reflection info and internal method data
     */
    public static function functionListFromSignature(
        FullyQualifiedFunctionName $fqsen,
        array $signature
    ) : array {

        $context = new Context();

        $return_type = UnionType::fromStringInContext(
            array_shift($signature),
            $context,
            Type::FROM_TYPE
        );

        $func = new Func(
            $context,
            $fqsen->getNamespacedName(),
            $return_type,
            0,
            $fqsen,
            []  // will be filled in by functionListFromFunction
        );

        return self::functionListFromFunction($func);
    }

    /**
     * @return array<int,Method>
     */
    public static function methodListFromReflectionClassAndMethod(
        Context $context,
        \ReflectionClass $class,
        \ReflectionMethod $reflection_method
    ) : array {
        $method_fqsen = FullyQualifiedMethodName::fromStringInContext(
            $reflection_method->getName(),
            $context
        );

        $class_name = $class->getName();
        $reflection_method = new \ReflectionMethod(
            $class_name,
            $reflection_method->name
        );

        $method = new Method(
            $context,
            $reflection_method->name,
            UnionType::empty(),
            $reflection_method->getModifiers(),
            $method_fqsen,
            null
        );

        $method->setNumberOfRequiredParameters(
            $reflection_method->getNumberOfRequiredParameters()
        );

        $method->setNumberOfOptionalParameters(
            $reflection_method->getNumberOfParameters()
            - $reflection_method->getNumberOfRequiredParameters()
        );

        if ($method->getIsMagicCall() || $method->getIsMagicCallStatic()) {
            $method->setNumberOfOptionalParameters(FunctionInterface::INFINITE_PARAMETERS);
            $method->setNumberOfRequiredParameters(0);
        }
        $method->setIsDeprecated($reflection_method->isDeprecated());
        // https://github.com/phan/phan/issues/888 - Reflection for that class's parameters causes php to throw/hang
        if ($class_name !== 'ServerResponse') {
            $method->setRealReturnType(UnionType::fromReflectionType($reflection_method->getReturnType()));
            $method->setRealParameterList(Parameter::listFromReflectionParameterList($reflection_method->getParameters()));
        }

        return self::functionListFromFunction($method);
    }

    /**
     * @param FunctionInterface $function
     * Get a list of methods hydrated with type information
     * for the given partial method
     *
     * @return array<int,FunctionInterface>
     * A list of typed functions/methods based on the given method
     */
    public static function functionListFromFunction(
        FunctionInterface $function
    ) : array {
        // See if we have any type information for this
        // internal function
        $map_list = UnionType::internalFunctionSignatureMapForFQSEN(
            $function->getFQSEN()
        );

        if (!$map_list) {
            return [$function];
        }

        $alternate_id = 0;
        return \array_map(function ($map) use (
            $function,
            &$alternate_id
        ) : FunctionInterface {
            $alternate_function = clone($function);

            $alternate_function->setFQSEN(
                $alternate_function->getFQSEN()->withAlternateId(
                    $alternate_id++
                )
            );

            // Set the return type if one is defined
            if (!empty($map['return_type'])) {
                $alternate_function->setUnionType($map['return_type']);
            }
            $alternate_function->clearParameterList();

            // Load parameter types if defined
            foreach ($map['parameter_name_type_map'] ?? [] as $parameter_name => $parameter_type) {
                $flags = 0;
                $phan_flags = 0;
                $is_optional = false;

                // Check to see if its a pass-by-reference parameter
                if (($parameter_name[0] ?? '') === '&') {
                    $flags |= \ast\flags\PARAM_REF;
                    $parameter_name = \substr($parameter_name, 1);
                    if (\strncmp($parameter_name, 'rw_', 3) === 0) {
                        $phan_flags |= Flags::IS_READ_REFERENCE | Flags::IS_WRITE_REFERENCE;
                        $parameter_name = \substr($parameter_name, 3);
                    } elseif (\strncmp($parameter_name, 'w_', 2) === 0) {
                        $phan_flags |= Flags::IS_WRITE_REFERENCE;
                        $parameter_name = \substr($parameter_name, 2);
                    }
                }

                // Check to see if its variadic
                if (\strpos($parameter_name, '...') !== false) {
                    $flags |= \ast\flags\PARAM_VARIADIC;
                    $parameter_name = \str_replace('...', '', $parameter_name);
                }

                // Check to see if its an optional parameter
                if (\strpos($parameter_name, '=') !== false) {
                    $is_optional = true;
                    $parameter_name = \str_replace('=', '', $parameter_name);
                }

                $parameter = Parameter::create(
                    $function->getContext(),
                    $parameter_name,
                    $parameter_type,
                    $flags
                );
                $parameter->enablePhanFlagBits($phan_flags);

                if ($is_optional) {
                    // TODO: could check isDefaultValueAvailable and getDefaultValue, for a better idea.
                    // I don't see any cases where this will be used for internal types, though.
                    $parameter->setDefaultValueType(
                        NullType::instance(false)->asUnionType()
                    );
                }

                // Add the parameter
                $alternate_function->appendParameter($parameter);
            }

            // TODO: Store the "real" number of required parameters,
            // if this is out of sync with the extension's ReflectionMethod->getParameterList()?
            // (e.g. third party extensions may add more required parameters?)
            $alternate_function->setNumberOfRequiredParameters(
                \array_reduce(
                    $alternate_function->getParameterList(),
                    function (int $carry, Parameter $parameter) : int {
                        return ($carry + (
                            $parameter->isOptional() ? 0 : 1
                        ));
                    },
                    0
                )
            );

            $alternate_function->setNumberOfOptionalParameters(
                \count($alternate_function->getParameterList()) -
                $alternate_function->getNumberOfRequiredParameters()
            );

            if ($alternate_function instanceof Method) {
                if ($alternate_function->getIsMagicCall() || $alternate_function->getIsMagicCallStatic()) {
                    $alternate_function->setNumberOfOptionalParameters(999);
                    $alternate_function->setNumberOfRequiredParameters(0);
                }
            }

            return $alternate_function;
        }, $map_list);
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

class Flags
{
    const IS_DEPRECATED                = (1 << 1);
    const IS_PHP_INTERNAL              = (1 << 2);

    const IS_PARENT_CONSTRUCTOR_CALLED = (1 << 3);

    const IS_RETURN_TYPE_UNDEFINED     = (1 << 4);
    const HAS_RETURN                   = (1 << 5);
    const IS_OVERRIDE                  = (1 << 6);
    const HAS_YIELD                    = (1 << 7);

    const CLASS_HAS_DYNAMIC_PROPERTIES = (1 << 8);
    const IS_CLONE_OF_VARIADIC         = (1 << 9);
    const CLASS_FORBID_UNDECLARED_MAGIC_PROPERTIES = (1 << 10);
    const CLASS_FORBID_UNDECLARED_MAGIC_METHODS    = (1 << 11);

    const IS_NS_INTERNAL               = (1 << 12);
    const IS_FROM_PHPDOC               = (1 << 13);

    // These can be combined in 3 ways, see Parameter->getReferenceType()
    const IS_READ_REFERENCE            = (1 << 14);
    const IS_WRITE_REFERENCE           = (1 << 15);
    // End of reference types

    // This will be compared against IS_OVERRIDE
    const IS_OVERRIDE_INTENDED         = (1 << 16);

    const IS_PARAM_USING_NULLABLE_SYNTAX = (1 << 17);

    // For dead code detection
    const WAS_PROPERTY_READ = (1 << 17);

    /**
     * Either enable or disable the given flag on
     * the given bit vector.
     *
     * @param int $bit_vector
     * The bit vector we're operating on
     *
     * @param int $flag
     * The flag we're setting on the bit vector such
     * as Flags::IS_DEPRECATED.
     *
     * @param bool $value
     * True to or the flag in, false to & the bit vector
     * with the flags negation
     *
     * @return int
     * A new bit vector with the given flag set or unset
     */
    public static function bitVectorWithState(
        int $bit_vector,
        int $flag,
        bool $value
    ) : int {
        return $value
            ? ($bit_vector | $flag)
            : ($bit_vector & (~$flag));
    }

    /**
     * @param int $bit_vector
     * The bit vector we'd like to get the state for
     *
     * @param int $flag
     * The flag we'd like to get the state for
     *
     * @return bool
     * True if all bits in the flag are enabled in the bit
     * vector, else false.
     */
    public static function bitVectorHasState(
        int $bit_vector,
        int $flag
    ) : bool {
        return (($bit_vector & $flag) === $flag);
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\FutureUnionType;
use Phan\Language\Type\NullType;
use Phan\Language\UnionType;

trait ElementFutureUnionType
{

    /**
     * @var FutureUnionType|null
     * A FutureUnionType is evaluated lazily only when
     * the type is actually needed. This lets us deal
     * with constants who's default type is another
     * constant who's type is not yet known during
     * parsing.
     */
    protected $future_union_type = null;

    /**
     * @param UnionType $type
     * Set the type of this element
     *
     * @return void
     */
    abstract public function setUnionType(UnionType $type);

    /**
     * @return void
     */
    public function setFutureUnionType(
        FutureUnionType $future_union_type
    ) {
        $this->future_union_type = $future_union_type;
    }

    /**
     * @return bool
     * Returns true if this element has an unresolved union type.
     *
     * @internal - Mostly useful for Phan internals
     *             (e.g. a property with an unresolved future union type can't have a template type)
     */
    public function hasUnresolvedFutureUnionType() : bool
    {
        return $this->future_union_type !== null;
    }

    /**
     * @return UnionType|null
     * Get the UnionType from a future union type defined
     * on this object or null if there is no future
     * union type.
     */
    public function getFutureUnionType()
    {
        if (empty($this->future_union_type)) {
            return null;
        }

        // null out the future_union_type before
        // we compute it to avoid unbounded
        // recursion
        $future_union_type = $this->future_union_type;
        $this->future_union_type = null;

        $union_type = $future_union_type->get();

        // Don't set 'null' as the type if that's the default
        // given that its the default.
        if ($union_type->isType(NullType::instance(false))) {
            $union_type = UnionType::empty();
        }

        return $union_type;
    }
}
<?php
declare(strict_types=1);
namespace Phan\Language\Element\Comment;

use Phan\Language\Context;
use Phan\Language\Element\Variable;
use Phan\Language\UnionType;

class Parameter
{

    /**
     * @var string
     * The name of the parameter
     */
    private $name;

    /**
     * @var UnionType
     * The type of the parameter
     */
    private $type;

    /**
     * @var bool
     * Whether or not the parameter is variadic (in the comment)
     */
    private $is_variadic;

    /**
     * @var bool
     * Whether or not the parameter is optional (Note: only applies to the comment for (at)method.)
     */
    private $has_default_value;

    /**
     * @var bool
     * True if a given parameter is an output-only parameter and ignores the passed in type.
     */
    private $is_output_reference;

    /**
     * @param string $name
     * The name of the parameter
     *
     * @param UnionType $type
     * The type of the parameter
     */
    public function __construct(
        string $name,
        UnionType $type,
        bool $is_variadic = false,
        bool $has_default_value = false,
        bool $is_output_reference = false
    ) {
        $this->name = $name;
        $this->type = $type;
        $this->is_variadic = $is_variadic;
        $this->has_default_value = $has_default_value;
        $this->is_output_reference = $is_output_reference;
    }

    /**
     *
     */
    public function asVariable(
        Context $context,
        int $flags = 0
    ) : Variable {
        return new Variable(
            $context,
            $this->getName(),
            $this->getUnionType(),
            $flags
        );
    }

    /**
     *
     */
    public function asRealParameter(
        Context $context
    ) : \Phan\Language\Element\Parameter {
        $flags = 0;
        if ($this->isVariadic()) {
            $flags |= \ast\flags\PARAM_VARIADIC;
        }
        $union_type = $this->getUnionType();
        $param = \Phan\Language\Element\Parameter::create(
            $context,
            $this->getName(),
            $union_type,
            $flags
        );
        if ($this->has_default_value) {
            $param->setDefaultValueType($union_type);
            // TODO: could setDefaultValue in a future PR. Would have to run \ast\parse_code on the default value, catch ParseError if necessary.
            // If given '= "Default"', then extract the default from '<?php ("Default");'
            // Then get the type from UnionTypeVisitor, for defaults such as SomeClass::CONST.
        }
        return $param;
    }

    /**
     * @return string
     * The name of the parameter
     */
    public function getName() : string
    {
        return $this->name;
    }

    /**
     * @return UnionType
     * The type of the parameter
     */
    public function getUnionType() : UnionType
    {
        return $this->type;
    }

    /**
     * @return bool
     * Whether or not the parameter is variadic
     */
    public function isVariadic() : bool
    {
        return $this->is_variadic;
    }

    /**
     * @return bool
     * Whether or not the parameter is an output reference
     */
    public function isOutputReference() : bool
    {
        return $this->is_output_reference;
    }

    /**
     * @return bool
     * Whether or not the parameter is required
     */
    public function isRequired() : bool
    {
        return !$this->isOptional();
    }

    /**
     * @return bool
     * Whether or not the parameter is optional
     */
    public function isOptional() : bool
    {
        return $this->has_default_value || $this->is_variadic;
    }

    public function __toString() : string
    {
        $string = '';

        if (!$this->type->isEmpty()) {
            $string .= "{$this->type} ";
        }
        if ($this->is_variadic) {
            $string .= '...';
        }

        $string .= '$' . $this->name;

        if ($this->has_default_value) {
            $string .= ' = default';
        }

        return $string;
    }
}
<?php
declare(strict_types=1);
namespace Phan\Language\Element\Comment;

use Phan\Language\UnionType;

class Method
{

    /**
     * @var string
     * The name of the method
     */
    private $name;

    /**
     * @var UnionType
     * The return type of the magic method
     */
    private $type;

    /**
     * @var array<int,Parameter>
     * A list of phpdoc parameters
     */
    private $parameters;

    /**
     * @var bool
     * Whether or not this is a static magic method
     */
    private $is_static;

    /**
     * @param string $name
     * The name of the method
     *
     * @param UnionType $type
     * The return type of the method
     *
     * @param array<int,Parameter> $parameters
     * 0 or more comment parameters for this magic method
     *
     * @param bool $is_static
     * Whether this method is static
     */
    public function __construct(
        string $name,
        UnionType $type,
        array $parameters,
        bool $is_static
    ) {
        $this->name = $name;
        $this->type = $type;
        $this->parameters = $parameters;
        $this->is_static = $is_static;
    }

    /**
     * @return string
     * The name of the magic method
     */
    public function getName() : string
    {
        return $this->name;
    }

    /**
     * @return UnionType
     * The return type of the magic method
     */
    public function getUnionType() : UnionType
    {
        return $this->type;
    }

    /**
     * @return array<int,Parameter> - comment parameters of magic method, from phpdoc.
     */
    public function getParameterList() : array
    {
        return $this->parameters;
    }

    /**
     * @return bool
     * Whether or not the magic method is static
     */
    public function isStatic() : bool
    {
        return $this->is_static;
    }

    /**
     * @return int
     * Number of required parameters of this method
     */
    public function getNumberOfRequiredParameters() : int
    {
        return array_reduce(
            $this->parameters,
            function (int $carry, Parameter $parameter) : int {
                return ($carry + ($parameter->isRequired() ? 1 : 0));
            },
            0
        );
    }

    /**
     * @return int
     * Number of optional parameters of this method
     */
    public function getNumberOfOptionalParameters() : int
    {
        return array_reduce(
            $this->parameters,
            function (int $carry, Parameter $parameter) : int {
                return ($carry + ($parameter->isOptional() ? 1 : 0));
            },
            0
        );
    }

    public function __toString() : string
    {
        if ($this->isStatic()) {
            $string = 'static function ';
        } else {
            $string = 'function ';
        }
        // Magic methods can't be by ref?
        $string .= $this->getName();

        $string .= '(' . implode(', ', $this->getParameterList()) . ')';

        if (!$this->getUnionType()->isEmpty()) {
            $string .= ' : ' . (string)$this->getUnionType();
        }

        return $string;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\FileRef;
use Phan\Language\UnionType;

/**
 * Any PHP structural element that also has a type and is
 * addressable such as a class, method, closure, property,
 * constant, variable, ...
 */
interface TypedElementInterface
{
    /**
     * @return string
     * The (not fully-qualified) name of this element.
     */
    public function getName() : string;

    /**
     * @return UnionType
     * Get the type of this structural element
     */
    public function getUnionType() : UnionType;

    /**
     * @param UnionType $type
     * Set the type of this element
     *
     * @return void
     */
    public function setUnionType(UnionType $type);

    /**
     * @return Context
     * The context in which this structural element exists
     */
    public function getContext() : Context;

    /**
     * @return FileRef
     * A reference to where this element was found
     */
    public function getFileRef() : FileRef;

    /**
     * @return bool
     * True if this element is marked as deprecated
     */
    public function isDeprecated() : bool;

    /**
     * @param bool $is_deprecated
     * Set this element as deprecated
     *
     * @return void
     */
    public function setIsDeprecated(bool $is_deprecated);

    /**
     * @param array<int,string> $suppress_issue_list
     * Set the set of issue names to suppress
     *
     * @return void
     */
    public function setSuppressIssueList(array $suppress_issue_list);

    /**
     * @return array<string,int>
     * Returns a map from issue name to count of suppressions
     */
    public function getSuppressIssueList() : array;

    /**
     * Increments the number of times $issue_name was suppressed.
     * @return void
     */
    public function incrementSuppressIssueCount(string $issue_name);

    /**
     * return bool
     * True if this element would like to suppress the given
     * issue name
     */
    public function hasSuppressIssue(string $issue_name) : bool;

    /**
     * @return bool
     * True if this was an internal PHP object
     */
    public function isPHPInternal() : bool;

    /**
     * This method must be called before analysis
     * begins.
     *
     * @return void
     */
    public function hydrate(CodeBase $code_base);
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\FileRef;
use Phan\Language\FutureUnionType;
use Phan\Language\Type;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\BoolType;
use Phan\Language\Type\ClosureDeclarationParameter;
use Phan\Language\Type\FalseType;
use Phan\Language\Type\FloatType;
use Phan\Language\Type\IntType;
use Phan\Language\Type\MixedType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\StringType;
use Phan\Language\Type\TrueType;
use Phan\Language\UnionType;
use ast\Node;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class Parameter extends Variable
{
    const REFERENCE_DEFAULT = 1;
    const REFERENCE_READ_WRITE = 2;
    const REFERENCE_WRITE_ONLY = 3;

    // __construct inherited from Variable

    /**
     * @var UnionType|null
     * The type of the default value if any
     */
    private $default_value_type = null;

    /**
     * @var FutureUnionType|null
     * The type of the default value if any
     */
    private $default_value_future_type = null;

    /**
     * @var mixed
     * The value of the default, if one is set
     */
    private $default_value = null;

    /**
     * @return static
     */
    public static function create(
        FileRef $context,
        string $name,
        UnionType $type,
        int $flags
    ) {
        if (Flags::bitVectorHasState($flags, \ast\flags\PARAM_VARIADIC)) {
            return new VariadicParameter($context, $name, $type, $flags);
        }
        return new Parameter($context, $name, $type, $flags);
    }

    /**
     * @return bool
     * True if this parameter has a type for its
     * default value
     */
    public function hasDefaultValue() : bool
    {
        return $this->default_value_type !== null;
    }

    /**
     * @param UnionType $type
     * The type of the default value for this parameter
     *
     * @return void
     */
    public function setDefaultValueType(UnionType $type)
    {
        $this->default_value_type = $type;
    }

    /**
     * @param FutureUnionType $type
     * The future type of the default value for this parameter
     *
     * @return void
     */
    public function setDefaultValueFutureType(FutureUnionType $type)
    {
        $this->default_value_future_type = $type;
    }

    /**
     * @return UnionType
     * The type of the default value for this parameter
     * if it exists
     * @suppress PhanAccessMethodInternal
     */
    public function getDefaultValueType() : UnionType
    {
        $future_type = $this->default_value_future_type;
        if ($future_type !== null) {
            // Only attempt to resolve the future type once.
            try {
                $this->default_value_type = $future_type->get();
            } catch (IssueException $exception) {
                // Ignore exceptions
                Issue::maybeEmitInstance(
                    $future_type->getCodebase(),
                    $future_type->getContext(),
                    $exception->getIssueInstance()
                );
            } finally {
                // Only try to resolve the FutureType once.
                $this->default_value_future_type = null;
            }
        }
        return $this->default_value_type;
    }

    /**
     * @param mixed $value
     * The value of the default for this parameter
     *
     * @return void
     */
    public function setDefaultValue($value)
    {
        $this->default_value = $value;
    }

    /**
     * If the value's default is null, or a constant evaluating to null,
     * then the parameter type should be converted to nullable
     * (E.g. `int $x = null` and `?int $x = null` are equivalent.
     */
    public function handleDefaultValueOfNull()
    {
        if ($this->default_value_type->isType(NullType::instance(false))) {
            // If it isn't already nullable, convert the parameter type to nullable.
            $this->convertToNullable();
        }
    }

    /**
     * @return mixed
     * The value of the default for this parameter if one
     * is defined, otherwise null.
     */
    public function getDefaultValue()
    {
        return $this->default_value;
    }

    /**
     * @return array<int,Parameter>
     * A list of parameters from an AST node.
     * @suppress PhanPluginUnusedVariable
     */
    public static function listFromNode(
        Context $context,
        CodeBase $code_base,
        Node $node
    ) : array {
        $parameter_list = [];
        $is_optional_seen = false;
        foreach ($node->children as $child_node) {
            $parameter =
                Parameter::fromNode($context, $code_base, $child_node);

            if (!$parameter->isOptional() && $is_optional_seen) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::ParamReqAfterOpt,
                    $node->lineno ?? 0
                );
            } elseif ($parameter->isOptional()
                && !$is_optional_seen
                && $parameter->getNonVariadicUnionType()->isEmpty()
            ) {
                $is_optional_seen = true;
            }

            $parameter_list[] = $parameter;
        }

        return $parameter_list;
    }

    /**
     * @param array<int,\ReflectionParameter> $reflection_parameters
     * @return array<int,Parameter>
     */
    public static function listFromReflectionParameterList(
        array $reflection_parameters
    ) : array {
        return \array_map(function (\ReflectionParameter $reflection_parameter) {
            return self::fromReflectionParameter($reflection_parameter);
        }, $reflection_parameters);
    }

    public static function fromReflectionParameter(
        \ReflectionParameter $reflection_parameter
    ) : Parameter {
        $flags = 0;
        // Check to see if its a pass-by-reference parameter
        if ($reflection_parameter->isPassedByReference()) {
            $flags |= \ast\flags\PARAM_REF;
        }

        // Check to see if its variadic
        if ($reflection_parameter->isVariadic()) {
            $flags |= \ast\flags\PARAM_VARIADIC;
        }
        $parameter = self::create(
            new Context(),
            $reflection_parameter->getName() ?? "arg",
            UnionType::fromReflectionType($reflection_parameter->getType()),
            $flags
        );
        if ($reflection_parameter->isOptional()) {
            $parameter->setDefaultValueType(
                NullType::instance(false)->asUnionType()
            );
        }
        return $parameter;
    }

    /**
     * @param Node|string|float|int $node
     * @return ?UnionType - Returns if we know the exact type of $node and can easily resolve it
     */
    private static function maybeGetKnownDefaultValueForNode($node)
    {
        if (!($node instanceof Node)) {
            return Type::fromObject($node)->asUnionType();
        }
        if ($node->kind === \ast\AST_CONST) {
            $name = $node->children['name']->children['name'] ?? null;
            if (\is_string($name)) {
                switch (\strtolower($name)) {
                    case 'false':
                        return FalseType::instance(false)->asUnionType();
                    case 'true':
                        return TrueType::instance(false)->asUnionType();
                    case 'null':
                        return NullType::instance(false)->asUnionType();
                }
            }
        }
        return null;
    }

    /**
     * @return Parameter
     * A parameter built from a node
     */
    public static function fromNode(
        Context $context,
        CodeBase $code_base,
        Node $node
    ) : Parameter {
        // Get the type of the parameter
        $type_node = $node->children['type'];
        $union_type = UnionTypeVisitor::unionTypeFromNode(
            $code_base,
            $context,
            $type_node
        );

        // Create the skeleton parameter from what we know so far
        $parameter = Parameter::create(
            $context,
            (string)$node->children['name'],
            $union_type,
            $node->flags ?? 0
        );
        if (($type_node->kind ?? null) === \ast\AST_NULLABLE_TYPE) {
            $parameter->setIsUsingNullableSyntax();
        }

        // If there is a default value, store it and its type
        if (($default_node = $node->children['default']) !== null) {
            // Set the actual value of the default
            $parameter->setDefaultValue($default_node);

            // We can't figure out default values during the
            // parsing phase, unfortunately
            $default_value_union_type = self::maybeGetKnownDefaultValueForNode($default_node);
            if ($default_value_union_type !== null) {
                // Set the default value
                $parameter->setDefaultValueType($default_value_union_type);
            } else {
                \assert($default_node instanceof Node);

                if ($default_node->kind === \ast\AST_ARRAY) {
                    // We know the parameter default is some sort of array, but we don't know any more (e.g. key types, value types).
                    // When the future type is resolved, we'll know something more specific.
                    $default_value_union_type = ArrayType::instance(false)->asUnionType();
                } else {
                    static $possible_parameter_default_union_type = null;
                    if ($possible_parameter_default_union_type === null) {
                        // These can be constants or literals (or null/true/false)
                        $possible_parameter_default_union_type = new UnionType([
                            ArrayType::instance(false),
                            BoolType::instance(false),
                            FloatType::instance(false),
                            IntType::instance(false),
                            StringType::instance(false),
                            NullType::instance(false),
                        ]);
                    }
                    $default_value_union_type = $possible_parameter_default_union_type;
                }
                $parameter->setDefaultValueType($default_value_union_type);
                $parameter->setDefaultValueFutureType(new FutureUnionType(
                    $code_base,
                    clone($context)->withLineNumberStart($default_node->lineno ?? 0),
                    $default_node
                ));
            }
            $parameter->handleDefaultValueOfNull();
        }

        return $parameter;
    }

    /**
     * @return bool
     * True if this is an optional parameter
     */
    public function isOptional() : bool
    {
        return $this->hasDefaultValue();
    }

    /**
     * @return bool
     * True if this is a required parameter
     */
    public function isRequired() : bool
    {
        return !$this->isOptional();
    }

    /**
     * @return bool
     * True if this parameter is variadic, i.e. can
     * take an unlimited list of parameters and express
     * them as an array.
     */
    public function isVariadic() : bool
    {
        return false;
    }

    /**
     * Returns the Parameter in the form expected by a caller.
     *
     * If this parameter is variadic (e.g. `DateTime ...$args`), then this
     * would return a parameter with the type of the elements (e.g. `DateTime`)
     *
     * If this parameter is not variadic, returns $this.
     *
     * @return static (usually $this)
     */
    public function asNonVariadic()
    {
        return $this;
    }

    /**
     * If this Parameter is variadic, calling `getUnionType`
     * will return an array type such as `DateTime[]`. This
     * method will return the element type (such as `DateTime`)
     * for variadic parameters.
     */
    public function getNonVariadicUnionType() : UnionType
    {
        return self::getUnionType();
    }

    /**
     * @return bool - True when this is a non-variadic clone of a variadic parameter.
     * (We avoid bugs by adding new types to a variadic parameter if this is cloned.)
     * However, error messages still need to convert variadic parameters to a string.
     */
    public function isCloneOfVariadic() : bool
    {
        return false;
    }

    /**
     * Add the given union type to this parameter's union type
     *
     * @param UnionType $union_type
     * The type to add to this parameter's union type
     *
     * @return void
     */
    public function addUnionType(UnionType $union_type)
    {
        parent::setUnionType(self::getUnionType()->withUnionType($union_type));
    }

    /**
     * Add the given type to this parameter's union type
     *
     * @param Type $type
     * The type to add to this parameter's union type
     *
     * @return void
     */
    public function addType(Type $type)
    {
        parent::setUnionType(self::getUnionType()->withType($type));
    }

    /**
     * @return bool
     * True if this parameter is pass-by-reference
     * i.e. prefixed with '&'.
     */
    public function isPassByReference() : bool
    {
        return $this->getFlagsHasState(\ast\flags\PARAM_REF);
    }

    public function getReferenceType() : int
    {
        $flags = $this->getPhanFlags();
        if (Flags::bitVectorHasState($flags, Flags::IS_READ_REFERENCE)) {
            return self::REFERENCE_READ_WRITE;
        } elseif (Flags::bitVectorHasState($flags, Flags::IS_WRITE_REFERENCE)) {
            return self::REFERENCE_WRITE_ONLY;
        }
        return self::REFERENCE_DEFAULT;
    }

    /**
     * @return void
     */
    public function setIsOutputReference()
    {
        $this->enablePhanFlagBits(Flags::IS_WRITE_REFERENCE);
        $this->disablePhanFlagBits(Flags::IS_READ_REFERENCE);
    }

    /**
     * @return void
     */
    private function setIsUsingNullableSyntax()
    {
        $this->enablePhanFlagBits(Flags::IS_PARAM_USING_NULLABLE_SYNTAX);
    }

    public function getIsUsingNullableSyntax() : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_PARAM_USING_NULLABLE_SYNTAX);
    }

    public function __toString() : string
    {
        $string = '';

        $typeObj = $this->getNonVariadicUnionType();
        if (!$typeObj->isEmpty()) {
            $string .= (string)$typeObj . ' ';
        }

        if ($this->isPassByReference()) {
            $string .= '&';
        }

        if ($this->isVariadic()) {
            $string .= '...';
        }

        $string .= "\${$this->getName()}";

        if ($this->hasDefaultValue() && !$this->isVariadic()) {
            $default_value = $this->getDefaultValue();
            if ($default_value instanceof \ast\Node) {
                $string .= ' = null';
            } else {
                $string .= ' = ' . var_export($default_value, true);
            }
        }

        return $string;
    }

    public function toStubString() : string
    {
        $string = '';

        $typeObj = $this->getNonVariadicUnionType();
        if (!$typeObj->isEmpty()) {
            $string .= (string)$typeObj . ' ';
        }

        if ($this->isPassByReference()) {
            $string .= '&';
        }

        if ($this->isVariadic()) {
            $string .= '...';
        }

        $name = $this->getName();
        if (!\preg_match('@' . Comment::WORD_REGEX . '@', $name)) {
            // Some PECL extensions have invalid parameter names.
            // Replace invalid characters with U+FFFD replacement character.
            $name = \preg_replace('@[^a-zA-Z0-9_\x7f-\xff]@', '�', $name);
            if (!\preg_match('@' . Comment::WORD_REGEX . '@', $name)) {
                $name = '_' . $name;
            }
        }

        $string .= "\$$name";

        if ($this->hasDefaultValue() && !$this->isVariadic()) {
            $default_value = $this->getDefaultValue();
            if ($default_value instanceof \ast\Node) {
                $kind = $default_value->kind;
                if ($kind === \ast\AST_NAME) {
                    $default_repr = $default_value->children['name'];
                } elseif ($kind === \ast\AST_ARRAY) {
                    $default_repr = '[]';
                } else {
                    $default_repr = 'null';
                }
            } else {
                $default_repr = var_export($this->getDefaultValue(), true);
            }
            $string .= ' = ' . $default_repr;
        }

        return $string;
    }

    public function asClosureDeclarationParameter() : ClosureDeclarationParameter
    {
        $param_type = $this->getNonVariadicUnionType();
        if ($param_type->isEmpty()) {
            $param_type = MixedType::instance(false)->asUnionType();
        }
        return new ClosureDeclarationParameter(
            $param_type,
            $this->isVariadic(),
            $this->isPassByReference(),
            $this->isOptional()
        );
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\FutureUnionType;
use ast\Node;

interface ConstantInterface
{

    /**
     * @return void
     */
    public function setFutureUnionType(
        FutureUnionType $future_union_type
    );

    /**
     * Sets the node with the AST representing the value of this constant.
     *
     * @param Node|string|float|int $node
     * @return void
     */
    public function setNodeForValue($node);

    /**
     * Gets the node with the AST representing the value of this constant.
     *
     * @return Node|string|float|int
     */
    public function getNodeForValue();
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\Context;
use Phan\Language\FileRef;
use Phan\Language\UnionType;

/**
 * This class wraps a parameter and a element and proxies
 * calls to the element but keeps the name of the parameter
 * allowing us to pass a element into a method as a
 * pass-by-reference parameter so that its value can be
 * updated when re-analyzing the method.
 */
class PassByReferenceVariable extends Variable
{

    /** @var Variable */
    private $parameter;

    /** @var TypedElement|UnaddressableTypedElement TODO: Make a common interface which has methods implemented */
    private $element;

    /** @param TypedElement|UnaddressableTypedElement $element */
    public function __construct(
        Variable $parameter,
        $element
    ) {
        $this->parameter = $parameter;
        $this->element = $element;
    }

    public function getName() : string
    {
        return $this->parameter->getName();
    }

    /**
     * Variables can't be variadic. This is the same as getUnionType for
     * variables, but not necessarily for subclasses. Method will return
     * the element type (such as `DateTime`) for variadic parameters.
     */
    public function getNonVariadicUnionType() : UnionType
    {
        return $this->element->getNonVariadicUnionType();
    }

    public function getUnionType() : UnionType
    {
        return $this->element->getUnionType();
    }

    public function setUnionType(UnionType $type)
    {
        $this->element->setUnionType($type);
    }

    public function getFlags() : int
    {
        return $this->element->getFlags();
    }

    public function getFlagsHasState(int $bits) : bool
    {
        return $this->element->getFlagsHasState($bits);
    }

    public function setFlags(int $flags)
    {
        $this->element->setFlags($flags);
    }

    public function getPhanFlags() : int
    {
        return $this->element->getPhanFlags();
    }

    public function getPhanFlagsHasState(int $bits) : bool
    {
        return $this->element->getPhanFlagsHasState($bits);
    }

    public function setPhanFlags(int $phan_flags)
    {
        $this->element->setPhanFlags($phan_flags);
    }

    public function getContext() : Context
    {
        return $this->element->getContext();
    }

    public function getFileRef() : FileRef
    {
        return $this->element->getFileRef();
    }

    public function isPHPInternal() : bool
    {
        return $this->element->isPHPInternal();
    }
}
<?php
declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\CodeBase;
use Phan\Config;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\Comment\Parameter as CommentParameter;
use Phan\Language\Element\Comment\Method as CommentMethod;
use Phan\Language\Element\Flags;
use Phan\Language\Type;
use Phan\Language\Type\TemplateType;
use Phan\Language\Type\VoidType;
use Phan\Language\UnionType;
use Phan\Library\FileCache;
use Phan\Library\None;
use Phan\Library\Option;
use Phan\Library\Some;

/**
 * Handles extracting information(param types, return types, magic methods/properties, etc.) from phpdoc comments.
 * Instances of Comment contain the extracted information.
 *
 * TODO: Pass the doccomment line's index to the Element that will use the client,
 * so that it can be used for more precise line numbers (E.g. for where magic methods were declared,
 * where functions with no signature types but phpdoc types declared types that are invalid class names, etc.
 *
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class Comment
{
    const ON_CLASS      = 1;
    const ON_VAR        = 2;
    const ON_PROPERTY   = 3;
    const ON_CONST      = 4;
    // TODO: Handle closure.
    const ON_METHOD     = 5;
    const ON_FUNCTION   = 6;

    // List of types that are function-like (e.g. have params and function body)
    const FUNCTION_LIKE = [
        self::ON_METHOD,
        self::ON_FUNCTION,
    ];

    // Lists of types that can have (at)var annotations.
    const HAS_VAR_ANNOTATION = [
        self::ON_METHOD,
        self::ON_FUNCTION,
        self::ON_VAR,
        self::ON_PROPERTY,
        self::ON_CONST,
    ];

    const NAME_FOR_TYPE = [
        self::ON_CLASS      => 'class',
        self::ON_VAR        => 'variable',
        self::ON_PROPERTY   => 'property',
        self::ON_CONST      => 'constant',
        self::ON_METHOD     => 'method',
        self::ON_FUNCTION   => 'function',
    ];

    /**
     * This regex contains a single pattern, which matches a valid PHP identifier.
     * (e.g. for variable names, magic property names, etc.
     * This does not allow backslashes.
     */
    const WORD_REGEX = '([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)';

    /**
     * @var int - contains a subset of flags to set on elements
     * Flags::CLASS_FORBID_UNDECLARED_MAGIC_PROPERTIES
     * Flags::CLASS_FORBID_UNDECLARED_MAGIC_METHODS
     * Flags::IS_DEPRECATED
     */
    private $comment_flags = 0;

    /**
     * @var array<int,CommentParameter>
     * A list of CommentParameters from var declarations
     */
    private $variable_list = [];

    /**
     * @var array<int,CommentParameter>
     * A list of CommentParameters from param declarations
     */
    private $parameter_list = [];

    /**
     * @var array<string,CommentParameter>
     * A map from variable name to CommentParameters from
     * param declarations
     */
    private $parameter_map = [];

    /**
     * @var array<int,TemplateType>
     * A list of template types parameterizing a generic class
     */
    private $template_type_list = [];

    /**
     * @var Option<Type>|None
     * Classes may specify their inherited type explicitly
     * via `(at)inherits Type`.
     */
    private $inherited_type;

    /**
     * @var UnionType|null
     * A UnionType defined by an (at)return directive
     */
    private $return_union_type = null;

    /**
     * @var array<int,string>
     * A list of issue types to be suppressed
     */
    private $suppress_issue_list = [];

    /**
     * @var array<string,CommentParameter>
     * A mapping from magic property parameters to types.
     */
    private $magic_property_map = [];

    /**
     * @var array<string,CommentMethod>
     * A mapping from magic methods to parsed parameters, name, and return types.
     */
    private $magic_method_map = [];

    /**
     * @var UnionType a list of types for (at)throws annotations
     */
    private $throw_union_type;

    /**
     * @var Option<Type>|None
     * An optional class name defined by an (at)phan-closure-scope directive.
     * (overrides the class in which it is analyzed)
     */
    private $closure_scope;

    /**
     * A private constructor meant to ingest a parsed comment
     * docblock.
     *
     * @param int $comment_flags uses the following flags
     * - Flags::IS_DEPRECATED
     *   Set to true if the comment contains a 'deprecated'
     *   directive.
     * - Flags::CLASS_FORBID_UNDECLARED_MAGIC_PROPERTIES
     * - Flags::CLASS_FORBID_UNDECLARED_MAGIC_METHODS
     *
     * @param array<int,CommentParameter> $variable_list
     *
     * @param array<int,CommentParameter> $parameter_list
     *
     * @param array<int,TemplateType> $template_type_list
     * A list of template types parameterizing a generic class
     *
     * @param Option<Type>|None $inherited_type (Note: some issues with templates and narrowing signature types to phpdoc type, added None as a workaround)
     * An override on the type of the extended class
     *
     * @param UnionType $return_union_type
     *
     * @param array<int,string> $suppress_issue_list
     * A list of tags for error type to be suppressed
     *
     * @param array<int,CommentParameter> $magic_property_list
     *
     * @param array<int,CommentMethod> $magic_method_list
     *
     * @param array<string,mixed> $phan_overrides
     *
     * @param Option<Type>|None $closure_scope
     * For closures: Allows us to document the class of the object
     * to which a closure will be bound.
     *
     * @param UnionType $throw_union_type
     */
    private function __construct(
        int $comment_flags,
        array $variable_list,
        array $parameter_list,
        array $template_type_list,
        Option $inherited_type,
        UnionType $return_union_type,
        array $suppress_issue_list,
        array $magic_property_list,
        array $magic_method_list,
        array $phan_overrides,
        Option $closure_scope,
        UnionType $throw_union_type
    ) {
        $this->comment_flags = $comment_flags;
        $this->variable_list = $variable_list;
        $this->parameter_list = $parameter_list;
        $this->template_type_list = $template_type_list;
        $this->inherited_type = $inherited_type;
        $this->return_union_type = $return_union_type;
        $this->suppress_issue_list = $suppress_issue_list;
        $this->closure_scope = $closure_scope;
        $this->throw_union_type = $throw_union_type;

        foreach ($this->parameter_list as $i => $parameter) {
            $name = $parameter->getName();
            if (!empty($name)) {
                // Add it to the named map
                $this->parameter_map[$name] = $parameter;

                // Remove it from the offset map
                unset($this->parameter_list[$i]);
            }
        }
        foreach ($magic_property_list as $property) {
            $name = $property->getName();
            if (!empty($name)) {
                // Add it to the named map
                // TODO: Detect duplicates, emit warning for duplicates.
                // TODO(optional): Emit Issues when a property with only property-read is written to
                // or vice versa.
                $this->magic_property_map[$name] = $property;
            }
        }
        foreach ($magic_method_list as $method) {
            $name = $method->getName();
            if (!empty($name)) {
                // Add it to the named map
                // TODO: Detect duplicates, emit warning for duplicates.
                $this->magic_method_map[$name] = $method;
            }
        }
        foreach ($phan_overrides as $key => $override_value) {
            $this->applyOverride($key, $override_value);
        }
    }

    /**
     * @param mixed $value
     * @return void
     */
    private function applyOverride(string $key, $value)
    {
        switch ($key) {
            case 'param':
                foreach ($value as $parameter) {
                    $name = $parameter->getName();
                    if (!empty($name)) {
                        // Add it to the named map
                        // TODO: could check that @phan-param is compatible with the original @param
                        $this->parameter_map[$name] = $parameter;
                    }
                }
                return;
            case 'return':
                // TODO: could check that @phan-return is compatible with the original @return
                $this->return_union_type = $value;
                return;
            case 'var':
                // TODO: Remove pre-existing entries.
                $this->mergeVariableList($value);
                return;
            case 'property':
                foreach ($value as $property) {
                    $name = $property->getName();
                    if (!empty($name)) {
                        // Override or add the entry in the named map
                        $this->magic_property_map[$name] = $property;
                    }
                }
                return;
            case 'method':
                foreach ($value as $method) {
                    $name = $method->getName();
                    if (!empty($name)) {
                        // Override or add the entry in the named map
                        $this->magic_method_map[$name] = $method;
                    }
                }
                return;
        }
    }

    /**
     * @var array<int,CommentParameter>
     * A list of CommentParameters from var declarations
     */
    private function mergeVariableList(array $override_comment_vars)
    {
        $known_names = [];
        foreach ($override_comment_vars as $override_var) {
            $known_names[$override_var->getName()] = true;
        }
        foreach ($this->variable_list as $i => $var) {
            if (isset($known_names[$var->getName()])) {
                unset($this->variable_list[$i]);
            }
        }
        $this->variable_list = array_merge($this->variable_list, $override_comment_vars);
    }


    private static function guessActualLineLocation(Context $context, int $declaration_lineno, int $i, int $comment_lines_count, string $line)
    {
        $path = Config::projectPath($context->getFile());
        $entry = FileCache::getEntry($path);
        if (!$entry) {
            return $declaration_lineno;
        }
        // $lineno_search <= $declaration_lineno
        $lineno_search = $declaration_lineno - ($comment_lines_count - $i - 1);
        // Search up to 10 lines before $lineno_search
        $lineno_stop = \max(1, $lineno_search - 9);
        $lines_array = $entry->getLines();
        $j = $i;
        for ($check_lineno = $lineno_search; $check_lineno >= $lineno_stop; $check_lineno--) {
            $cur_line = $lines_array[$check_lineno];
            if (\stripos($cur_line, $line) !== false) {
                // Better heuristic: Lines in the middle of phpdoc are guaranteed to be complete, including a few newlines at the end.
                $j = $i - ($lineno_search - $check_lineno);
                if ($j > 0 && $j < $comment_lines_count - 1) {
                    if (\trim($line) !== \trim($cur_line)) {
                        continue;
                    }
                }
                return $check_lineno;
            }
        }
        // We couldn't identify the line;
        return $declaration_lineno;
    }

    /**
     * @param string $comment full text of doc comment
     * @param CodeBase $code_base
     * @param Context $context
     * @param int $comment_type self::ON_* (the type of comment this is)
     * @return Comment
     * A comment built by parsing the given doc block
     * string.
     *
     * suppress PhanTypeMismatchArgument - Still need to work out issues with prefer_narrowed_phpdoc_param_type
     */
    public static function fromStringInContext(
        string $comment,
        CodeBase $code_base,
        Context $context,
        int $lineno,
        int $comment_type
    ) : Comment {

        if (!Config::getValue('read_type_annotations')) {
            return new Comment(
                0,
                [],
                [],
                [],
                new None,
                UnionType::empty(),
                [],
                [],
                [],
                [],
                new None,
                UnionType::empty()
            );
        }

        $variable_list = [];
        $parameter_list = [];
        $template_type_list = [];
        $inherited_type = new None;
        $return_union_type = UnionType::empty();
        $suppress_issue_list = [];
        $magic_property_list = [];
        $magic_method_list = [];
        $closure_scope = new None;
        $comment_flags = 0;
        $phan_overrides = [];
        $throw_union_type = UnionType::empty();

        $lines = \explode("\n", $comment);
        $comment_lines_count = \count($lines);

        /**
         * @param array<int,int> $validTypes
         * @return void
         */
        $check_compatible = function (string $paramName, array $validTypes, int $i, string $line) use ($code_base, $context, $comment_type, $lineno, $comment_lines_count) {
            if (!\in_array($comment_type, $validTypes, true)) {
                self::emitInvalidCommentForDeclarationType(
                    $code_base,
                    $context,
                    $paramName,
                    $comment_type,
                    self::guessActualLineLocation($context, $lineno, $i, $comment_lines_count, $line)
                );
            }
        };

        foreach ($lines as $i => $line) {
            if (\strpos($line, '@') === false) {
                continue;
            }
            $line = \trim($line);
            // https://secure.php.net/manual/en/regexp.reference.internal-options.php
            // (?i) makes this case sensitive, (?-1) makes it case insensitive
            if (\preg_match('/@((?i)param|var|return|throws|throw|returns|inherits|suppress|phan-[a-z0-9_-]*(?-i)|method|property|template|PhanClosureScope)\b/', $line, $matches)) {
                $type = \strtolower($matches[1]);

                if ($type === 'param') {
                    $check_compatible('@param', Comment::FUNCTION_LIKE, $i, $line);
                    $parameter_list[] =
                        self::parameterFromCommentLine($code_base, $context, $line, false, $lineno, $i, $comment_lines_count);
                } elseif ($type === 'var') {
                    $check_compatible('@var', Comment::HAS_VAR_ANNOTATION, $i, $line);
                    $comment_var = self::parameterFromCommentLine($code_base, $context, $line, true, $lineno, $i, $comment_lines_count);
                    if ($comment_var->getName() !== '' || !\in_array($comment_type, self::FUNCTION_LIKE)) {
                        $variable_list[] = $comment_var;
                    }
                } elseif ($type === 'template') {
                    // Make sure support for generic types is enabled
                    if (Config::getValue('generic_types_enabled')) {
                        $check_compatible('@template', [Comment::ON_CLASS], $i, $line);
                        $template_type = self::templateTypeFromCommentLine($line);
                        if ($template_type) {
                            $template_type_list[] = $template_type;
                        }
                    }
                } elseif ($type === 'inherits') {
                    $check_compatible('@inherits', [Comment::ON_CLASS], $i, $line);
                    // Make sure support for generic types is enabled
                    if (Config::getValue('generic_types_enabled')) {
                        $inherited_type =
                            self::inheritsFromCommentLine($context, $line);
                    }
                } elseif ($type === 'return') {
                    $check_compatible('@return', Comment::FUNCTION_LIKE, $i, $line);
                    $type = self::returnTypeFromCommentLine($code_base, $context, $line, $lineno, $i, $comment_lines_count)->withUnionType($return_union_type);
                    if (!$type->isEmpty()) {
                        $return_union_type = $type;
                    }
                } elseif ($type === 'returns') {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::MisspelledAnnotation,
                        self::guessActualLineLocation($context, $lineno, $i, $comment_lines_count, $line),
                        '@returns',
                        '@return'
                    );
                } elseif ($type === 'throws') {
                    $check_compatible('@throws', Comment::FUNCTION_LIKE, $i, $line);
                    $throw_union_type = $throw_union_type->withUnionType(self::returnTypeFromCommentLine($code_base, $context, $line, $lineno, $i, $comment_lines_count));
                } elseif ($type === 'throw') {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::MisspelledAnnotation,
                        self::guessActualLineLocation($context, $lineno, $i, $comment_lines_count, $line),
                        '@throw',
                        '@throws'
                    );
                } elseif ($type === 'suppress') {
                    $suppress_issue_type = self::suppressIssueFromCommentLine($line);
                    if ($suppress_issue_type !== '') {
                        $suppress_issue_list[] = $suppress_issue_type;
                    } else {
                        Issue::maybeEmit(
                            $code_base,
                            $context,
                            Issue::UnextractableAnnotation,
                            self::guessActualLineLocation($context, $lineno, $i, $comment_lines_count, $line),
                            trim($line)
                        );
                    }
                } elseif ($type === 'property') {
                    $check_compatible('@property', [Comment::ON_CLASS], $i, $line);
                    // Make sure support for magic properties is enabled.
                    if (Config::getValue('read_magic_property_annotations')) {
                        $magic_property = self::magicPropertyFromCommentLine($code_base, $context, $line, $lineno);
                        if ($magic_property !== null) {
                            $magic_property_list[] = $magic_property;
                        }
                    }
                } elseif ($type === 'method') {
                    // Make sure support for magic methods is enabled.
                    if (Config::getValue('read_magic_method_annotations')) {
                        $check_compatible('@method', [Comment::ON_CLASS], $i, $line);
                        $magic_method = self::magicMethodFromCommentLine($code_base, $context, $line, $lineno, $i, $comment_lines_count);
                        if ($magic_method !== null) {
                            $magic_method_list[] = $magic_method;
                        }
                    }
                } elseif ($type === 'phanclosurescope' || $type === 'phan-closure_scope') {
                    // TODO: different type for closures
                    $check_compatible('@PhanClosureScope', Comment::FUNCTION_LIKE, $i, $line);
                    $closure_scope = self::getPhanClosureScopeFromCommentLine($code_base, $context, $line, $lineno, $i, $comment_lines_count);
                } elseif (\strpos($type, 'phan-') === 0) {
                    if ($type === 'phan-forbid-undeclared-magic-properties') {
                        $check_compatible('@phan-forbid-undeclared-magic-properties', [Comment::ON_CLASS], $i, $line);
                        $comment_flags |= Flags::CLASS_FORBID_UNDECLARED_MAGIC_PROPERTIES;
                    } elseif ($type === 'phan-forbid-undeclared-magic-methods') {
                        $check_compatible('@phan-forbid-undeclared-magic-methods', [Comment::ON_CLASS], $i, $line);
                        $comment_flags |= Flags::CLASS_FORBID_UNDECLARED_MAGIC_METHODS;
                    } elseif ($type === 'phan-closure-scope') {
                        $check_compatible('@phan-closure-scope', Comment::FUNCTION_LIKE, $i, $line);
                        $closure_scope = self::getPhanClosureScopeFromCommentLine($code_base, $context, $line, $lineno, $i, $comment_lines_count);
                    } elseif ($type === 'phan-param') {
                        $check_compatible('@phan-param', Comment::FUNCTION_LIKE, $i, $line);
                        $phan_overrides['param'][] =
                            self::parameterFromCommentLine($code_base, $context, $line, false, $lineno, $i, $comment_lines_count);
                    } elseif ($type === 'phan-return') {
                        $check_compatible('@phan-return', Comment::FUNCTION_LIKE, $i, $line);
                        $phan_overrides['return'] = self::returnTypeFromCommentLine($code_base, $context, $line, $lineno, $i, $comment_lines_count);
                    } elseif ($type === 'phan-override') {
                        $check_compatible('@override', [Comment::ON_METHOD, Comment::ON_CONST], $i, $line);
                        $comment_flags |= Flags::IS_OVERRIDE_INTENDED;
                    } elseif ($type === 'phan-var') {
                        $check_compatible('@phan-var', Comment::HAS_VAR_ANNOTATION, $i, $line);
                        $comment_var = self::parameterFromCommentLine($code_base, $context, $line, true, $lineno, $i, $comment_lines_count);
                        if ($comment_var->getName() !== '' || !\in_array($comment_type, self::FUNCTION_LIKE)) {
                            $phan_overrides['var'][] = $comment_var;
                        }
                    } elseif ($type === 'phan-file-suppress') {
                        $suppress_issue_type = self::fileSuppressIssueFromCommentLine($line);
                        if ($suppress_issue_type) {
                            $code_base->addFileLevelSuppression($context->getFile(), $suppress_issue_type);
                        }
                    } elseif ($type === 'phan-suppress') {
                        $suppress_issue_type = self::suppressIssueFromCommentLine($line);
                        if ($suppress_issue_type !== '') {
                            $suppress_issue_list[] = $suppress_issue_type;
                        }
                    } elseif ($type === 'phan-property' || $type === 'phan-property-read' || $type === 'phan-property-write') {
                        $check_compatible('@phan-property', [Comment::ON_CLASS], $i, $line);
                        // Make sure support for magic properties is enabled.
                        if (Config::getValue('read_magic_property_annotations')) {
                            $magic_property = self::magicPropertyFromCommentLine($code_base, $context, $line, $lineno);
                            if ($magic_property !== null) {
                                $phan_overrides['property'][] = $magic_property;
                            }
                        }
                    } elseif ($type === 'phan-method') {
                        // Make sure support for magic methods is enabled.
                        if (Config::getValue('read_magic_method_annotations')) {
                            $check_compatible('@phan-method', [Comment::ON_CLASS], $i, $line);
                            $magic_method = self::magicMethodFromCommentLine($code_base, $context, $line, $lineno, $i, $comment_lines_count);
                            if ($magic_method !== null) {
                                $phan_overrides['method'][] = $magic_method;
                            }
                        }
                    } else {
                        Issue::maybeEmit(
                            $code_base,
                            $context,
                            Issue::MisspelledAnnotation,
                            self::guessActualLineLocation($context, $lineno, $i, $comment_lines_count, $line),
                            '@' . $matches[1],
                            '@phan-forbid-undeclared-magic-methods @phan-forbid-undeclared-magic-properties @phan-closure-scope @phan-override'
                        );
                    }
                }
            }

            if (\strpos($line, '@deprecated') !== false) {
                if (\preg_match('/@deprecated\b/', $line, $match)) {
                    $comment_flags |= Flags::IS_DEPRECATED;
                }
            }

            if (\strpos($line, '@internal') !== false) {
                if (\preg_match('/@internal\b/', $line, $match)) {
                    $comment_flags |= Flags::IS_NS_INTERNAL;
                }
            }

            if (\stripos($line, 'override') !== false) {
                if (\preg_match('/@([Oo]verride)\b/', $line, $match)) {
                    // TODO: split class const and global const.
                    $check_compatible('@override', [Comment::ON_METHOD, Comment::ON_CONST], $i, $line);
                    $comment_flags |= Flags::IS_OVERRIDE_INTENDED;
                }
            }
        }

        return new Comment(
            $comment_flags,
            $variable_list,
            $parameter_list,
            $template_type_list,
            $inherited_type,
            $return_union_type,
            $suppress_issue_list,
            $magic_property_list,
            $magic_method_list,
            $phan_overrides,
            $closure_scope,
            $throw_union_type
        );
    }

    /**
     * @return void
     */
    private static function emitInvalidCommentForDeclarationType(
        CodeBase $code_base,
        Context $context,
        string $annotation_type,
        int $comment_type,
        int $lineno
    ) {
        Issue::maybeEmit(
            $code_base,
            $context,
            Issue::InvalidCommentForDeclarationType,
            $lineno,
            $annotation_type,
            self::NAME_FOR_TYPE[$comment_type]
        );
    }

    // TODO: Is `@return &array` valid phpdoc2?
    const return_comment_regex = '/@(?:phan-)?(?:return|throws)\s+(&\s*)?(' . UnionType::union_type_regex_or_this . '+)/';

    /**
     * @param Context $context
     * The context in which the comment line appears
     *
     * @param string $line
     * An individual line of a comment
     *
     * @return UnionType
     * The declared return type
     * @suppress PhanParamSuspiciousOrder strstr
     */
    private static function returnTypeFromCommentLine(
        CodeBase $code_base,
        Context $context,
        string $line,
        int $lineno,
        int $i,
        int $comment_lines_count
    ) {
        $return_union_type_string = '';

        if (\preg_match(self::return_comment_regex, $line, $match)) {
            $return_union_type_string = $match[2];
            $raw_match = $match[0];
            $char_at_end_offset = $line[\strpos($line, $raw_match) + \strlen($raw_match)] ?? ' ';
            if (\ord($char_at_end_offset) > 32) {  // Not a control character or space
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::UnextractableAnnotationSuffix,
                    self::guessActualLineLocation($context, $lineno, $i, $comment_lines_count, $line),
                    \trim($line),
                    $return_union_type_string,
                    $char_at_end_offset
                );
            }
        } else {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::UnextractableAnnotation,
                self::guessActualLineLocation($context, $lineno, $i, $comment_lines_count, $line),
                trim($line)
            );
        }
        // Not emitting any issues about failing to extract, e.g. `@return - Description of what this returns` is a valid comment.
        $return_union_type_string = self::rewritePHPDocType($return_union_type_string);

        $return_union_type = UnionType::fromStringInContext(
            $return_union_type_string,
            $context,
            Type::FROM_PHPDOC
        );

        return $return_union_type;
    }

    private static function rewritePHPDocType(
        string $original_type
    ) : string {
        // TODO: Would need to pass in CodeBase to emit an issue:
        $type = Config::get()->phpdoc_type_mapping[\strtolower($original_type)] ?? null;
        if (\is_string($type)) {
            return $type;
        }
        return $original_type;
    }

    const param_comment_regex =
        '/@(?:phan-)?(param|var)\b\s*(' . UnionType::union_type_regex . ')?(?:\s*(\.\.\.)?\s*&?(?:\\$' . self::WORD_REGEX . '))?/';

    /**
     * @param CodeBase $code_base
     * CodeBase, for emitting issues.
     *
     * @param Context $context
     * The context in which the comment line appears
     *
     * @param string $line
     * An individual line of a comment
     *
     * @param bool $is_var
     * True if this is parsing a variable, false if parsing a parameter.
     *
     * @param int $lineno
     * The line number of the element this comment annotates.
     *
     * @return CommentParameter
     * A CommentParameter associated with a line that has a var
     * or param reference.
     *
     * TODO: account for difference between (at)var and (at)param
     */
    private static function parameterFromCommentLine(
        CodeBase $code_base,
        Context $context,
        string $line,
        bool $is_var,
        int $lineno,
        int $i,
        int $comment_lines_count
    ) {
        $matched = \preg_match(self::param_comment_regex, $line, $match);
        // Parse https://docs.phpdoc.org/references/phpdoc/tags/param.html
        // Exceptions: Deliberately allow "&" in "@param int &$x" when documenting references.
        // Warn if there is neither a union type nor a variable
        if ($matched && (isset($match[2]) || isset($match[21]))) {
            if (!isset($match[2])) {
                return new CommentParameter('', UnionType::empty());
            }
            if (!$is_var && !isset($match[21])) {
                self::checkParamWithoutVarName($code_base, $context, $line, $match[0], $match[2], $lineno, $i, $comment_lines_count);
            }
            $original_type = $match[2];

            $is_variadic = ($match[20] ?? '') === '...';

            if ($is_var && $is_variadic) {
                $variable_name = '';  // "@var int ...$x" is nonsense and invalid phpdoc.
            } else {
                $variable_name = $match[21] ?? '';
            }
            // Fix typos or non-standard phpdoc tags, according to the user's configuration.
            // Does nothing by default.
            $type = self::rewritePHPDocType($original_type);

            // If the type looks like a variable name, make it an
            // empty type so that other stuff can match it. We can't
            // just skip it or we'd mess up the parameter order.
            $union_type = null;
            if (0 !== \strpos($type, '$')) {
                $union_type =
                    UnionType::fromStringInContext(
                        $type,
                        $context,
                        Type::FROM_PHPDOC
                    );
            } else {
                $union_type = UnionType::empty();
            }
            $is_output_parameter = \stripos($line, '@phan-output-reference') !== false;

            return new CommentParameter(
                $variable_name,
                $union_type,
                $is_variadic,
                false,  // has_default_value
                $is_output_parameter
            );
        }

        // Don't warn about @param $x Description of $x goes here
        // TODO: extract doc comment of @param &$x?
        // TODO: Use the right for the name of the comment parameter?
        //       (don't see a benefit, would create a type if it was (at)var on a function-like)
        if (!\preg_match('/@(param|var)\s+(\.\.\.)?\s*(\\$\S+)/', $line)) {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::UnextractableAnnotation,
                self::guessActualLineLocation($context, $lineno, $i, $comment_lines_count, $line),
                \trim($line)
            );
        }

        return new CommentParameter('', UnionType::empty());
    }

    /**
     * This should be uncommon: $line is a parameter for which a parameter name could not be parsed
     * @suppress PhanParamSuspiciousOrder
     */
    private static function checkParamWithoutVarName(
        CodeBase $code_base,
        Context $context,
        string $line,
        string $raw_match,
        string $union_type_string,
        int $lineno,
        int $i,
        int $comment_lines_count
    ) {

        $match_offset = \strpos($line, $raw_match);
        $end_offset = $match_offset + strlen($raw_match);

        $char_at_end_offset = $line[$end_offset] ?? ' ';
        $issue_line = self::guessActualLineLocation($context, $lineno, $i, $comment_lines_count, $line);
        if (\ord($char_at_end_offset) > 32) {  // Not a control character or space
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::UnextractableAnnotationSuffix,
                $issue_line,
                \trim($line),
                $union_type_string,
                $char_at_end_offset
            );
        }

        Issue::maybeEmit(
            $code_base,
            $context,
            Issue::UnextractableAnnotationElementName,
            $issue_line,
            \trim($line),
            $union_type_string
        );
    }

    /**
     * @param string $line
     * An individual line of a comment
     *
     * @return TemplateType|null
     * A generic type identifier or null if a valid type identifier
     * wasn't found.
     */
    private static function templateTypeFromCommentLine(
        string $line
    ) {
        // TODO: Just use WORD_REGEX? Backslashes or nested templates wouldn't make sense.
        if (preg_match('/@template\s+(' . Type::simple_type_regex. ')/', $line, $match)) {
            $template_type_identifier = $match[1];
            return new TemplateType($template_type_identifier);
        }

        return null;
    }

    /**
     * @param Context $context
     * The context in which the comment line appears
     *
     * @param string $line
     * An individual line of a comment
     *
     * @return Option<Type>
     * An optional type overriding the extended type of the class
     */
    private static function inheritsFromCommentLine(
        Context $context,
        string $line
    ) {
        $match = [];
        if (preg_match('/@inherits\s+(' . Type::type_regex . ')/', $line, $match)) {
            $type_string = $match[1];

            $type = new Some(Type::fromStringInContext(
                $type_string,
                $context,
                Type::FROM_PHPDOC
            ));

            return $type;
        }

        return new None();
    }

    /**
     * @param string $line
     * An individual line of a comment
     *
     * @return string
     * An issue name to suppress
     */
    private static function suppressIssueFromCommentLine(
        string $line
    ) : string {
        if (preg_match('/@(?:phan-)?suppress\s+' . self::WORD_REGEX . '/', $line, $match)) {
            return $match[1];
        }

        return '';
    }

    const PHAN_FILE_SUPPRESS_REGEX =
        '/@phan-file-suppress\s+' . self::WORD_REGEX . '/';

    private static function fileSuppressIssueFromCommentLine(
        string $line
    ) : string {
        if (preg_match(self::PHAN_FILE_SUPPRESS_REGEX, $line, $match)) {
            return $match[1];
        }

        return '';
    }

    /** @internal */
    const magic_param_regex = '/^(' . UnionType::union_type_regex . ')?\s*(?:(\.\.\.)\s*)?(?:\$' . self::WORD_REGEX . ')?((?:\s*=.*)?)$/';

    /**
     * Parses a magic method based on https://phpdoc.org/docs/latest/references/phpdoc/tags/method.html
     * @return ?CommentParameter - if null, the phpdoc magic method was invalid.
     */
    private static function magicParamFromMagicMethodParamString(
        Context $context,
        string $param_string,
        int $param_index
    ) {
        $param_string = trim($param_string);
        // Don't support trailing commas, or omitted params. Provide at least one of [type] or [parameter]
        if ($param_string === '') {
            return null;
        }
        // Parse an entry for [type] [parameter] - Assume both of those are optional.
        // https://github.com/phpDocumentor/phpDocumentor2/pull/1271/files - phpdoc allows passing an default value.
        // Phan allows `=.*`, to indicate that a parameter is optional
        // TODO: in another PR, check that optional parameters aren't before required parameters.
        if (preg_match(self::magic_param_regex, $param_string, $param_match)) {
            // Note: a magic method parameter can be variadic, but it can't be pass-by-reference? (No support in __call)
            $union_type_string = $param_match[1];
            $union_type = UnionType::fromStringInContext(
                $union_type_string,
                $context,
                Type::FROM_PHPDOC
            );
            $is_variadic = $param_match[19] === '...';
            $default_str = $param_match[21];
            $has_default_value = $default_str !== '';
            if ($has_default_value) {
                $default_value_repr = trim(explode('=', $default_str, 2)[1]);
                if (strcasecmp($default_value_repr, 'null') === 0) {
                    $union_type = $union_type->nullableClone();
                }
            }
            $var_name = $param_match[20];
            if ($var_name === '') {
                // placeholder names are p1, p2, ...
                $var_name = 'p' . ($param_index + 1);
            }
            return new CommentParameter($var_name, $union_type, $is_variadic, $has_default_value);
        }
        return null;
    }

    /**
     * @param CodeBase $code_base
     * @param Context $context
     * @param string $line
     * An individual line of a comment
     *
     * @return ?CommentMethod
     * magic method with the parameter types, return types, and name.
     */
    private static function magicMethodFromCommentLine(
        CodeBase $code_base,
        Context $context,
        string $line,
        int $lineno,
        int $comment_line_offset,
        int $comment_lines_count
    ) {
        // https://phpdoc.org/docs/latest/references/phpdoc/tags/method.html
        // > Going to assume "static" is a magic keyword, based on https://github.com/phpDocumentor/phpDocumentor2/issues/822
        // > TODO: forbid in trait?
        // TODO: finish writing the regex.
        // Syntax:
        //    @method [return type] [name]([[type] [parameter]<, ...>]) [<description>]
        //    Assumes the parameters end at the first ")" after "("
        //    As an exception, allows one level of matching brackets
        //    to support old style arrays such as $x = array(), $x = array(2) (Default values are ignored)
        if (preg_match('/@(?:phan-)?method(?:\s+(static))?(?:(?:\s+(' . UnionType::union_type_regex_or_this . '))?)\s+' . self::WORD_REGEX . '\s*\(((?:[^()]|\([()]*\))*)\)\s*(.*)/', $line, $match)) {
            $is_static = $match[1] === 'static';
            $return_union_type_string = $match[2];
            if ($return_union_type_string !== '') {
                $return_union_type =
                    UnionType::fromStringInContext(
                        $return_union_type_string,
                        $context,
                        Type::FROM_PHPDOC
                    );
            } else {
                // From https://phpdoc.org/docs/latest/references/phpdoc/tags/method.html
                // > When the intended method does not have a return value then the return type MAY be omitted; in which case 'void' is implied.
                $return_union_type = VoidType::instance(false)->asUnionType();
            }
            $method_name = $match[26];

            $arg_list = trim($match[27]);
            $comment_params = [];
            // Special check if param list has 0 params.
            if ($arg_list !== '') {
                // TODO: Would need to use a different approach if templates were ever supported
                //       e.g. The magic method parsing doesn't support commas?
                $params_strings = explode(',', $arg_list);
                $failed = false;
                foreach ($params_strings as $i => $param_string) {
                    $param = self::magicParamFromMagicMethodParamString($context, $param_string, $i);
                    if ($param === null) {
                        Issue::maybeEmit(
                            $code_base,
                            $context,
                            Issue::UnextractableAnnotationPart,
                            self::guessActualLineLocation($context, $lineno, $comment_line_offset, $comment_lines_count, $line),
                            \trim($line),
                            $param_string
                        );
                        $failed = true;
                    }
                    $comment_params[] = $param;
                }
                if ($failed) {
                    // Emit everything that was wrong with the parameters of the @method annotation at once, then reject it.
                    return null;
                }
            }

            // TODO: Track the line number for comment methods?
            return new CommentMethod($method_name, $return_union_type, $comment_params, $is_static);
        } else {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::UnextractableAnnotation,
                self::guessActualLineLocation($context, $lineno, $comment_line_offset, $comment_lines_count, $line),
                trim($line)
            );
        }

        return null;
    }

    /**
     * @param Context $context
     * @param string $line
     * An individual line of a comment
     * Currently treats property-read and property-write the same way
     * because of the rewrites required for read-only properties.
     *
     * @return CommentParameter|null
     * magic property with the union type.
     */
    private static function magicPropertyFromCommentLine(
        CodeBase $code_base,
        Context $context,
        string $line,
        int $lineno
    ) {
        // Note that the type of a property can be left out (@property $myVar) - This is equivalent to @property mixed $myVar
        // TODO: properly handle duplicates...
        // TODO: support read-only/write-only checks elsewhere in the codebase?
        if (\preg_match('/@(?:phan-)?(property|property-read|property-write)(?:\s+(' . UnionType::union_type_regex . '))?(?:\s+(?:\\$' . self::WORD_REGEX . '))/', $line, $match)) {
            $type = $match[2] ?? '';

            $property_name = $match[20] ?? '';
            if ($property_name === '') {
                return null;
            }

            // If the type looks like a property name, make it an
            // empty type so that other stuff can match it.
            $union_type =
                UnionType::fromStringInContext(
                    $type,
                    $context,
                    Type::FROM_PHPDOC
                );

            return new CommentParameter(
                $property_name,
                $union_type
            );
        } else {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::UnextractableAnnotation,
                $lineno,
                trim($line)
            );
        }

        return null;
    }

    /**
     * The context in which the comment line appears
     *
     * @param string $line
     * An individual line of a comment
     *
     * @return Option<Type>
     * A class/interface to use as a context for a closure.
     * (Phan expects a ClassScope to have exactly one type)
     */
    private static function getPhanClosureScopeFromCommentLine(
        CodeBase $code_base,
        Context $context,
        string $line,
        int $lineno,
        int $comment_line_offset,
        int $comment_lines_count
    ) : Option {
        $closure_scope_union_type_string = '';

        // https://secure.php.net/manual/en/closure.bindto.php
        // There wasn't anything in the phpdoc standard to indicate the class to which
        // a Closure would be bound with bind() or bindTo(), so using a custom tag.
        //
        // TODO: Also add a version which forbids using $this in the closure?
        if (preg_match('/@(PhanClosureScope|phan-closure-scope)\s+(' . Type::type_regex . ')/', $line, $match)) {
            $closure_scope_union_type_string = $match[2];
        }

        if ($closure_scope_union_type_string !== '') {
            return new Some(Type::fromStringInContext(
                $closure_scope_union_type_string,
                $context,
                Type::FROM_PHPDOC
            ));
        }
        Issue::maybeEmit(
            $code_base,
            $context,
            Issue::UnextractableAnnotation,
            self::guessActualLineLocation($context, $lineno, $comment_line_offset, $comment_lines_count, $line),
            trim($line)
        );
        return new None();
    }

    /**
     * @return bool
     * Set to true if the comment contains a 'deprecated'
     * directive.
     */
    public function isDeprecated() : bool
    {
        return ($this->comment_flags & Flags::IS_DEPRECATED) != 0;
    }

    /**
     * @return bool
     * Set to true if the comment contains an 'override'
     * directive.
     */
    public function isOverrideIntended() : bool
    {
        return ($this->comment_flags & Flags::IS_OVERRIDE_INTENDED) != 0;
    }

    /**
     * @return bool
     * Set to true if the comment contains an 'internal'
     * directive.
     */
    public function isNSInternal() : bool
    {
        return ($this->comment_flags & Flags::IS_NS_INTERNAL) != 0;
    }

    /**
     * @return bool
     * Set to true if the comment contains a 'phan-forbid-undeclared-magic-properties'
     * directive.
     */
    public function getForbidUndeclaredMagicProperties() : bool
    {
        return ($this->comment_flags & Flags::CLASS_FORBID_UNDECLARED_MAGIC_PROPERTIES) != 0;
    }

    /**
     * @return bool
     * Set to true if the comment contains a 'phan-forbid-undeclared-magic-methods'
     * directive.
     */
    public function getForbidUndeclaredMagicMethods() : bool
    {
        return ($this->comment_flags & Flags::CLASS_FORBID_UNDECLARED_MAGIC_METHODS) != 0;
    }

    /**
     * @return UnionType
     * A UnionType defined by a (at)return directive
     */
    public function getReturnType() : UnionType
    {
        return $this->return_union_type;
    }

    /**
     * Sets A UnionType defined by a (at)return directive
     * @return void
     * @suppress PhanUnreferencedPublicMethod not used right now, but making it available for plugins
     */
    public function setReturnType(UnionType $return_union_type)
    {
        $this->return_union_type = $return_union_type;
    }

    /**
     * @return bool
     * True if this doc block contains a (at)return
     * directive specifying a type.
     */
    public function hasReturnUnionType() : bool
    {
        return !$this->return_union_type->isEmpty();
    }

    /**
     * @return Option<Type>
     * An optional Type defined by a (at)PhanClosureScope
     * directive specifying a single type.
     *
     * @suppress PhanPartialTypeMismatchReturn (Null)
     */
    public function getClosureScopeOption() : Option
    {
        return $this->closure_scope;
    }

    /**
     * @return array<int,CommentParameter> (The leftover parameters without a name)
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function getParameterList() : array
    {
        return $this->parameter_list;
    }

    /**
     * @return array<string,CommentParameter> (maps the names of parameters to their values. Does not include parameters which didn't provide names)
     *
     * @suppress PhanUnreferencedPublicMethod
     * @suppress PhanPartialTypeMismatchReturn (Null)
     */
    public function getParameterMap() : array
    {
        return $this->parameter_map;
    }

    /**
     * @return array<int,TemplateType>
     * A list of template types parameterizing a generic class
     */
    public function getTemplateTypeList() : array
    {
        return $this->template_type_list;
    }

    /**
     * @return Option<Type>
     * An optional type declaring what a class extends.
     * @suppress PhanPartialTypeMismatchReturn (Null)
     */
    public function getInheritedTypeOption() : Option
    {
        return $this->inherited_type;
    }

    /**
     * @return array<int,string>
     * A set of issue names like 'PhanUnreferencedPublicMethod' to suppress
     */
    public function getSuppressIssueList() : array
    {
        return $this->suppress_issue_list;
    }

    /**
     * @return bool
     * True if we have a parameter at the given offset
     */
    public function hasParameterWithNameOrOffset(
        string $name,
        int $offset
    ) : bool {
        if (!empty($this->parameter_map[$name])) {
            return true;
        }

        return !empty($this->parameter_list[$offset]);
    }

    /**
     * @return CommentParameter
     * The parameter at the given offset
     */
    public function getParameterWithNameOrOffset(
        string $name,
        int $offset
    ) : CommentParameter {
        if (!empty($this->parameter_map[$name])) {
            return $this->parameter_map[$name];
        }

        return $this->parameter_list[$offset];
    }

    /**
     * @unused
     * @return bool
     * True if we have a magic property with the given name
     */
    public function hasMagicPropertyWithName(
        string $name
    ) : bool {
        return isset($this->magic_property_map[$name]);
    }

    /**
     * @return CommentParameter
     * The magic property with the given name. May or may not have a type.
     * @unused
     * @suppress PhanUnreferencedPublicMethod not used right now, but making it available for plugins
     */
    public function getMagicPropertyWithName(
        string $name
    ) : CommentParameter {
        return $this->magic_property_map[$name];
    }

    /**
     * @return array<string,CommentParameter> map from parameter name to parameter
     */
    public function getMagicPropertyMap() : array
    {
        return $this->magic_property_map;
    }

    /**
     * @return array<string,CommentMethod> map from method name to method info
     */
    public function getMagicMethodMap() : array
    {
        return $this->magic_method_map;
    }

    /**
     * @return UnionType list of types for throws statements
     */
    public function getThrowsUnionType() : UnionType
    {
        return $this->throw_union_type;
    }

    /**
     * @return array<int,CommentParameter>
     */
    public function getVariableList() : array
    {
        return $this->variable_list;
    }

    public function __toString() : string
    {
        // TODO: add new properties of Comment to this method
        // (magic methods, magic properties, custom @phan directives, etc.))
        $string = "/**\n";

        if (($this->comment_flags & Flags::IS_DEPRECATED) != 0) {
            $string  .= " * @deprecated\n";
        }

        foreach ($this->variable_list as $variable) {
            $string  .= " * @var $variable\n";
        }

        foreach ($this->parameter_list as $parameter) {
            $string  .= " * @param $parameter\n";
        }

        if ($this->return_union_type) {
            $string .= " * @return {$this->return_union_type}\n";
        }
        foreach ($this->throw_union_type->getTypeSet() as $type) {
            $string .= " * @throws {$type}\n";
        }

        $string .= " */\n";

        return $string;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\FQSEN;

/**
 * This contains info for a single sub-node of a node of type \ast\AST_USE_TRAIT
 * (Which aliases of methods exist for this trait, which `insteadof` exist, etc)
 */
class TraitAdaptations
{
    /**
     * @var FQSEN
     */
    private $trait_fqsen;

    /**
     * @var array<string,TraitAliasSource>
     * maps alias methods from this trait
     * to the info about the source method
     */
    public $alias_methods = [];

    /**
     * @var array<string,bool> Has an entry mapping name to true if a method with a given name is hidden.
     */
    public $hidden_methods = [];

    public function __construct(FQSEN $trait_fqsen)
    {
        $this->trait_fqsen = $trait_fqsen;
    }

    /**
     * Gets the FQSEN
     *
     * @return FQSEN the trait's FQSEN
     */
    public function getTraitFQSEN() : FQSEN
    {
        return $this->trait_fqsen;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\Language\UnionType;
use Phan\Language\Type\GenericArrayType;

class VariadicParameter extends Parameter
{
    // __construct inherited from Parameter

    /**
     * @return static - non-variadic clone which can be modified.
     * @override
     */
    public function cloneAsNonVariadic()
    {
        $result = clone($this);
        if (!$result->isCloneOfVariadic()) {
            $result->convertToNonVariadic();
            $result->enablePhanFlagBits(Flags::IS_CLONE_OF_VARIADIC);
        }
        return $result;
    }

    /**
     * @return void
     */
    private function convertToNonVariadic()
    {
        // Avoid a redundant clone of toGenericArray()
        $this->type = $this->getUnionType();
    }

    /**
     * @return bool - True when this is a non-variadic clone of a variadic parameter.
     * (We avoid bugs by adding new types to a variadic parameter if this is cloned.)
     * However, error messages still need to convert variadic parameters to a string.
     * @override
     */
    public function isCloneOfVariadic() : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_CLONE_OF_VARIADIC);
    }

    /**
     * @return bool
     * True if this parameter is variadic, i.e. can
     * take an unlimited list of parameters and express
     * them as an array.
     * @override
     */
    public function isVariadic() : bool
    {
        return true;
    }

    /**
     * @return bool
     * True if this is an optional parameter (true because this is variadic)
     * @override
     */
    public function isOptional() : bool
    {
        return true;
    }

    /**
     * @return bool
     * True if this is a required parameter (false because this is variadic)
     * @override
     */
    public function isRequired() : bool
    {
        return false;
    }

    /**
     * Returns the Parameter in the form expected by a caller.
     *
     * If this parameter is variadic (e.g. `DateTime ...$args`), then this
     * would return a parameter with the type of the elements (e.g. `DateTime`)
     *
     * If this parameter is not variadic, returns $this.
     *
     * @return static
     * @override
     */
    public function asNonVariadic()
    {
        // TODO: Is it possible to cache this while maintaining
        //       correctness? PostOrderAnalysisVisitor clones the
        //       value to avoid it being reused.
        //
        // Also, figure out if the cloning still working correctly
        // after this PR for fixing variadic args. Create a single
        // Parameter instance for analyzing callers of the
        // corresponding method/function.
        // e.g. $this->getUnionType() is of type T[]
        //      $this->non_variadic->getUnionType() is of type T
        return new Parameter(
            $this->getFileRef(),
            $this->getName(),
            $this->type,
            Flags::bitVectorWithState($this->getFlags(), \ast\flags\PARAM_VARIADIC, false)
        );
    }

    /**
     * If this Parameter is variadic, calling `getUnionType`
     * will return an array type such as `DateTime[]`. This
     * method will return the element type (such as `DateTime`)
     * for variadic parameters.
     */
    public function getNonVariadicUnionType() : UnionType
    {
        return $this->type;
    }

    /**
     * If this parameter is variadic (e.g. `DateTime ...$args`),
     * then this returns the corresponding array type(s) of $args.
     * (e.g. `array<int,DateTime>`)
     *
     * NOTE: For analyzing the code within a function,
     * code should pass $param->cloneAsNonVariadic() instead.
     * Modifying/analyzing the clone should work without any bugs.
     *
     * TODO(Issue #376) : We will probably want to be able to modify
     * the underlying variable, e.g. by creating
     * `class UnionTypeGenericArrayView extends UnionType`.
     * Otherwise, type inference of `...$args` based on the function
     * source will be less effective without phpdoc types.
     *
     * @override
     */
    public function getUnionType() : UnionType
    {
        if (!$this->isCloneOfVariadic()) {
            // TODO: Figure out why asNonEmptyGenericArrayTypes() causes test failures
            return parent::getUnionType()->asGenericArrayTypes(GenericArrayType::KEY_INT);
        }
        return $this->type;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Element;

use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Exception\CodeBaseException;
use Phan\Language\Context;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\Scope\FunctionLikeScope;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\UnionType;
use ast\Node;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class Method extends ClassElement implements FunctionInterface
{
    use \Phan\Analysis\Analyzable;
    use \Phan\Memoize;
    use FunctionTrait;
    use ClosedScopeElement;

    /**
     * @var ?FullyQualifiedMethodName If this was originally defined in a trait, this is the trait's defining fqsen.
     * This is tracked separately from getDefiningFQSEN() in order to not break access checks on protected/private methods.
     * Used for dead code detection.
     */
    private $real_defining_fqsen;

    /**
     * @param Context $context
     * The context in which the structural element lives
     *
     * @param string $name
     * The name of the typed structural element
     *
     * @param UnionType $type
     * A '|' delimited set of types satisfied by this
     * typed structural element.
     *
     * @param int $flags
     * The flags property contains node specific flags. It is
     * always defined, but for most nodes it is always zero.
     * ast\kind_uses_flags() can be used to determine whether
     * a certain kind has a meaningful flags value.
     *
     * @param FullyQualifiedMethodName $fqsen
     * A fully qualified name for the element
     *
     * @param ?array<int,Parameter> $parameter_list
     * A list of parameters to set on this method
     */
    public function __construct(
        Context $context,
        string $name,
        UnionType $type,
        int $flags,
        FullyQualifiedMethodName $fqsen,
        $parameter_list
    ) {
        parent::__construct(
            $context,
            FullyQualifiedMethodName::canonicalName($name),
            $type,
            $flags,
            $fqsen
        );

        // Presume that this is the original definition
        // of this method, and let it be overwritten
        // if it isn't.
        $this->setDefiningFQSEN($fqsen);
        $this->real_defining_fqsen = $fqsen;

        // Record the FQSEN of this method (With the current Clazz),
        // to prevent recursing from a method into itself in non-quick mode.
        $this->setInternalScope(new FunctionLikeScope(
            $context->getScope(),
            $fqsen
        ));

        if ($parameter_list !== null) {
            $this->setParameterList($parameter_list);
        }
    }

    /**
     * @return bool
     * True if this is a magic phpdoc method (declared via (at)method on class declaration phpdoc)
     */
    public function isFromPHPDoc() : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_FROM_PHPDOC);
    }

    /**
     * @param bool $from_phpdoc - True if this is a magic phpdoc method (declared via (at)method on class declaration phpdoc)
     * @return void
     */
    public function setIsFromPHPDoc(bool $from_phpdoc)
    {
        $this->setPhanFlags(
            Flags::bitVectorWithState(
                $this->getPhanFlags(),
                Flags::IS_FROM_PHPDOC,
                $from_phpdoc
            )
        );
    }

    /**
     * @return bool
     * True if this method is intended to be an override of another method (contains (at)override)
     */
    public function isOverrideIntended() : bool
    {
        return $this->getPhanFlagsHasState(Flags::IS_OVERRIDE_INTENDED);
    }

    /**
     * @param bool $is_override_intended - True if this method is intended to be an override of another method (contains (at)override)

     * @return void
     */
    public function setIsOverrideIntended(bool $is_override_intended)
    {
        $this->setPhanFlags(
            Flags::bitVectorWithState(
                $this->getPhanFlags(),
                Flags::IS_OVERRIDE_INTENDED,
                $is_override_intended
            )
        );
    }

    /**
     * @return bool
     * True if this is an abstract method
     */
    public function isAbstract() : bool
    {
        return $this->getFlagsHasState(\ast\flags\MODIFIER_ABSTRACT);
    }

    /**
     * @return bool
     * True if this is a final method
     */
    public function isFinal() : bool
    {
        return $this->getFlagsHasState(\ast\flags\MODIFIER_FINAL);
    }

    /**
     * @return bool
     * True if this method returns a reference
     */
    public function returnsRef() : bool
    {
        return $this->getFlagsHasState(\ast\flags\RETURNS_REF);
    }

    /**
     * @return bool
     * True if this is a magic method
     * (Names are all normalized in FullyQualifiedMethodName::make())
     */
    public function getIsMagic() : bool
    {
        return \array_key_exists($this->getName(), FullyQualifiedMethodName::MAGIC_METHOD_NAME_SET);
    }

    /**
     * @return bool
     * True if this is a magic method which should have return type of void
     * (Names are all normalized in FullyQualifiedMethodName::make())
     */
    public function getIsMagicAndVoid() : bool
    {
        return \array_key_exists($this->getName(), FullyQualifiedMethodName::MAGIC_VOID_METHOD_NAME_SET);
    }

    /**
     * @return bool
     * True if this is the `__construct` method
     * (Does not return true for php4 constructors)
     */
    public function getIsNewConstructor() : bool
    {
        return ($this->getName() === '__construct');
    }

    /**
     * @return bool
     * True if this is the magic `__call` method
     */
    public function getIsMagicCall() : bool
    {
        return ($this->getName() === '__call');
    }

    /**
     * @return bool
     * True if this is the magic `__callStatic` method
     */
    public function getIsMagicCallStatic() : bool
    {
        return ($this->getName() === '__callStatic');
    }

    /**
     * @return Method
     * A default constructor for the given class
     */
    public static function defaultConstructorForClass(
        Clazz $clazz,
        CodeBase $code_base
    ) : Method {
        if ($clazz->getFQSEN()->getNamespace() === '\\' && $clazz->hasMethodWithName($code_base, $clazz->getName())) {
            $old_style_constructor = $clazz->getMethodByName($code_base, $clazz->getName());
        } else {
            $old_style_constructor = null;
        }

        $method_fqsen = FullyQualifiedMethodName::make(
            $clazz->getFQSEN(),
            '__construct'
        );

        $method = new Method(
            $old_style_constructor ? $old_style_constructor->getContext() : $clazz->getContext(),
            '__construct',
            $clazz->getUnionType(),
            0,
            $method_fqsen,
            $old_style_constructor ? $old_style_constructor->getParameterList() : null
        );

        if ($old_style_constructor) {
            $method->setRealParameterList($old_style_constructor->getRealParameterList());
            $method->setNumberOfRequiredParameters($old_style_constructor->getNumberOfRequiredParameters());
            $method->setNumberOfOptionalParameters($old_style_constructor->getNumberOfOptionalParameters());
            $method->setRealReturnType($old_style_constructor->getRealReturnType());
            $method->setUnionType($old_style_constructor->getUnionType());
        }

        return $method;
    }

    /**
     * @param Clazz $clazz - The class to treat as the defining class of the alias. (i.e. the inheriting class)
     * @param string $alias_method_name - The alias method name.
     * @param int $new_visibility_flags (0 if unchanged)
     * @return Method
     *
     * An alias from a trait use, which is treated as though it was defined in $clazz
     * E.g. if you import a trait's method as private/protected, it becomes private/protected **to the class which used the trait**
     *
     * The resulting alias doesn't inherit the \ast\Node of the method body, so aliases won't have a redundant analysis step.
     */
    public function createUseAlias(
        Clazz $clazz,
        string $alias_method_name,
        int $new_visibility_flags
    ) : Method {

        $method_fqsen = FullyQualifiedMethodName::make(
            $clazz->getFQSEN(),
            $alias_method_name
        );

        $method = new Method(
            $this->getContext(),
            $alias_method_name,
            $this->getUnionType(),
            $this->getFlags(),
            $method_fqsen,
            $this->getParameterList()
        );
        switch ($new_visibility_flags) {
            case \ast\flags\MODIFIER_PUBLIC:
            case \ast\flags\MODIFIER_PROTECTED:
            case \ast\flags\MODIFIER_PRIVATE:
                // Replace the visibility with the new visibility.
                $method->setFlags(Flags::bitVectorWithState(
                    Flags::bitVectorWithState(
                        $method->getFlags(),
                        \ast\flags\MODIFIER_PUBLIC | \ast\flags\MODIFIER_PROTECTED | \ast\flags\MODIFIER_PRIVATE,
                        false
                    ),
                    $new_visibility_flags,
                    true
                ));
                break;
            default:
                break;
        }

        $defining_fqsen = $this->getDefiningFQSEN();
        if ($method->isPublic()) {
            $method->setDefiningFQSEN($defining_fqsen);
        }
        $method->real_defining_fqsen = $defining_fqsen;

        $method->setRealParameterList($this->getRealParameterList());
        $method->setRealReturnType($this->getRealReturnType());
        $method->setNumberOfRequiredParameters($this->getNumberOfRequiredParameters());
        $method->setNumberOfOptionalParameters($this->getNumberOfOptionalParameters());

        return $method;
    }

    /**
     * @param Context $context
     * The context in which the node appears
     *
     * @param CodeBase $code_base
     *
     * @param Node $node
     * An AST node representing a method
     *
     * @return Method
     * A Method representing the AST node in the
     * given context
     */
    public static function fromNode(
        Context $context,
        CodeBase $code_base,
        Node $node,
        FullyQualifiedMethodName $fqsen
    ) : Method {

        // @var array<int,Parameter>
        // The list of parameters specified on the
        // method
        $parameter_list =
            Parameter::listFromNode(
                $context,
                $code_base,
                $node->children['params']
            );

        // Create the skeleton method object from what
        // we know so far
        $method = new Method(
            $context,
            (string)$node->children['name'],
            UnionType::empty(),
            $node->flags ?? 0,
            $fqsen,
            $parameter_list
        );

        // Parse the comment above the method to get
        // extra meta information about the method.
        $comment = Comment::fromStringInContext(
            $node->children['docComment'] ?? '',
            $code_base,
            $context,
            $node->lineno ?? 0,
            Comment::ON_METHOD
        );

        // Add each parameter to the scope of the function
        // NOTE: it's important to clone this,
        // because we don't want any assignments to modify the original Parameter
        foreach ($parameter_list as $parameter) {
            $method->getInternalScope()->addVariable(
                $parameter->cloneAsNonVariadic()
            );
        }

        if (!$method->isPHPInternal()) {
            // If the method is Analyzable, set the node so that
            // we can come back to it whenever we like and
            // rescan it
            $method->setNode($node);
        }

        // Keep an copy of the original parameter list, to check for fatal errors later on.
        $method->setRealParameterList($parameter_list);

        $method->setNumberOfRequiredParameters(array_reduce(
            $parameter_list,
            function (int $carry, Parameter $parameter) : int {
                return ($carry + ($parameter->isRequired() ? 1 : 0));
            },
            0
        ));

        $method->setNumberOfOptionalParameters(array_reduce(
            $parameter_list,
            function (int $carry, Parameter $parameter) : int {
                return ($carry + ($parameter->isOptional() ? 1 : 0));
            },
            0
        ));

        // Check to see if the comment specifies that the
        // method is deprecated
        $method->setIsDeprecated($comment->isDeprecated());

        // Set whether or not the element is internal to
        // the namespace.
        $method->setIsNSInternal($comment->isNSInternal());

        // Set whether or not the comment indicates that the method is intended
        // to override another method.
        $method->setIsOverrideIntended($comment->isOverrideIntended());
        $method->setSuppressIssueList($comment->getSuppressIssueList());

        if ($method->getIsMagicCall() || $method->getIsMagicCallStatic()) {
            $method->setNumberOfOptionalParameters(FunctionInterface::INFINITE_PARAMETERS);
            $method->setNumberOfRequiredParameters(0);
        }

        // Add the syntax-level return type to the method's union type
        // if it exists
        $return_union_type = UnionType::empty();
        if ($node->children['returnType'] !== null) {
            $return_union_type = UnionTypeVisitor::unionTypeFromNode(
                $code_base,
                $context,
                $node->children['returnType']
            );
            $method->setUnionType($method->getUnionType()->withUnionType($return_union_type));
        }
        $method->setRealReturnType($return_union_type);

        // If available, add in the doc-block annotated return type
        // for the method.
        if ($comment->hasReturnUnionType()) {
            $comment_return_union_type = $comment->getReturnType();
            if ($comment_return_union_type->hasSelfType()) {
                // We can't actually figure out 'static' at this
                // point, but fill it in regardless. It will be partially
                // correct
                if ($context->isInClassScope()) {
                    // n.b.: We're leaving the reference to self, static
                    //       or $this in the type because I'm guessing
                    //       it doesn't really matter. Apologies if it
                    //       ends up being an issue.
                    $comment_return_union_type = $comment_return_union_type->withType(
                        $context->getClassFQSEN()->asType()
                    );
                    // $comment->setReturnType($comment_return_union_type);
                }
            }

            $method->setUnionType($method->getUnionType()->withUnionType($comment_return_union_type));
            $method->setPHPDocReturnType($comment_return_union_type);
        }

        // Defer adding params to the local scope for user functions. (FunctionTrait::addParamsToScopeOfFunctionOrMethod)
        // See PostOrderAnalysisVisitor->analyzeCallToMethod
        $method->setComment($comment);

        return $method;
    }

    /**
     * @return UnionType
     * The type of this method in its given context.
     */
    public function getUnionType() : UnionType
    {
        $union_type = parent::getUnionType();

        // If the type is 'static', add this context's class
        // to the return type
        if ($union_type->hasStaticType()) {
            $union_type = $union_type->withType(
                $this->getFQSEN()->getFullyQualifiedClassName()->asType()
            );
        }

        // If the type is a generic array of 'static', add
        // a generic array of this context's class to the return type
        if ($union_type->genericArrayElementTypes()->hasStaticType()) {
            // TODO: Base this on the static array type...
            $key_type_enum = GenericArrayType::keyTypeFromUnionTypeKeys($union_type);
            $union_type = $union_type->withType(
                $this->getFQSEN()->getFullyQualifiedClassName()->asType()->asGenericArrayType($key_type_enum)
            );
        }

        return $union_type;
    }

    /**
     * @return FullyQualifiedMethodName
     */
    public function getFQSEN() : FullyQualifiedMethodName
    {
        return $this->fqsen;
    }

    /**
     * @return \Generator
     * The set of all alternates to this method
     */
    public function alternateGenerator(CodeBase $code_base) : \Generator
    {
        $alternate_id = 0;
        $fqsen = $this->getFQSEN();

        while ($code_base->hasMethodWithFQSEN($fqsen)) {
            yield $code_base->getMethodByFQSEN($fqsen);
            $fqsen = $fqsen->withAlternateId(++$alternate_id);
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base with which to look for classes
     *
     * @return Method[]
     * The Methods that this Method is overriding
     * (Abstract methods are returned before concrete methods)
     *
     * @throws CodeBaseException if 0 methods were found.
     */
    public function getOverriddenMethods(
        CodeBase $code_base
    ) : array {
        // Get the class that defines this method
        $class = $this->getClass($code_base);

        // Get the list of ancestors of that class
        $ancestor_class_list = $class->getAncestorClassList(
            $code_base
        );

        $defining_fqsen = $this->getDefiningFQSEN();

        $method_list = [];
        $abstract_method_list = [];
        // Hunt for any ancestor classes that define a method with
        // the same name as this one.
        foreach ($ancestor_class_list as $ancestor_class) {
            // TODO: Handle edge cases in traits.
            // A trait may be earlier in $ancestor_class_list than the parent, but the parent may define abstract classes.
            // TODO: What about trait aliasing rules?
            if ($ancestor_class->hasMethodWithName($code_base, $this->getName())) {
                $method = $ancestor_class->getMethodByName(
                    $code_base,
                    $this->getName()
                );
                if ($method->getDefiningFQSEN() === $defining_fqsen) {
                    // Skip it, this method **is** the one which defined this.
                    continue;
                }
                // We initialize the overridden method's scope to ensure that
                // analyzers are aware of the full param/return types of the overridden method.
                $method->ensureScopeInitialized($code_base);
                if ($method->isAbstract()) {
                    // TODO: check for trait conflicts, etc.
                    $abstract_method_list[] = $method;
                    continue;
                }
                $method_list[] = $method;
            }
        }
        // Return abstract methods before concrete methods, in order to best check method compatibility.
        $method_list = array_merge($abstract_method_list, $method_list);
        if (count($method_list) > 0) {
            return $method_list;
        }

        // Throw an exception if this method doesn't override
        // anything
        throw new CodeBaseException(
            $this->getFQSEN(),
            "Method $this with FQSEN {$this->getFQSEN()} does not override another method"
        );
    }

    /**
     * @return FullyQualifiedMethodName the FQSEN with the original definition (Even if this is private/protected and inherited from a trait). Used for dead code detection.
     *                                  Inheritance tests use getDefiningFQSEN() so that access checks won't break.
     *
     * @suppress PhanPartialTypeMismatchReturn TODO: Allow subclasses to make property types more specific
     */
    public function getRealDefiningFQSEN() : FullyQualifiedMethodName
    {
        return $this->real_defining_fqsen ?? $this->getDefiningFQSEN();
    }

    /**
     * @return string
     * A string representation of this method signature (preferring phpdoc types)
     */
    public function __toString() : string
    {
        $string = '';
        // TODO: should this representation and other representations include visibility?

        $string .= 'function ';
        if ($this->returnsRef()) {
            $string .= '&';
        }
        $string .= $this->getName();

        $string .= '(' . \implode(', ', $this->getParameterList()) . ')';

        if (!$this->getUnionType()->isEmpty()) {
            $string .= ' : ' . (string)$this->getUnionType();
        }

        return $string;
    }

    /**
     * @return string
     * A string representation of this method signature
     * (Based on real types only, instead of phpdoc+real types)
     */
    public function toRealSignatureString() : string
    {
        $string = '';

        $string .= 'function ';
        if ($this->returnsRef()) {
            $string .= '&';
        }
        $string .= $this->getName();

        $string .= '(' . \implode(', ', $this->getRealParameterList()) . ')';

        if (!$this->getRealReturnType()->isEmpty()) {
            $string .= ' : ' . (string)$this->getRealReturnType();
        }

        return $string;
    }

    public function toStub(bool $class_is_interface = false) : string
    {
        $string = '    ';
        // It's an error to have visibility or abstract in an interface's stub (e.g. JsonSerializable)
        if (!$class_is_interface) {
            if ($this->isPrivate()) {
                $string .= 'private ';
            } elseif ($this->isProtected()) {
                $string .= 'protected ';
            } else {
                $string .= 'public ';
            }

            if ($this->isAbstract()) {
                $string .= 'abstract ';
            }
        }

        if ($this->isStatic()) {
            $string .= 'static ';
        }

        $string .= 'function ';
        if ($this->returnsRef()) {
            $string .= '&';
        }
        $string .= $this->getName();

        $string .= '(' . implode(', ', array_map(function (Parameter $parameter) : string {
            return $parameter->toStubString();
        }, $this->getRealParameterList())) . ')';

        if (!$this->getRealReturnType()->isEmpty()) {
            $string .= ' : ' . (string)$this->getRealReturnType();
        }
        if ($this->isAbstract()) {
            $string .= ';';
        } else {
            $string .= ' {}';
        }

        return $string;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language;

use Phan\CodeBase;
use Phan\Exception\CodeBaseException;
use Phan\Issue;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Property;
use Phan\Language\Element\TypedElement;
use Phan\Language\Element\Variable;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedGlobalConstantName;
use Phan\Language\FQSEN\FullyQualifiedGlobalStructuralElement;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\FQSEN\FullyQualifiedPropertyName;
use Phan\Language\Scope\GlobalScope;

/**
 * An object representing the context in which any
 * structural element (such as a class or method) lives.
 */
class Context extends FileRef
{
    /**
     * @var string
     * The namespace of the file
     */
    private $namespace = '';

    /**
     * @var int
     * The id of the namespace
     */
    private $namespace_id = 0;

    /**
     * @var array<int,array<string,NamespaceMapEntry>>
     * Maps [int flags => [string name/namespace => NamespaceMapEntry(fqsen, is_used)]]
     * Note that for \ast\USE_CONST (global constants), this is case sensitive,
     * but the remaining types are case insensitive (stored with lowercase name).
     */
    private $namespace_map = [];

    /**
     * @var array<int,array<string,NamespaceMapEntry>>
     * Maps [int flags => [string name/namespace => NamespaceMapEntry(fqsen, is_used)]]
     *
     * (This is used in the analysis phase after the parse phase)
     * @see $this->namespace_map
     */
    private $parse_namespace_map = [];

    /**
     * @var int
     * strict_types setting for the file
     */
    protected $strict_types = 0;

    /**
     * @var Scope
     * The current scope in this context
     */
    private $scope;

    /**
     * @var array<mixed,mixed>
     * caches union types for a given node
     */
    private $cache = [];

    /**
     * Create a new context
     */
    public function __construct()
    {
        $this->scope = new GlobalScope;
    }

    /*
     * @param string $namespace
     * The namespace of the file
     *
     * @return Context
     * A clone of this context with the given value is returned
     */
    public function withNamespace(string $namespace) : Context
    {
        $context = clone($this);
        $context->namespace = $namespace;
        $context->namespace_id += 1;  // Assumes namespaces are walked in order
        $context->namespace_map = [];
        return $context;
    }

    /**
     * @return string
     * The namespace of the file
     */
    public function getNamespace() : string
    {
        return $this->namespace;
    }

    /**
     * @return int
     * The namespace id within the file (incrementing starting from 0)
     * Used because a file can have duplicate identical namespace declarations.
     */
    public function getNamespaceId() : int
    {
        return $this->namespace_id;
    }

    /**
     * @return bool
     * True if we have a mapped NS for the given named element
     */
    public function hasNamespaceMapFor(int $flags, string $name) : bool
    {
        // Look for the mapping on the part before a
        // slash
        $name_parts = \explode('\\', $name, 2);
        if (\count($name_parts) > 1) {
            // We're looking for a namespace if there's more than one part
            // Namespaces are case insensitive.
            $namespace_map_key = \strtolower($name_parts[0]);
            $flags = \ast\flags\USE_NORMAL;
        } else {
            if ($flags !== \ast\flags\USE_CONST) {
                $namespace_map_key = \strtolower($name);
            } else {
                // Constants are case sensitive, and stored in a case sensitive manner.
                $namespace_map_key = $name;
            }
        }
        return isset($this->namespace_map[$flags][$namespace_map_key]);
    }

    /**
     * @return FullyQualifiedGlobalStructuralElement
     * The namespace mapped name for the given flags and name
     */
    public function getNamespaceMapFor(
        int $flags,
        string $name
    ) : FullyQualifiedGlobalStructuralElement {

        // Look for the mapping on the part before a
        // slash
        $name_parts = \explode('\\', $name, 2);
        if (\count($name_parts) > 1) {
            $name = \strtolower($name_parts[0]);
            $suffix = $name_parts[1];
            // In php, namespaces, functions, and classes are case insensitive.
            // However, constants are almost always case insensitive.
            if ($flags !== \ast\flags\USE_CONST) {
                $suffix = \strtolower($suffix);
            }
            // The name we're looking for is a namespace(USE_NORMAL).
            // The suffix has type $flags
            $map_flags = \ast\flags\USE_NORMAL;
        } else {
            $suffix = '';
            $map_flags = $flags;
            if ($flags !== \ast\flags\USE_CONST) {
                $name = \strtolower($name);
            }
        }

        $namespace_map_entry = $this->namespace_map[$map_flags][$name] ?? null;

        \assert(
            !empty($namespace_map_entry),
            "No namespace defined for name"
        );
        $fqsen = $namespace_map_entry->fqsen;
        $namespace_map_entry->is_used = true;

        \assert(
            $fqsen instanceof FullyQualifiedGlobalStructuralElement,
            "Namespace map was not a FullyQualifiedGlobalStructuralElement"
        );

        if (!$suffix) {
            return $fqsen;
        }

        // Create something of the corresponding type (which may or may not be within a suffix)
        switch ($flags) {
            case \ast\flags\USE_NORMAL:
                return FullyQualifiedClassName::fromFullyQualifiedString(
                    (string)$fqsen . '\\' . $suffix
                );
            case \ast\flags\USE_FUNCTION:
                return FullyQualifiedFunctionName::fromFullyQualifiedString(
                    (string)$fqsen . '\\' . $suffix
                );
            case \ast\flags\USE_CONST:
                return FullyQualifiedGlobalConstantName::fromFullyQualifiedString(
                    (string)$fqsen . '\\' . $suffix
                );
        }

        throw new \AssertionError("Unknown flag $flags");
    }

    /**
     * @return Context
     * This context with the given value is returned
     */
    public function withNamespaceMap(
        int $flags,
        string $alias,
        FullyQualifiedGlobalStructuralElement $target,
        int $lineno
    ) : Context {
        $original_alias = $alias;
        if ($flags !== \ast\flags\USE_CONST) {
            $alias = \strtolower($alias);
        } else {
            $last_part_index = \strrpos($alias, '\\');
            if ($last_part_index !== false) {
                // Convert the namespace to lowercase, but not the constant name.
                $alias = \strtolower(\substr($alias, 0, $last_part_index + 1)) . substr($alias, $last_part_index + 1);
            }
        }
        // we may have imported this namespace map from the parse phase, making the target already exist
        // TODO: Warn if namespace_map already exists? Then again, `php -l` already does.
        $parse_entry = $this->parse_namespace_map[$flags][$alias] ?? null;
        if ($parse_entry !== null) {
            // We add entries to namespace_map only after encounting them
            // This is because statements can appear before 'use Foo\Bar;' (and those don't use the 'use' statement.)
            $this->namespace_map[$flags][$alias] = $parse_entry;
            return $this;
        }
        $this->namespace_map[$flags][$alias] = new NamespaceMapEntry($target, $original_alias, $lineno);
        return $this;
    }

    /**
     * @param int $strict_types
     * The strict_type setting for the file
     *
     * @return Context
     * This context with the given value is returned
     */
    public function withStrictTypes(int $strict_types) : Context
    {
        $this->strict_types = $strict_types;
        return $this;
    }

    /**
     * @return bool
     * True if strict_types is set to 1 in this
     * context.
     */
    public function getIsStrictTypes() : bool
    {
        return (1 === $this->strict_types);
    }

    /**
     * @return Scope
     * An object describing the contents of the current
     * scope.
     */
    public function getScope() : Scope
    {
        return $this->scope;
    }

    /**
     * Set the scope on the context
     *
     * @return void
     */
    public function setScope(Scope $scope)
    {
        $this->scope = $scope;
        // TODO: Less aggressive? ConditionVisitor creates a lot of scopes
        $this->cache = [];
    }

    /**
     * @return Context
     * A new context with the given scope
     */
    public function withScope(Scope $scope) : Context
    {
        $context = clone($this);
        $context->setScope($scope);
        return $context;
    }

    /**
     * @param Variable $variable
     * A variable to add to the scope for the new
     * context
     *
     * @return Context
     * A new context based on this with a variable
     * as defined by the parameters in scope
     */
    public function withScopeVariable(
        Variable $variable
    ) : Context {
        return $this->withScope(
            $this->getScope()->withVariable($variable)
        );
    }

    /**
     * @param Variable $variable
     * A variable to add to the scope for the new
     * context
     *
     * @return void
     */
    public function addGlobalScopeVariable(Variable $variable)
    {
        $this->getScope()->addGlobalVariable($variable);
    }

    /**
     * Add a variable to this context's scope. Note that
     * this does not create a new context. You're actually
     * injecting the variable into the context. Use with
     * caution.
     *
     * @param Variable $variable
     * A variable to inject into this context
     *
     * @return void
     */
    public function addScopeVariable(
        Variable $variable
    ) {
        $this->getScope()->addVariable($variable);
    }

    /**
     * Unset a variable in this context's scope. Note that
     * this does not create a new context. You're actually
     * removing the variable from the context. Use with
     * caution.
     *
     * @param string $variable_name
     * The name of a variable to remove from the context.
     *
     * @return void
     */
    public function unsetScopeVariable(
        string $variable_name
    ) {
        $this->getScope()->unsetVariable($variable_name);
    }

    /**
     * @return bool
     * True if this context is currently within a class
     * scope, else false.
     */
    public function isInClassScope() : bool
    {
        return $this->getScope()->isInClassScope();
    }

    /**
     * @return FullyQualifiedClassName
     * A fully-qualified structural element name describing
     * the current class in scope.
     */
    public function getClassFQSEN() : FullyQualifiedClassName
    {
        return $this->getScope()->getClassFQSEN();
    }

    /**
     * @return bool
     * True if this context is currently within a property
     * scope, else false.
     */
    public function isInPropertyScope() : bool
    {
        return $this->getScope()->isInPropertyScope();
    }

    /**
     * @return FullyQualifiedPropertyName
     * A fully-qualified structural element name describing
     * the current property in scope.
     */
    public function getPropertyFQSEN() : FullyQualifiedPropertyName
    {
        return $this->getScope()->getPropertyFQSEN();
    }

    /**
     * @param CodeBase $code_base
     * The global code base holding all state
     *
     * @return Clazz
     * Get the class in this scope, or fail real hard
     *
     * @throws CodeBaseException
     * Thrown if we can't find the class in scope within the
     * given codebase.
     */
    public function getClassInScope(CodeBase $code_base) : Clazz
    {
        \assert(
            $this->isInClassScope(),
            "Must be in class scope to get class"
        );

        if (!$code_base->hasClassWithFQSEN($this->getClassFQSEN())) {
            throw new CodeBaseException(
                $this->getClassFQSEN(),
                "Cannot find class with FQSEN {$this->getClassFQSEN()} in context {$this}"
            );
        }

        return $code_base->getClassByFQSEN(
            $this->getClassFQSEN()
        );
    }

    /**
     * @param CodeBase $code_base
     * The global code base holding all state
     *
     * @return Property
     * Get the property in this scope, or fail real hard
     *
     * @throws CodeBaseException
     * Thrown if we can't find the property in scope within the
     * given codebase.
     */
    public function getPropertyInScope(CodeBase $code_base) : Property
    {
        \assert(
            $this->isInPropertyScope(),
            "Must be in property scope to get property"
        );

        $property_fqsen = $this->getPropertyFQSEN();
        if (!$code_base->hasPropertyWithFQSEN($property_fqsen)) {
            throw new CodeBaseException(
                $property_fqsen,
                "Cannot find class with FQSEN {$property_fqsen} in context {$this}"
            );
        }

        return $code_base->getPropertyByFQSEN(
            $property_fqsen
        );
    }

    /**
     * @return bool
     * True if this context is currently within a method,
     * function or closure scope.
     */
    public function isInFunctionLikeScope() : bool
    {
        return $this->getScope()->isInFunctionLikeScope();
    }

    /**
     * @return bool
     * True if this context is currently within a method.
     */
    public function isInMethodScope() : bool
    {
        return (
            $this->isInClassScope()
            && $this->isInFunctionLikeScope()
        );
    }

    /*
     * @return FullyQualifiedFunctionLikeName|FullyQualifiedMethodName|FullyQualifiedFunctionName
     * A fully-qualified structural element name describing
     * the current function or method in scope.
     */
    public function getFunctionLikeFQSEN()
    {
        \assert($this->getScope()->isInFunctionLikeScope());
        return $this->getScope()->getFunctionLikeFQSEN();
    }

    /**
     * @param CodeBase $code_base
     * The global code base holding all state
     *
     * @return Element\Func|Element\Method
     * Get the method in this scope or fail real hard
     */
    public function getFunctionLikeInScope(
        CodeBase $code_base
    ) : FunctionInterface {
        \assert(
            $this->isInFunctionLikeScope(),
            "Must be in method scope to get method."
        );

        $fqsen = $this->getFunctionLikeFQSEN();

        if ($fqsen instanceof FullyQualifiedFunctionName) {
            if (!$code_base->hasFunctionWithFQSEN($fqsen)) {
                throw new \RuntimeException("The function $fqsen does not exist, but Phan is in that function's scope");
            }
            return $code_base->getFunctionByFQSEN($fqsen);
        }

        if ($fqsen instanceof FullyQualifiedMethodName) {
            \assert(
                $code_base->hasMethodWithFQSEN($fqsen),
                "Method does not exist"
            );
            return $code_base->getMethodByFQSEN($fqsen);
        }

        throw new \AssertionError("FQSEN must be for a function or method");
    }

    /**
     * @return bool
     * True if we're within the scope of a class, method,
     * function or closure. False if we're in the global
     * scope
     */
    public function isInElementScope() : bool
    {
        return (
            $this->isInFunctionLikeScope()
            || $this->isInClassScope()  // isInPropertyScope implies isInClassScope
        );
    }

    /**
     * @return bool
     * True if we're in the global scope (not in a class,
     * method, function, closure).
     */
    public function isInGlobalScope() : bool
    {
        return !$this->isInElementScope();
    }

    /**
     * @param CodeBase $code_base
     * The code base from which to retrieve the TypedElement
     *
     * @return TypedElement
     * The element who's scope we're in. If we're in the global
     * scope this method will go down in flames and take your
     * process with it.
     */
    public function getElementInScope(CodeBase $code_base) : TypedElement
    {
        \assert(
            $this->isInElementScope(),
            "Cannot get element in scope if we're in the global scope"
        );

        if ($this->isInFunctionLikeScope()) {
            return $this->getFunctionLikeInScope($code_base);
        } elseif ($this->isInPropertyScope()) {
            return $this->getPropertyInScope($code_base);
        } elseif ($this->isInClassScope()) {
            return $this->getClassInScope($code_base);
        }

        throw new CodeBaseException(
            null,
            "Cannot get element in scope if we're in the global scope"
        );
    }

    /**
     * @param CodeBase $code_base
     * The code base from which to retrieve a possible TypedElement
     * that contains an issue suppression list
     *
     * @return bool
     * True if issues with the given name are suppressed within
     * this context.
     */
    public function hasSuppressIssue(
        CodeBase $code_base,
        string $issue_name
    ) : bool {
        if ($code_base->hasFileLevelSuppression($this->getFile(), $issue_name)) {
            return true;
        }
        if (!$this->isInElementScope()) {
            return false;
        }

        $has_suppress_issue =
            $this->getElementInScope($code_base)->hasSuppressIssue(
                $issue_name
            );

        // Increment the suppression use count
        if ($has_suppress_issue) {
            $this->getElementInScope($code_base)->incrementSuppressIssueCount($issue_name);
        }

        return $has_suppress_issue;
    }

    /**
     * $this->cache is reused for multiple types of caches
     * We xor the node ids with the following bits so that the values don't overlap.
     * (The node id is based on \spl_object_id(), which is the object ID number.
     *
     * (This caching scheme makes a reasonable assumption
     * that there are less than 1 billion Node objects on 32-bit systems,
     * (It'd run out of memory with more than 4 bytes needed per Node)
     * and less than (1 << 62) objects on 64-bit systems.)
     *
     * It also assumes that nodes won't be freed while this Context still exists
     *
     * 0x00(node_id) is used for getUnionTypeOfNodeIfCached(int $node_id, false)
     * 0x10(node_id) is used for getUnionTypeOfNodeIfCached(int $node_id, true)
     * 0x01(node_id) is used for getCachedClassListOfNode(int $node_id)
     */
    const HIGH_BIT_1 = (1 << (PHP_INT_SIZE * 8) - 1);
    const HIGH_BIT_2 = (1 << (PHP_INT_SIZE * 8) - 2);

    /**
     * @param int $node_id \spl_object_id($node)
     * @param bool $should_catch_issue_exception the value passed to UnionTypeVisitor
     * @return ?UnionType
     */
    public function getUnionTypeOfNodeIfCached(int $node_id, bool $should_catch_issue_exception)
    {
        if ($should_catch_issue_exception) {
            return $this->cache[$node_id] ?? null;
        }
        return $this->cache[$node_id ^ self::HIGH_BIT_1] ?? null;
    }

    /**
     * TODO: This may be unsafe? Clear the cache after a function goes out of scope.
     *
     * A UnionType is only cached if there is no exception.
     *
     * @param int $node_id \spl_object_id($node)
     * @param UnionType $type the type to cache.
     * @param bool $should_catch_issue_exception the value passed to UnionTypeVisitor
     * @return void
     */
    public function setCachedUnionTypeOfNode(int $node_id, UnionType $type, bool $should_catch_issue_exception)
    {
        if (!$should_catch_issue_exception) {
            $this->cache[$node_id ^ self::HIGH_BIT_1] = $type;
            // If we weren't suppressing exceptions and setCachedUnionTypeOfNode was called,
            // that would mean that there were no exceptions to catch.
            // So, that means the UnionType for should_catch_issue_exception = true will be the same
        }
        $this->cache[$node_id] = $type;
    }

    /**
     * @param int $node_id
     * @return ?array{0:UnionType,1:Clazz[]} $result
     * @suppress PhanPartialTypeMismatchReturn cache is mixed with other cache objects
     */
    public function getCachedClassListOfNode(int $node_id)
    {
        return $this->cache[$node_id ^ self::HIGH_BIT_2] ?? null;
    }

    /**
     * TODO: This may be unsafe? Clear the cache after a function goes out of scope.
     * @param int $node_id \spl_object_id($node)
     * @param array{0:UnionType,1:Clazz[]} $result
     * @return void
     */
    public function setCachedClassListOfNode(int $node_id, array $result)
    {
        $this->cache[$node_id ^ self::HIGH_BIT_2] = $result;
    }

    /**
     * @return void
     */
    public function clearCachedUnionTypes()
    {
        $this->cache = [];
    }

    /**
     * Gets Phan's internal representation of all of the 'use elem;' statements in a namespace.
     * Use hasNamespaceMapFor and getNamespaceMapFor instead.
     *
     * @internal
     *
     * @return array<int,array<string,NamespaceMapEntry>>
     */
    public function getNamespaceMap() : array
    {
        return $this->namespace_map;
    }

    /**
     * Warn about any unused \ast\AST_USE or \ast\AST_GROUP_USE nodes (`use Foo\Bar;`)
     * This should be called after analyzing the end of a namespace (And before analyzing the next namespace)
     *
     * @param CodeBase $code_base
     * The code base within which we're operating
     *
     * @internal
     *
     * @return void
     */
    public function warnAboutUnusedUseElements(CodeBase $code_base)
    {
        foreach ($this->namespace_map as $flags => $entries_for_flag) {
            foreach ($entries_for_flag as $namespace_map_entry) {
                if ($namespace_map_entry->is_used) {
                    continue;
                }
                switch ($flags) {
                    case \ast\flags\USE_NORMAL:
                    default:
                        $issue_type = Issue::UnreferencedUseNormal;
                        break;
                    case \ast\flags\USE_FUNCTION:
                        $issue_type = Issue::UnreferencedUseFunction;
                        break;
                    case \ast\flags\USE_CONST:
                        $issue_type = Issue::UnreferencedUseConstant;
                        break;
                }
                Issue::maybeEmit(
                    $code_base,
                    $this,
                    $issue_type,
                    $namespace_map_entry->lineno,
                    $namespace_map_entry->original_name,
                    (string)$namespace_map_entry->fqsen
                );
            }
        }
    }

    /**
     * @internal
     * @suppress PhanAccessMethodInternal
     */
    public function importNamespaceMapFromParsePhase(CodeBase $code_base)
    {
        $this->parse_namespace_map = $code_base->getNamespaceMapFromParsePhase($this->getFile(), $this->namespace, $this->namespace_id);
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Scope;

use Phan\Language\Scope;

class ClosedScope extends Scope
{
}
<?php declare(strict_types=1);
namespace Phan\Language\Scope;

use Phan\Language\FQSEN\FullyQualifiedClassName;

// TODO: Wrap this with a ClosureLikeScope
class ClosureScope extends FunctionLikeScope
{
    /**
     * @var FullyQualifiedClassName|null
     */
    private $override_class_fqsen = null;

    /**
     * @return void
     */
    public function overrideClassFQSEN(FullyQualifiedClassName $fqsen = null)
    {
        $this->override_class_fqsen = $fqsen;
    }

    /**
     * @return FullyQualifiedClassName|null
     */
    public function getOverrideClassFQSEN()
    {
        return $this->override_class_fqsen;
    }

    /**
     * @return bool
     * True if we're in a class scope
     */
    public function isInClassScope() : bool
    {
        if ($this->override_class_fqsen !== null) {
            return true;
        }
        return parent::isInClassScope();
    }

    /**
     * @return FullyQualifiedClassName
     * Crawl the scope hierarchy to get a class FQSEN.
     */
    public function getClassFQSEN() : FullyQualifiedClassName
    {
        if ($this->override_class_fqsen) {
            return $this->override_class_fqsen;
        }
        return parent::getClassFQSEN();
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Scope;

use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;

// TODO: Wrap this with a ClosureLikeScope
class FunctionLikeScope extends ClosedScope
{
    /**
     * @return bool
     * True if we're in a class scope
     */
    public function isInClassScope() : bool
    {
        return $this->parent_scope->isInClassScope();
    }

    /**
     * @return FullyQualifiedClassName
     * Crawl the scope hierarchy to get a class FQSEN.
     */
    public function getClassFQSEN() : FullyQualifiedClassName
    {
        return $this->parent_scope->getClassFQSEN();
    }

    /**
     * @return bool
     * True if we're in a function scope
     */
    public function isInFunctionLikeScope() : bool
    {
        return true;
    }

    /**
     * @return bool
     * True if we're in a function scope
     */
    public function isInPropertyScope() : bool
    {
        return false;
    }

    /**
     * @return FullyQualifiedMethodName|FullyQualifiedFunctionName
     * Get the FQSEN for the closure, method or function we're in
     */
    public function getFunctionLikeFQSEN()
    {
        $fqsen = $this->getFQSEN();

        if ($fqsen instanceof FullyQualifiedMethodName) {
            return $fqsen;
        }

        if ($fqsen instanceof FullyQualifiedFunctionName) {
            return $fqsen;
        }

        throw new \AssertionError("FQSEN must be a function-like FQSEN");
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Scope;

use Phan\Language\FQSEN\FullyQualifiedClassName;

class ClassScope extends ClosedScope
{

    /**
     * @return bool
     * True if we're in a class scope
     * @override
     */
    public function isInClassScope() : bool
    {
        return true;
    }

    /**
     * @return bool
     * True if we're in a class scope
     * @override
     */
    public function isInPropertyScope() : bool
    {
        return false;
    }

    /**
     * @return FullyQualifiedClassName
     * Get the FullyQualifiedClassName of the class who's scope
     * we're in
     */
    public function getClassFQSEN() : FullyQualifiedClassName
    {
        $fqsen = $this->getFQSEN();

        if ($fqsen instanceof FullyQualifiedClassName) {
            return $fqsen;
        }

        throw new \AssertionError("FQSEN must be a FullyQualifiedClassName");
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Scope;

use Phan\Language\Element\Variable;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\Scope;

class BranchScope extends Scope
{

    /**
     * @return bool
     * True if a variable with the given name is defined
     * within this scope
     *
     * TODO: Allow unsetting a variable within a scope, and properly merge that
     */
    public function hasVariableWithName(string $name) : bool
    {
        return (
            \array_key_exists($name, $this->variable_map)
            || $this->parent_scope->hasVariableWithName($name)
        );
    }

    /**
     * @return Variable
     */
    public function getVariableByName(string $name) : Variable
    {
        return (
            $this->variable_map[$name]
            ?? $this->parent_scope->getVariableByName($name)
        );
    }

    /**
     * @return array<string,Variable>
     * A map from name to Variable in this scope
     */
    public function getVariableMap() : array
    {
        return $this->variable_map + $this->parent_scope->getVariableMap();
    }

    /**
     * @return bool
     * True if we're in a class scope
     */
    public function isInClassScope() : bool
    {
        return $this->parent_scope->isInClassScope();
    }

    /**
     * @return FullyQualifiedClassName
     * Crawl the scope hierarchy to get a class FQSEN.
     */
    public function getClassFQSEN() : FullyQualifiedClassName
    {
        return $this->parent_scope->getClassFQSEN();
    }

    /**
     * @return FullyQualifiedMethodName|FullyQualifiedFunctionName
     * Get the FQSEN for the closure, method or function we're in
     */
    public function getFunctionLikeFQSEN()
    {
        return $this->parent_scope->getFunctionLikeFQSEN();
    }

    /**
     * @return bool
     * True if we're in a class scope
     */
    public function isInFunctionLikeScope() : bool
    {
        return $this->parent_scope->isInFunctionLikeScope();
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Scope;

use Phan\Language\FQSEN\FullyQualifiedPropertyName;

class PropertyScope extends ClosedScope
{

    /**
     * @return bool
     * True if we're in a property scope
     * @override
     */
    public function isInPropertyScope() : bool
    {
        return true;
    }

    /**
     * @return bool
     * True if we're in a class scope (True for properties)
     * @override
     */
    public function isInClassScope() : bool
    {
        return true;
    }

    /**
     * @return FullyQualifiedPropertyName
     * Get the FullyQualifiedPropertyName of the class who's scope
     * we're in.
     * @override
     */
    public function getPropertyFQSEN() : FullyQualifiedPropertyName
    {
        $fqsen = $this->getFQSEN();

        if ($fqsen instanceof FullyQualifiedPropertyName) {
            return $fqsen;
        }

        throw new \AssertionError("FQSEN must be a FullyQualifiedPropertyName");
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Scope;

use Phan\Language\Element\Variable;
use Phan\Language\Scope;

class GlobalScope extends Scope
{

    /**
     * @var array<string,Variable>
     * A map from name to variables for all
     * variables registered under $GLOBALS.
     */
    private static $global_variable_map = [];

    /**
     * @return bool
     * True if we're in a class scope
     */
    public function isInClassScope() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if we're in a method/function/closure scope
     */
    public function isInFunctionLikeScope() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if a variable with the given name is defined
     * within this scope
     */
    public function hasVariableWithName(string $name) : bool
    {
        return (!empty(self::$global_variable_map[$name]));
    }

    /**
     * @return Variable
     */
    public function getVariableByName(string $name) : Variable
    {
        return self::$global_variable_map[$name];
    }

    /**
     * @return array<string,Variable>
     * A map from name to Variable in this scope
     */
    public function getVariableMap() : array
    {
        return self::$global_variable_map;
    }

    /**
     * @param Variable $variable
     * A variable to add to the local scope
     *
     * @return Scope
     */
    public function withVariable(Variable $variable) : Scope
    {
        $this->addVariable($variable);
        return $this;
    }

    /**
     * @return void
     */
    public function addVariable(Variable $variable)
    {
        $variable_name = $variable->getName();
        if (Variable::isHardcodedGlobalVariableWithName($variable_name)) {
            // Silently ignore globally replacing $_POST, $argv, runkit superglobals, etc.
            // with superglobals.
            // TODO: Add a warning for incompatible assignments in callers.
            return;
        }
        self::$global_variable_map[$variable->getName()] = $variable;
    }

    /**
     * @param Variable $variable
     * A variable to add to the set of global variables
     *
     * @return void
     */
    public function addGlobalVariable(Variable $variable)
    {
        $this->addVariable($variable);
    }

    /**
     * @return bool
     * True if a global variable with the given name exists
     */
    public function hasGlobalVariableWithName(string $name) : bool
    {
        return $this->hasVariableWithName($name);
    }

    /**
     * @return Variable
     * The global variable with the given name
     */
    public function getGlobalVariableByName(string $name) : Variable
    {
        return $this->getVariableByName($name);
    }
}
<?php declare(strict_types=1);

namespace Phan\Language;

final class UnionTypeBuilder
{
    /** @var array<int,Type> */
    private $type_set;

    /** @param array<int,Type> $type_set (must be unique) */
    public function __construct(array $type_set = [])
    {
        $this->type_set = $type_set;
    }

    /**
     * @return void
     */
    public function addType(Type $type)
    {
        if (\in_array($type, $this->type_set, true)) {
            return;
        }
        $this->type_set[] = $type;
    }

    /**
     * @return void
     */
    public function addUnionType(UnionType $union_type)
    {
        $old_type_set = $this->type_set;
        foreach ($union_type->getTypeSet() as $type) {
            if (!\in_array($type, $old_type_set, true)) {
                $this->type_set[] = $type;
            }
        }
    }

    /**
     * @return void
     */
    public function removeType(Type $type)
    {
        $i = \array_search($type, $this->type_set, true);
        if ($i !== false) {
            // equivalent to unset($new_type_set[$i]) but fills in the gap in array keys.
            // TODO: How do other versions affect performance on large projects?
            $replacement_type = \array_pop($this->type_set);
            if ($replacement_type !== $type) {
                $this->type_set[$i] = $replacement_type;
            }
        }
    }

    public function isEmpty() : bool
    {
        return \count($this->type_set) === 0;
    }

    /**
     * @return array<int,Type>
     * @suppress PhanPartialTypeMismatchReturn additional types were inferred
     */
    public function getTypeSet() : array
    {
        return $this->type_set;
    }

    /**
     * @suppress PhanPartialTypeMismatchArgument
     * */
    public function getUnionType() : UnionType
    {
        return UnionType::of($this->type_set);
    }
}
<?php

<<<PHAN
@phan-file-suppress PhanPluginMixedKeyNoKey (read by Phan when analyzing this file)
PHAN;

/**
 * This contains the information needed to convert the function signatures for php 7.1 to php 7.0 (and vice versa)
 *
 * This has two sections.
 * The 'new' section contains function/method names from FunctionSignatureMap (And alternates, if applicable) that do not exist in php7.0 or have different signatures in php 7.1.
 *   If they were just updated, the function/method will be present in the 'added' signatures.
 * The 'old' signatures contains the signatures that are different in php 7.0.
 *   Functions are expected to be removed only in major releases of php. (e.g. php 7.0 removed various functions that were deprecated in 5.6)
 *
 * @see FunctionSignatureMap.php
 */
return [
'new' => [
    'Closure::fromCallable' => ['Closure', 'callable'=>'callable'],
    'curl_multi_errno' => ['int', 'mh'=>'resource'],
    'curl_share_errno' => ['int', 'sh'=>'resource'],
    'curl_share_strerror' => ['string', 'code'=>'int'],
    'getopt' => ['array<string,string>|array<string,false>|array<string,array<int,string|false>>', 'options'=>'string', 'longopts='=>'array', '&w_optind='=>'int'],
    'hash_hkdf' => ['string', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
    'is_iterable' => ['bool', 'var'=>'mixed'],
    'openssl_get_curve_names' => ['array<int,string>'],
    'pcntl_async_signals' => ['bool', 'on='=>'bool'],
    'pcntl_signal_get_handler' => ['int|string', 'signo'=>'int'],
    'pg_fetch_all' => ['array', 'result'=>'resource', 'result_type='=>'int'],
    'pg_last_error' => ['string', 'connection='=>'resource', 'operation='=>'int'],
    'pg_select' => ['mixed', 'db'=>'resource', 'table'=>'string', 'ids'=>'array', 'options='=>'int', 'result_type='=>'int'],
    'sapi_windows_cp_conv' => ['string', 'in_codepage'=>'int|string', 'out_codepage'=>'int|string', 'subject'=>'string'],
    'sapi_windows_cp_get' => ['int'],
    'sapi_windows_cp_is_utf8' => ['bool'],
    'sapi_windows_cp_set' => ['bool', 'code_page'=>'int'],
    'session_create_id' => ['string', 'prefix'=>'string'],
    'session_gc' => ['int'],
    'unpack' => ['array', 'format'=>'string', 'data'=>'string', 'offset='=>'int'],
],
'old' => [
    'getopt' => ['array<string,string>|array<string,false>|array<string,array<int,string|false>>', 'options'=>'string', 'longopts='=>'array'],
    'pg_fetch_all' => ['array', 'result'=>'resource'],
    'pg_last_error' => ['string', 'connection='=>'resource'],
    'pg_select' => ['mixed', 'db'=>'resource', 'table'=>'string', 'ids'=>'array', 'options='=>'int'],
    'unpack' => ['array', 'format'=>'string', 'data'=>'string'],
],
];
<?php // @codingStandardsIgnoreFile

<<<PHAN
@phan-file-suppress PhanPluginMixedKeyNoKey (read by Phan when analyzing this file)
PHAN;

/**
 * This contains the information needed to convert the function signatures for php 7.2 to php 7.1 (and vice versa)
 *
 * This has two sections.
 * The 'new' section contains function/method names from FunctionSignatureMap (And alternates, if applicable) that do not exist in php7.1 or have different signatures in php 7.2.
 *   If they were just updated, the function/method will be present in the 'added' signatures.
 * The 'old' signatures contains the signatures that are different in php 7.1.
 *   Functions are expected to be removed only in major releases of php. (e.g. php 7.0 removed various functions that were deprecated in 5.6)
 *
 * @see FunctionSignatureMap.php
 */
return [
'new' => [
    'DOMNodeList::count' => ['int'],
    'ftp_append' => ['bool', 'ftp'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'int'],
    'hash_copy' => ['HashContext', 'context'=>'HashContext'],
    'hash_final' => ['string', 'context'=>'HashContext', 'raw_output='=>'bool'],
    'hash_hmac_algos' => ['array<int,string>'],
    'hash_init' => ['HashContext', 'algo'=>'string', 'options='=>'int', 'key='=>'string'],
    'hash_update' => ['bool', 'context'=>'HashContext', 'data'=>'string'],
    'hash_update_file' => ['bool', 'context='=>'HashContext', 'filename'=>'string', 'scontext='=>'?HashContext'],
    'hash_update_stream' => ['int', 'context'=>'HashContext', 'handle'=>'', 'length='=>'int'],
    'imagebmp' => ['bool', 'image'=>'resource', 'to='=>'mixed', 'compressed='=>'bool'],
    'imagecreatefrombmp' => ['resource', 'filename'=>'string'],
    'imageopenpolygon' => ['bool', 'image'=>'resource', 'points'=>'array', 'num_points'=>'int', 'color'=>'int'],
    'imageresolution' => ['mixed', 'image'=>'resource', 'res_x='=>'int', 'res_y='=>'int'],
    'imagesetclip' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int'],
    'ldap_exop' => ['mixed', 'link'=>'resource', 'reqoid'=>'string', 'reqdata='=>'string', 'servercontrols='=>'array', 'retdata='=>'string', 'retoid='=>'string'],
    'ldap_exop_passwd' => ['mixed', 'link'=>'resource', 'user='=>'string', 'oldpw='=>'string', 'newpw='=>'string', 'serverctrls='=>'array'],
    'ldap_exop_refresh' => ['int', 'link'=>'resource', 'dn'=>'string', 'ttl'=>'int'],
    'ldap_exop_whoami' => ['string', 'link'=>'resource'],
    'ldap_parse_exop' => ['bool', 'link'=>'resource', 'result'=>'resource', 'retdata='=>'string', 'retoid='=>'string'],
    'mb_chr' => ['string', 'cp'=>'int', 'encoding='=>'string'],
    'mb_ord' => ['int', 'str'=>'string', 'enc='=>'string'],
    'mb_scrub' => ['string', 'str'=>'string', 'enc='=>'string'],
    'oci_register_taf_callback' => ['bool', 'connection'=>'resource', 'callback='=>'callable'],
    'oci_unregister_taf_callback' => ['bool', 'connection'=>'resource'],
    'ReflectionClass::isIterable' => ['bool'],
    'sapi_windows_vt100_support' => ['bool', 'stream'=>'resource', 'enable='=>'bool'],
    'socket_addrinfo_bind' => ['resource', 'addrinfo'=>'resource'],
    'socket_addrinfo_connect' => ['resource', 'addrinfo'=>'resource'],
    'socket_addrinfo_explain' => ['array', 'addrinfo'=>'resource'],
    'socket_addrinfo_lookup' => ['resource[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'],
    'sodium_add' => ['string', 'string_1'=>'string', 'string_2'=>'string'],
    'sodium_base642bin' => ['string', 'base64'=>'string', 'variant'=>'int', 'ignore'=>'string'],
    'sodium_bin2base64' => ['string', 'binary'=>'string', 'variant'=>'int'],
    'sodium_bin2hex' => ['string', 'binary'=>'string'],
    'sodium_compare' => ['int', 'string_1'=>'string', 'string_2'=>'string'],
    'sodium_crypto_aead_aes256gcm_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_aead_aes256gcm_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_aead_aes256gcm_is_available' => ['bool'],
    'sodium_crypto_aead_aes256gcm_keygen' => ['string'],
    'sodium_crypto_aead_chacha20poly1305_decrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_aead_chacha20poly1305_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_aead_chacha20poly1305_ietf_keygen' => ['string'],
    'sodium_crypto_aead_chacha20poly1305_keygen' => ['string'],
    'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_aead_xchacha20poly1305_ietf_keygen' => ['string'],
    'sodium_crypto_auth' => ['string', 'message'=>'string', 'key'=>'string'],
    'sodium_crypto_auth_keygen' => ['string'],
    'sodium_crypto_auth_verify' => ['bool', 'mac'=>'string', 'message'=>'string', 'key'=>'string'],
    'sodium_crypto_box' => ['string', 'string'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_box_keypair' => ['string'],
    'sodium_crypto_box_keypair_from_secretkey_and_publickey' => ['string', 'secret_key'=>'string', 'public_key'=>'string'],
    'sodium_crypto_box_open' => ['string|false', 'message'=>'string', 'nonce'=>'string', 'message_keypair'=>'string'],
    'sodium_crypto_box_publickey' => ['string', 'keypair'=>'string'],
    'sodium_crypto_box_publickey_from_secretkey' => ['string', 'secretkey'=>'string'],
    'sodium_crypto_box_seal' => ['string', 'message'=>'string', 'publickey'=>'string'],
    'sodium_crypto_box_seal_open' => ['string|false', 'message'=>'string', 'recipient_keypair'=>'string'],
    'sodium_crypto_box_secretkey' => ['string', 'keypair'=>'string'],
    'sodium_crypto_box_seed_keypair' => ['string', 'seed'=>'string'],
    'sodium_crypto_generichash' => ['string', 'msg'=>'string', 'key='=>'?string', 'length='=>'?int'],
    'sodium_crypto_generichash_final' => ['string', 'state'=>'string', 'length='=>'?int'],
    'sodium_crypto_generichash_init' => ['string', 'key='=>'?string', 'length='=>'?int'],
    'sodium_crypto_generichash_keygen' => ['string'],
    'sodium_crypto_generichash_update' => ['bool', 'state'=>'string', 'string'=>'string'],
    'sodium_crypto_kdf_derive_from_key' => ['string', 'subkey_len'=>'int', 'subkey_id'=>'int', 'context'=>'string', 'key'=>'string'],
    'sodium_crypto_kdf_keygen' => ['string'],
    'sodium_crypto_kx_client_session_keys' => ['string', 'client_keypair'=>'string', 'server_key'=>'string'],
    'sodium_crypto_kx_keypair' => ['string'],
    'sodium_crypto_kx_publickey' => ['string', 'keypair'=>'string'],
    'sodium_crypto_kx_secretkey' => ['string', 'keypair'=>'string'],
    'sodium_crypto_kx_seed_keypair' => ['string', 'seed'=>'string'],
    'sodium_crypto_kx_server_session_keys' => ['string', 'server_keypair'=>'string', 'client_key'=>'string'],
    'sodium_crypto_pwhash' => ['string', 'length'=>'int', 'password'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int', 'alg='=>'int'],
    'sodium_crypto_pwhash_scryptsalsa208sha256' => ['string', 'length'=>'int', 'password'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
    'sodium_crypto_pwhash_scryptsalsa208sha256_str' => ['string', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
    'sodium_crypto_pwhash_scryptsalsa208sha256_str_verify' => ['bool', 'hash'=>'string', 'password'=>'string'],
    'sodium_crypto_pwhash_str' => ['string', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
    'sodium_crypto_pwhash_str_needs_rehash' => ['bool', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
    'sodium_crypto_pwhash_str_verify' => ['bool', 'hash'=>'string', 'password'=>'string'],
    'sodium_crypto_scalarmult' => ['string', 'string_1'=>'string', 'string_2'=>'string'],
    'sodium_crypto_scalarmult_base' => ['string', 'string_1'=>'string', 'string_2'=>'string'],
    'sodium_crypto_secretbox' => ['string', 'plaintext'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_secretbox_keygen' => ['string'],
    'sodium_crypto_secretbox_open' => ['string|false', 'ciphertext'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_secretstream_xchacha20poly1305_init_pull' => ['string', 'header'=>'string', 'key'=>'string'],
    'sodium_crypto_secretstream_xchacha20poly1305_init_push' => ['array', 'key'=>'string'],
    'sodium_crypto_secretstream_xchacha20poly1305_keygen' => ['string'],
    'sodium_crypto_secretstream_xchacha20poly1305_pull' => ['array', 'state'=>'string', 'c'=>'string', 'ad='=>'string'],
    'sodium_crypto_secretstream_xchacha20poly1305_push' => ['string', 'state'=>'string', 'msg'=>'string', 'ad='=>'string', 'tag='=>'int'],
    'sodium_crypto_secretstream_xchacha20poly1305_rekey' => ['void', 'state'=>'string'],
    'sodium_crypto_shorthash' => ['string', 'message'=>'string', 'key'=>'string'],
    'sodium_crypto_shorthash_keygen' => ['string'],
    'sodium_crypto_sign' => ['string', 'message'=>'string', 'secretkey'=>'string'],
    'sodium_crypto_sign_detached' => ['string', 'message'=>'string', 'secretkey'=>'string'],
    'sodium_crypto_sign_ed25519_pk_to_curve25519' => ['string', 'ed25519pk'=>'string'],
    'sodium_crypto_sign_ed25519_sk_to_curve25519' => ['string', 'ed25519sk'=>'string'],
    'sodium_crypto_sign_keypair' => ['string'],
    'sodium_crypto_sign_keypair_from_secretkey_and_publickey' => ['string', 'secret_key'=>'string', 'public_key'=>'string'],
    'sodium_crypto_sign_open' => ['string|false', 'message'=>'string', 'publickey'=>'string'],
    'sodium_crypto_sign_publickey' => ['string', 'keypair'=>'string'],
    'sodium_crypto_sign_publickey_from_secretkey' => ['string', 'secretkey'=>'string'],
    'sodium_crypto_sign_secretkey' => ['string', 'keypair'=>'string'],
    'sodium_crypto_sign_seed_keypair' => ['string', 'seed'=>'string'],
    'sodium_crypto_sign_verify_detached' => ['bool', 'signature'=>'string', 'message'=>'string', 'publickey'=>'string'],
    'sodium_crypto_stream' => ['string', 'length'=>'int', 'nonce'=>'string', 'key'=>'string'],
    'sodium_crypto_stream_keygen' => ['string'],
    'sodium_crypto_stream_xor' => ['string', 'message'=>'string', 'nonce'=>'string', 'key'=>'string'],
    'sodium_hex2bin' => ['string', 'hex'=>'string', 'ignore='=>'string'],
    'sodium_increment' => ['string', '&binary_string'=>'string'],
    'sodium_memcmp' => ['int', 'string_1'=>'string', 'string_2'=>'string'],
    'sodium_memzero' => ['void', '&secret'=>'string'],
    'sodium_pad' => ['string', 'unpadded'=>'string', 'length'=>'int'],
    'sodium_unpad' => ['string', 'padded'=>'string', 'length'=>'int'],
    'stream_isatty' => ['bool', 'stream'=>'resource'],
    'ZipArchive::count' => ['int'],
    'ZipArchive::setEncryptionIndex' => ['bool', 'index'=>'int', 'method'=>'string', 'password='=>'string'],
    'ZipArchive::setEncryptionName' => ['bool', 'name'=>'string', 'method'=>'int', 'password='=>'string'],
],
'old' => [
    'hash_copy' => ['resource', 'context'=>'resource'],
    'hash_final' => ['string', 'context'=>'resource', 'raw_output='=>'bool'],
    'hash_hkdf' => ['string', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
    'hash_init' => ['resource', 'algo'=>'string', 'options='=>'int', 'key='=>'string'],
    'hash_pbkdf2' => ['string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
    'hash_update' => ['bool', 'context'=>'resource', 'data'=>'string'],
    'hash_update_file' => ['bool', 'hcontext'=>'resource', 'filename'=>'string', 'scontext='=>'?resource'],
    'hash_update_stream' => ['int', 'context'=>'resource', 'handle'=>'resource', 'length='=>'int'],
]
];
<?php declare(strict_types=1);
namespace Phan\Language\Internal;

/**
 * A list of classes that support dynamic properties. These have
 * been manually entered.
 */
return [
    'stdclass',
    'simplexmlelement',
    'solrobject',
    'v8js',
];
<?php // @codingStandardsIgnoreFile (phpcs runs out of memory)
namespace Phan\Language\Internal;

<<<PHAN
@phan-file-suppress PhanPluginMixedKeyNoKey (read by Phan when analyzing this file)
PHAN;

/**
 * Format
 *
 * '<function_name>' => ['<return_type>, '<arg_name>'=>'<arg_type>']
 * alternative signature for the same function
 * '<function_name\'1>' => ['<return_type>, '<arg_name>'=>'<arg_type>']
 *
 * A '&' in front of the <arg_name> means the arg is always passed by reference.
 * (i.e. ReflectionParameter->isPassedByReference())
 * This was previously only used in cases where the function actually created the
 * variable in the local scope.
 * Some reference arguments will have prefixes in <arg_name> to indicate the way the argument is used.
 * Currently, the only prefixes with meaning are 'rw_' (read-write) and 'w_' (write).
 * Those prefixes don't mean anything for non-references.
 * Code using these signatures should remove those prefixes from messages rendered to the user.
 * 1. '&rw_<arg_name>' indicates that a parameter with a value is expected to be passed in, and may be modified.
 *    Phan will warn if the variable has an incompatible type, or is undefined.
 * 2. '&w_<arg_name>' indicates that a parameter is expected to be passed in, and the value will be ignored, and may be overwritten.
 * 3. The absence of a prefix is treated by Phan the same way as having the prefix 'w_' (Some may be changed to 'rw_name'). These will have prefixes added later.
 *
 * So, for functions like sort() where technically the arg is by-ref,
 * indicate the reference param's signature by-ref and read-write,
 * as `'&rw_array'=>'array'`
 * so that Phan won't create it in the local scope
 *
 * However, for a function like preg_match() where the 3rd arg is an array of sub-pattern matches (and optional),
 * this arg needs to be marked as by-ref and write-only, as `'&w_matches='=>'array'`.
 *
 * A '=' following the <arg_name> indicates this arg is optional.
 *
 * The <arg_name> can begin with '...' to indicate the arg is variadic.
 * '...args=' indicates it is both variadic and optional.
 *
 * Some reference arguments will have prefixes in <arg_name> to indicate the way the argument is used.
 * Currently, the only prefixes with meaning are 'rw_' and 'w_'.
 * Code using these signatures should remove those prefixes from messages rendered to the user.
 * 1. '&rw_name' indicates that a parameter with a value is expected to be passed in, and may be modified.
 * 2. '&w_name' indicates that a parameter is expected to be passed in, and the value will be ignored, and may be overwritten.
 *
 * This file contains the signatures for the most recent minor release of PHP supported by phan (php 7.2)
 *
 * Changes:
 *
 * In Phan 0.12.3,
 *
 * - This started using array shapes for union types (array{...}).
 *
 *   \Phan\Language\UnionType->withFlattenedArrayShapeTypeInstances() may be of help to programatically convert these to array<string,T1>|array<string,T2>
 *
 * - This started using array shapes with optional fields for union types (array{key?:int}).
 *   A `?` after the array shape field's key indicates that the field is optional.
 *
 * - This started adding param signatures and return signatures to `callable` types.
 *   E.g. 'usort' => ['bool', '&rw_array_arg'=>'array', 'cmp_function'=>'callable(mixed,mixed):int'].
 *   See NEWS.md for 0.12.3 for possible syntax. A suffix of `=` within `callable(...)` means that a parameter is optional.
 *
 *   (Phan assumes that callbacks with optional arguments can be cast to callbacks with/without those args (Similar to inheritance checks)
 *   (e.g. callable(T1,T2=) can be cast to callable(T1) or callable(T1,T2), in the same way that a subclass would check).
 *   For some signatures, e.g. set_error_handler, this results in repetition, because callable(T1=) can't cast to callable(T1).
 *
 * Sources of stub info:
 *
 * 1. Reflection
 * 2. docs.php.net's SVN repo or website, and examples (See internal/internalsignatures.php)
 * 3. Various websites documenting individual extensions
 * 4. PHPStorm stubs (For anything missing from the above sources)
 *    See internal/internalsignatures.php
 */
return [
'_' => ['string', 'message'=>'string'],
'__halt_compiler' => ['void'],
'abs' => ['int', 'number'=>'int'],
'abs\'1' => ['float', 'number'=>'float'],
'accelerator_get_configuration' => ['array'],
'accelerator_get_scripts' => ['array'],
'accelerator_get_status' => ['array', 'fetch_scripts'=>'bool'],
'accelerator_reset' => [''],
'accelerator_set_status' => ['void', 'status'=>''],
'acos' => ['float', 'number'=>'float'],
'acosh' => ['float', 'number'=>'float'],
'addcslashes' => ['string', 'str'=>'string', 'charlist'=>'string'],
'addslashes' => ['string', 'str'=>'string'],
'aggregate' => ['', 'object'=>'object', 'class_name'=>'string'],
'aggregate_methods' => ['', 'object'=>'object', 'class_name'=>'string'],
'aggregate_methods_by_list' => ['', 'object'=>'object', 'class_name'=>'string', 'methods_list'=>'array', 'exclude='=>'bool'],
'aggregate_methods_by_regexp' => ['', 'object'=>'object', 'class_name'=>'string', 'regexp'=>'string', 'exclude='=>'bool'],
'aggregate_properties' => ['', 'object'=>'object', 'class_name'=>'string'],
'aggregate_properties_by_list' => ['', 'object'=>'object', 'class_name'=>'string', 'properties_list'=>'array', 'exclude='=>'bool'],
'aggregate_properties_by_regexp' => ['', 'object'=>'object', 'class_name'=>'string', 'regexp'=>'string', 'exclude='=>'bool'],
'AMQPChannel::__construct' => ['void', 'amqp_connection'=>'AMQPConnection'],
'AMQPChannel::basicRecover' => ['', 'requeue='=>'bool|true'],
'AMQPChannel::commitTransaction' => ['bool'],
'AMQPChannel::getChannelId' => ['int'],
'AMQPChannel::getConnection' => ['AMQPConnection'],
'AMQPChannel::getPrefetchCount' => ['int'],
'AMQPChannel::getPrefetchSize' => ['int'],
'AMQPChannel::isConnected' => ['bool'],
'AMQPChannel::qos' => ['bool', 'size'=>'int', 'count'=>'int'],
'AMQPChannel::rollbackTransaction' => ['bool'],
'AMQPChannel::setPrefetchCount' => ['bool', 'count'=>'int'],
'AMQPChannel::setPrefetchSize' => ['bool', 'size'=>'int'],
'AMQPChannel::startTransaction' => ['bool'],
'AMQPConnection::__construct' => ['void', 'credentials='=>'array'],
'AMQPConnection::connect' => ['bool'],
'AMQPConnection::disconnect' => ['bool'],
'AMQPConnection::getHost' => ['string'],
'AMQPConnection::getLogin' => ['string'],
'AMQPConnection::getMaxChannels' => ['int|null'],
'AMQPConnection::getPassword' => ['string'],
'AMQPConnection::getPort' => ['int'],
'AMQPConnection::getReadTimeout' => ['float'],
'AMQPConnection::getTimeout' => ['float'],
'AMQPConnection::getUsedChannels' => ['int'],
'AMQPConnection::getVhost' => ['string'],
'AMQPConnection::getWriteTimeout' => ['float'],
'AMQPConnection::isConnected' => ['bool'],
'AMQPConnection::isPersistent' => ['bool|null'],
'AMQPConnection::pconnect' => ['bool'],
'AMQPConnection::pdisconnect' => ['bool'],
'AMQPConnection::preconnect' => ['bool'],
'AMQPConnection::reconnect' => ['bool'],
'AMQPConnection::setHost' => ['bool', 'host'=>'string'],
'AMQPConnection::setLogin' => ['bool', 'login'=>'string'],
'AMQPConnection::setPassword' => ['bool', 'password'=>'string'],
'AMQPConnection::setPort' => ['bool', 'port'=>'int'],
'AMQPConnection::setReadTimeout' => ['bool', 'timeout'=>'int'],
'AMQPConnection::setTimeout' => ['bool', 'timeout'=>'int'],
'AMQPConnection::setVhost' => ['bool', 'vhost'=>'string'],
'AMQPConnection::setWriteTimeout' => ['bool', 'timeout'=>'int'],
'AMQPEnvelope::getAppId' => ['string'],
'AMQPEnvelope::getBody' => ['string'],
'AMQPEnvelope::getContentEncoding' => ['string'],
'AMQPEnvelope::getContentType' => ['string'],
'AMQPEnvelope::getCorrelationId' => ['string'],
'AMQPEnvelope::getDeliveryMode' => ['int'],
'AMQPEnvelope::getDeliveryTag' => ['string'],
'AMQPEnvelope::getExchangeName' => ['string'],
'AMQPEnvelope::getExpiration' => ['string'],
'AMQPEnvelope::getHeader' => ['bool|string', 'header_key'=>'string'],
'AMQPEnvelope::getHeaders' => ['array'],
'AMQPEnvelope::getMessageId' => ['string'],
'AMQPEnvelope::getPriority' => ['int'],
'AMQPEnvelope::getReplyTo' => ['string'],
'AMQPEnvelope::getRoutingKey' => ['string'],
'AMQPEnvelope::getTimeStamp' => ['string'],
'AMQPEnvelope::getType' => ['string'],
'AMQPEnvelope::getUserId' => ['string'],
'AMQPEnvelope::isRedelivery' => ['bool'],
'AMQPExchange::__construct' => ['void', 'amqp_channel'=>'AMQPChannel'],
'AMQPExchange::bind' => ['bool', 'exchange_name'=>'string', 'routing_key='=>'string', 'arguments='=>'array'],
'AMQPExchange::declareExchange' => ['bool'],
'AMQPExchange::delete' => ['bool', 'exchangeName='=>'string', 'flags='=>'int'],
'AMQPExchange::getArgument' => ['bool|int|string', 'key'=>'string'],
'AMQPExchange::getArguments' => ['array'],
'AMQPExchange::getChannel' => ['AMQPChannel'],
'AMQPExchange::getConnection' => ['AMQPConnection'],
'AMQPExchange::getFlags' => ['int'],
'AMQPExchange::getName' => ['string'],
'AMQPExchange::getType' => ['string'],
'AMQPExchange::publish' => ['bool', 'message'=>'string', 'routing_key='=>'string', 'flags='=>'int', 'attributes='=>'array'],
'AMQPExchange::setArgument' => ['bool', 'key'=>'string', 'value'=>'int|string'],
'AMQPExchange::setArguments' => ['bool', 'arguments'=>'array'],
'AMQPExchange::setFlags' => ['bool', 'flags'=>'int'],
'AMQPExchange::setName' => ['bool', 'exchange_name'=>'string'],
'AMQPExchange::setType' => ['bool', 'exchange_type'=>'string'],
'AMQPExchange::unbind' => ['bool', 'exchange_name'=>'string', 'routing_key='=>'string', 'arguments='=>'array'],
'AMQPQueue::__construct' => ['void', 'amqp_channel'=>'AMQPChannel'],
'AMQPQueue::ack' => ['bool', 'delivery_tag'=>'string', 'flags='=>'int'],
'AMQPQueue::bind' => ['bool', 'exchange_name'=>'string', 'routing_key='=>'string', 'arguments='=>'array'],
'AMQPQueue::cancel' => ['bool', 'consumer_tag='=>'string'],
'AMQPQueue::consume' => ['void', 'callback='=>'?callable', 'flags='=>'int', 'consumerTag='=>'string'],
'AMQPQueue::declareQueue' => ['int'],
'AMQPQueue::delete' => ['int', 'flags='=>'int'],
'AMQPQueue::get' => ['AMQPEnvelope|bool', 'flags='=>'int'],
'AMQPQueue::getArgument' => ['bool|int|string', 'key'=>'string'],
'AMQPQueue::getArguments' => ['array'],
'AMQPQueue::getChannel' => ['AMQPChannel'],
'AMQPQueue::getConnection' => ['AMQPConnection'],
'AMQPQueue::getFlags' => ['int'],
'AMQPQueue::getName' => ['string'],
'AMQPQueue::nack' => ['bool', 'delivery_tag'=>'string', 'flags='=>'int'],
'AMQPQueue::purge' => ['bool'],
'AMQPQueue::reject' => ['bool', 'delivery_tag'=>'string', 'flags='=>'int'],
'AMQPQueue::setArgument' => ['bool', 'key'=>'string', 'value'=>'mixed'],
'AMQPQueue::setArguments' => ['bool', 'arguments'=>'array'],
'AMQPQueue::setFlags' => ['bool', 'flags'=>'int'],
'AMQPQueue::setName' => ['bool', 'queue_name'=>'string'],
'AMQPQueue::unbind' => ['bool', 'exchange_name'=>'string', 'routing_key='=>'string', 'arguments='=>'array'],
'apache_child_terminate' => ['bool'],
'apache_get_modules' => ['array'],
'apache_get_version' => ['string|false'],
'apache_getenv' => ['string|false', 'variable'=>'string', 'walk_to_top='=>'bool'],
'apache_lookup_uri' => ['object', 'filename'=>'string'],
'apache_note' => ['string|false', 'note_name'=>'string', 'note_value='=>'string'],
'apache_request_headers' => ['array|false'],
'apache_reset_timeout' => ['bool'],
'apache_response_headers' => ['array|false'],
'apache_setenv' => ['bool', 'variable'=>'string', 'value'=>'string', 'walk_to_top='=>'bool'],
'apc_add' => ['bool', 'key'=>'string', 'ttl='=>'int'],
'apc_add\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'],
'apc_bin_dump' => ['string', 'files='=>'array', 'user_vars='=>'array'],
'apc_bin_dumpfile' => ['int', 'files'=>'array', 'user_vars'=>'array', 'filename'=>'string', 'flags='=>'int', 'context='=>'resource'],
'apc_bin_load' => ['bool', 'data'=>'string', 'flags='=>'int'],
'apc_bin_loadfile' => ['bool', 'filename'=>'string', 'context='=>'resource', 'flags='=>'int'],
'apc_cache_info' => ['array', 'cache_type='=>'string', 'limited='=>'bool'],
'apc_cas' => ['bool', 'key'=>'string', 'old'=>'int', 'new'=>'int'],
'apc_clear_cache' => ['bool', 'cache_type='=>'string'],
'apc_compile_file' => ['mixed', 'filename'=>'string', 'atomic='=>'bool'],
'apc_dec' => ['int', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool'],
'apc_define_constants' => ['bool', 'key'=>'string', 'constants'=>'array', 'case_sensitive='=>'bool'],
'apc_delete' => ['bool', 'key'=>'string|string[]|APCIterator'],
'apc_delete_file' => ['mixed', 'keys'=>'mixed'],
'apc_exists' => ['bool', 'keys'=>'string'],
'apc_exists\'1' => ['array', 'keys'=>'string[]'],
'apc_fetch' => ['mixed', 'key'=>'mixed', '&w_success='=>'bool'],
'apc_inc' => ['int', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool'],
'apc_load_constants' => ['bool', 'key'=>'string', 'case_sensitive='=>'bool'],
'apc_sma_info' => ['array', 'limited='=>'bool'],
'apc_store' => ['bool', 'key'=>'string', 'var'=>'', 'ttl='=>'int'],
'apc_store\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'],
'APCIterator::__construct' => ['void', 'cache'=>'string', 'search='=>'', 'format='=>'int', 'chunk_size='=>'int', 'list='=>'int'],
'APCIterator::current' => ['mixed'],
'APCIterator::getTotalCount' => ['int'],
'APCIterator::getTotalHits' => ['int'],
'APCIterator::getTotalSize' => ['int'],
'APCIterator::key' => ['string'],
'APCIterator::next' => ['void'],
'APCIterator::rewind' => ['void'],
'APCIterator::valid' => ['bool'],
'apcu_add' => ['bool', 'key'=>'string', 'var'=>'', 'ttl='=>'int'],
'apcu_add\'1' => ['array<string,int>', 'values'=>'array<string,mixed>', 'unused='=>'', 'ttl='=>'int'],
'apcu_cache_info' => ['array<string,mixed>', 'limited='=>'bool'],
'apcu_cas' => ['bool', 'key'=>'string', 'old'=>'int', 'new'=>'int'],
'apcu_clear_cache' => ['bool'],
'apcu_dec' => ['int', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool'],
'apcu_delete' => ['bool|array<int,string>', 'key'=>'string|string[]|APCUIterator'],
'apcu_entry' => ['mixed', 'key'=>'string', 'generator'=>'callable', 'ttl='=>'int'],
'apcu_exists' => ['bool', 'keys'=>'string'],
'apcu_exists\'1' => ['array', 'keys'=>'string[]'],
'apcu_fetch' => ['mixed', 'key'=>'string||string[]', '&w_success='=>'bool'],
'apcu_inc' => ['int', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool'],
'apcu_sma_info' => ['array', 'limited='=>'bool'],
'apcu_store' => ['bool', 'key'=>'string', 'var='=>'', 'ttl='=>'int'],
'apcu_store\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'],
'APCUIterator::__construct' => ['void', 'search='=>'string|string[]|null', 'format='=>'int', 'chunk_size='=>'int', 'list='=>'int'],
'APCUIterator::current' => ['mixed'],
'APCUIterator::getTotalCount' => ['int'],
'APCUIterator::getTotalHits' => ['int'],
'APCUIterator::getTotalSize' => ['int'],
'APCUIterator::key' => ['string'],
'APCUIterator::next' => ['void'],
'APCUIterator::rewind' => ['void'],
'APCUIterator::valid' => ['bool'],
'apd_breakpoint' => ['bool', 'debug_level'=>'int'],
'apd_callstack' => ['array'],
'apd_clunk' => ['void', 'warning'=>'string', 'delimiter='=>'string'],
'apd_continue' => ['bool', 'debug_level'=>'int'],
'apd_croak' => ['void', 'warning'=>'string', 'delimiter='=>'string'],
'apd_dump_function_table' => ['void'],
'apd_dump_persistent_resources' => ['array'],
'apd_dump_regular_resources' => ['array'],
'apd_echo' => ['bool', 'output'=>'string'],
'apd_get_active_symbols' => ['array'],
'apd_set_pprof_trace' => ['string', 'dump_directory='=>'string', 'fragment='=>'string'],
'apd_set_session' => ['void', 'debug_level'=>'int'],
'apd_set_session_trace' => ['void', 'debug_level'=>'int', 'dump_directory='=>'string'],
'apd_set_session_trace_socket' => ['bool', 'tcp_server'=>'string', 'socket_type'=>'int', 'port'=>'int', 'debug_level'=>'int'],
'AppendIterator::__construct' => ['void'],
'AppendIterator::append' => ['void', 'it'=>'iterator'],
'AppendIterator::current' => ['mixed'],
'AppendIterator::getArrayIterator' => ['ArrayIterator'],
'AppendIterator::getInnerIterator' => ['iterator'],
'AppendIterator::getIteratorIndex' => ['int'],
'AppendIterator::key' => ['int|string|float|bool'],
'AppendIterator::next' => ['void'],
'AppendIterator::rewind' => ['void'],
'AppendIterator::valid' => ['bool'],
'array_change_key_case' => ['array', 'input'=>'array', 'case='=>'int'],
'array_chunk' => ['array[]', 'input'=>'array', 'size'=>'int', 'preserve_keys='=>'bool'],
'array_column' => ['array', 'array'=>'array', 'column_key'=>'mixed', 'index_key='=>'mixed'],
'array_combine' => ['array', 'keys'=>'array', 'values'=>'array'],
'array_count_values' => ['int[]', 'input'=>'array'],
'array_diff' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'],
'array_diff_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'],
'array_diff_key' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'],
'array_diff_uassoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_comp_func'=>'callable(mixed,mixed):int'],
'array_diff_uassoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'],
'array_diff_ukey' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'key_comp_func'=>'callable(mixed,mixed):int'],
'array_diff_ukey\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'],
'array_fill' => ['array', 'start_key'=>'int', 'num'=>'int', 'val'=>'mixed'],
'array_fill_keys' => ['array', 'keys'=>'array', 'val'=>'mixed'],
'array_filter' => ['array', 'input'=>'array', 'callback='=>'callable(mixed,mixed):bool|callable(mixed):bool', 'flag='=>'int'],
'array_flip' => ['array', 'input'=>'array'],
'array_intersect' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'],
'array_intersect_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'],
'array_intersect_key' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'],
'array_intersect_uassoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'],
'array_intersect_uassoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest'=>'array|callable(mixed,mixed):int'],
'array_intersect_ukey' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'key_compare_func'=>'callable(mixed,mixed):int'],
'array_intersect_ukey\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest'=>'array|callable(mixed,mixed):int'],
'array_key_exists' => ['bool', 'key'=>'string|int', 'search'=>'array'],
'array_keys' => ['array', 'input'=>'array', 'search_value='=>'mixed', 'strict='=>'bool'],
'array_map' => ['array', 'callback'=>'?callable', 'input1'=>'array', '...args='=>'array'],
'array_merge' => ['array', 'arr1'=>'array', '...args='=>'array'],
'array_merge_recursive' => ['array', 'arr1'=>'array', '...args='=>'array'],
'array_multisort' => ['bool', '&rw_array1'=>'array', 'array1_sort_order='=>'array|int', 'array1_sort_flags='=>'array|int', '...args='=>'array|int'],
'array_pad' => ['array', 'input'=>'array', 'pad_size'=>'int', 'pad_value'=>'mixed'],
'array_pop' => ['mixed', '&rw_stack'=>'array'],
'array_product' => ['int|float', 'input'=>'array'],
'array_push' => ['int', '&rw_stack'=>'array', 'var'=>'mixed', '...vars='=>'mixed'],
'array_rand' => ['int|string|array<int,int>|array<int,string>', 'input'=>'array', 'num_req'=>'int'],
'array_rand\'1' => ['int|string', 'input'=>'array'],
'array_reduce' => ['mixed', 'input'=>'array', 'callback'=>'callable(mixed,mixed):mixed', 'initial='=>'mixed'],
'array_replace' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'],
'array_replace_recursive' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'],
'array_reverse' => ['array', 'input'=>'array', 'preserve='=>'bool'],
'array_search' => ['int|string|false', 'needle'=>'mixed', 'haystack'=>'array', 'strict='=>'bool'],
'array_shift' => ['mixed', '&rw_stack'=>'array'],
'array_slice' => ['array', 'input'=>'array', 'offset'=>'int', 'length='=>'?int', 'preserve_keys='=>'bool'],
'array_splice' => ['array', '&rw_input'=>'array', 'offset'=>'int', 'length='=>'int', 'replacement='=>'array|string'],
'array_sum' => ['int|float', 'input'=>'array'],
'array_udiff' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_comp_func'=>'callable(mixed,mixed):int'],
'array_udiff\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'],
'array_udiff_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'key_comp_func'=>'callable(mixed,mixed):int'],
'array_udiff_assoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'],
'array_udiff_uassoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_comp_func'=>'callable(mixed,mixed):int', 'key_comp_func'=>'callable(mixed,mixed):int'],
'array_udiff_uassoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'],
'array_uintersect' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int'],
'array_uintersect\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'],
'array_uintersect_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int'],
'array_uintersect_assoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable', '...rest='=>'array|callable(mixed,mixed):int'],
'array_uintersect_uassoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int', 'key_compare_func'=>'callable(mixed,mixed):int'],
'array_uintersect_uassoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'],
'array_unique' => ['array', 'input'=>'array', 'sort_flags='=>'int'],
'array_unshift' => ['int', '&rw_stack'=>'array', 'var'=>'mixed', '...vars='=>'mixed'],
'array_values' => ['array', 'input'=>'array'],
'array_walk' => ['bool', '&rw_input'=>'array', 'callback'=>'callable', 'userdata='=>'mixed'],
'array_walk_recursive' => ['bool', '&rw_input'=>'array', 'callback'=>'callable', 'userdata='=>'mixed'],
'ArrayAccess::offsetExists' => ['bool', 'offset'=>'mixed'],
'ArrayAccess::offsetGet' => ['mixed', 'offset'=>'mixed'],
'ArrayAccess::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'],
'ArrayAccess::offsetUnset' => ['void', 'offset'=>'mixed'],
'ArrayIterator::__construct' => ['void', 'array='=>'array|object', 'flags='=>'int'],
'ArrayIterator::append' => ['void', 'value'=>'mixed'],
'ArrayIterator::asort' => ['void'],
'ArrayIterator::count' => ['int'],
'ArrayIterator::current' => ['mixed'],
'ArrayIterator::getArrayCopy' => ['array'],
'ArrayIterator::getFlags' => ['void'],
'ArrayIterator::key' => ['int|string|false'],
'ArrayIterator::ksort' => ['void'],
'ArrayIterator::natcasesort' => ['void'],
'ArrayIterator::natsort' => ['void'],
'ArrayIterator::next' => ['void'],
'ArrayIterator::offsetExists' => ['void', 'index'=>'string'],
'ArrayIterator::offsetGet' => ['mixed', 'index'=>'string'],
'ArrayIterator::offsetSet' => ['void', 'index'=>'string', 'newval'=>'string'],
'ArrayIterator::offsetUnset' => ['void', 'index'=>'string'],
'ArrayIterator::rewind' => ['void'],
'ArrayIterator::seek' => ['void', 'position'=>'int'],
'ArrayIterator::serialize' => ['string'],
'ArrayIterator::setFlags' => ['void', 'flags'=>'string'],
'ArrayIterator::uasort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'],
'ArrayIterator::uksort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'],
'ArrayIterator::unserialize' => ['string', 'serialized'=>'string'],
'ArrayIterator::valid' => ['bool'],
'ArrayObject::__construct' => ['void', 'input='=>'array|object', 'flags='=>'int', 'iterator_class='=>'string'],
'ArrayObject::append' => ['void', 'value'=>'mixed'],
'ArrayObject::asort' => ['void'],
'ArrayObject::count' => ['int'],
'ArrayObject::exchangeArray' => ['array', 'ar'=>'mixed'],
'ArrayObject::getArrayCopy' => ['array'],
'ArrayObject::getFlags' => ['int'],
'ArrayObject::getIterator' => ['ArrayIterator'],
'ArrayObject::getIteratorClass' => ['string'],
'ArrayObject::ksort' => ['void'],
'ArrayObject::natcasesort' => ['void'],
'ArrayObject::natsort' => ['void'],
'ArrayObject::offsetExists' => ['bool', 'index'=>'mixed'],
'ArrayObject::offsetGet' => ['mixed', 'index'=>'mixed'],
'ArrayObject::offsetSet' => ['void', 'index'=>'mixed', 'newval'=>'mixed'],
'ArrayObject::offsetUnset' => ['void', 'index'=>'mixed'],
'ArrayObject::serialize' => ['string'],
'ArrayObject::setFlags' => ['void', 'flags'=>'int'],
'ArrayObject::setIteratorClass' => ['void', 'iterator_class'=>'string'],
'ArrayObject::uasort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'],
'ArrayObject::uksort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'],
'ArrayObject::unserialize' => ['void', 'serialized'=>'string'],
'arsort' => ['bool', '&rw_array_arg'=>'array', 'sort_flags='=>'int'],
'asin' => ['float', 'number'=>'float'],
'asinh' => ['float', 'number'=>'float'],
'asort' => ['bool', '&rw_array_arg'=>'array', 'sort_flags='=>'int'],
'assert' => ['int', 'assertion'=>'string|bool', 'description='=>'string|Throwable|null'],
'assert_options' => ['mixed', 'what'=>'int', 'value='=>'mixed'],
'ast\get_kind_name' => ['string', 'kind'=>'int'],
'ast\get_metadata' => ['array<int,ast\Metadata>'],
'ast\get_supported_versions' => ['array<int,int>', 'exclude_deprecated='=>'bool'],
'ast\kind_uses_flags' => ['bool', 'kind'=>'int'],
'ast\Node::__construct' => ['void', 'kind='=>'int', 'flags='=>'int', 'children='=>'ast\Node\Decl[]|ast\Node[]|array[]|int[]|string[]|float[]|bool[]|null[]', 'start_line='=>'int'],
'ast\parse_code' => ['ast\Node', 'code'=>'string', 'version'=>'int', 'filename='=>'string'],
'ast\parse_file' => ['ast\Node', 'filename'=>'string', 'version'=>'int'],
'atan' => ['float', 'number'=>'float'],
'atan2' => ['float', 'y'=>'float', 'x'=>'float'],
'atanh' => ['float', 'number'=>'float'],
'BadFunctionCallException::__clone' => ['void'],
'BadFunctionCallException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?BadFunctionCallException'],
'BadFunctionCallException::__toString' => ['string'],
'BadFunctionCallException::getCode' => ['int'],
'BadFunctionCallException::getFile' => ['string'],
'BadFunctionCallException::getLine' => ['int'],
'BadFunctionCallException::getMessage' => ['string'],
'BadFunctionCallException::getPrevious' => ['?Throwable|?BadFunctionCallException'],
'BadFunctionCallException::getTrace' => ['array'],
'BadFunctionCallException::getTraceAsString' => ['string'],
'BadMethodCallException::__clone' => ['void'],
'BadMethodCallException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?BadMethodCallException'],
'BadMethodCallException::__toString' => ['string'],
'BadMethodCallException::getCode' => ['int'],
'BadMethodCallException::getFile' => ['string'],
'BadMethodCallException::getLine' => ['int'],
'BadMethodCallException::getMessage' => ['string'],
'BadMethodCallException::getPrevious' => ['?Throwable|?BadMethodCallException'],
'BadMethodCallException::getTrace' => ['array'],
'BadMethodCallException::getTraceAsString' => ['string'],
'base64_decode' => ['string', 'str'=>'string', 'strict='=>'bool'],
'base64_encode' => ['string', 'str'=>'string'],
'base_convert' => ['string', 'number'=>'string', 'frombase'=>'int', 'tobase'=>'int'],
'basename' => ['string', 'path'=>'string', 'suffix='=>'string'],
'bbcode_add_element' => ['bool', 'bbcode_container'=>'resource', 'tag_name'=>'string', 'tag_rules'=>'array'],
'bbcode_add_smiley' => ['bool', 'bbcode_container'=>'resource', 'smiley'=>'string', 'replace_by'=>'string'],
'bbcode_create' => ['resource', 'bbcode_initial_tags='=>'array'],
'bbcode_destroy' => ['bool', 'bbcode_container'=>'resource'],
'bbcode_parse' => ['string', 'bbcode_container'=>'resource', 'to_parse'=>'string'],
'bbcode_set_arg_parser' => ['bool', 'bbcode_container'=>'resource', 'bbcode_arg_parser'=>'resource'],
'bbcode_set_flags' => ['bool', 'bbcode_container'=>'resource', 'flags'=>'int', 'mode='=>'int'],
'bcadd' => ['string', 'left_operand'=>'int|string', 'right_operand'=>'int|string', 'scale='=>'int'],
'bccomp' => ['int', 'left_operand'=>'int|string', 'right_operand'=>'int|string', 'scale='=>'int'],
'bcdiv' => ['string', 'left_operand'=>'int|string', 'right_operand'=>'int|string', 'scale='=>'int'],
'bcmod' => ['string', 'left_operand'=>'int|string', 'right_operand'=>'int|string'],
'bcmul' => ['string', 'left_operand'=>'int|string', 'right_operand'=>'int|string', 'scale='=>'int'],
'bcompiler_load' => ['bool', 'filename'=>'string'],
'bcompiler_load_exe' => ['bool', 'filename'=>'string'],
'bcompiler_parse_class' => ['bool', 'class'=>'string', 'callback'=>'string'],
'bcompiler_read' => ['bool', 'filehandle'=>'resource'],
'bcompiler_write_class' => ['bool', 'filehandle'=>'resource', 'classname'=>'string', 'extends='=>'string'],
'bcompiler_write_constant' => ['bool', 'filehandle'=>'resource', 'constantname'=>'string'],
'bcompiler_write_exe_footer' => ['bool', 'filehandle'=>'resource', 'startpos'=>'int'],
'bcompiler_write_file' => ['bool', 'filehandle'=>'resource', 'filename'=>'string'],
'bcompiler_write_footer' => ['bool', 'filehandle'=>'resource'],
'bcompiler_write_function' => ['bool', 'filehandle'=>'resource', 'functionname'=>'string'],
'bcompiler_write_functions_from_file' => ['bool', 'filehandle'=>'resource', 'filename'=>'string'],
'bcompiler_write_header' => ['bool', 'filehandle'=>'resource', 'write_ver='=>'string'],
'bcompiler_write_included_filename' => ['bool', 'filehandle'=>'resource', 'filename'=>'string'],
'bcpow' => ['string', 'x'=>'int|string', 'y'=>'int|string', 'scale='=>'int'],
'bcpowmod' => ['string', 'x'=>'int|string', 'y'=>'int|string', 'mod'=>'int|string', 'scale='=>'int'],
'bcscale' => ['bool', 'scale'=>'int'],
'bcsqrt' => ['string', 'operand'=>'int|string', 'scale='=>'int'],
'bcsub' => ['string', 'left_operand'=>'int|string', 'right_operand'=>'int|string', 'scale='=>'int'],
'bin2hex' => ['string', 'data'=>'string'],
'bind_textdomain_codeset' => ['string', 'domain'=>'string', 'codeset'=>'string'],
'bindec' => ['int', 'binary_number'=>'string'],
'bindtextdomain' => ['string', 'domain_name'=>'string', 'dir'=>'string'],
'birdstep_autocommit' => ['bool', 'index'=>'int'],
'birdstep_close' => ['bool', 'id'=>'int'],
'birdstep_commit' => ['bool', 'index'=>'int'],
'birdstep_connect' => ['int', 'server'=>'string', 'user'=>'string', 'pass'=>'string'],
'birdstep_exec' => ['int', 'index'=>'int', 'exec_str'=>'string'],
'birdstep_fetch' => ['bool', 'index'=>'int'],
'birdstep_fieldname' => ['string', 'index'=>'int', 'col'=>'int'],
'birdstep_fieldnum' => ['int', 'index'=>'int'],
'birdstep_freeresult' => ['bool', 'index'=>'int'],
'birdstep_off_autocommit' => ['bool', 'index'=>'int'],
'birdstep_result' => ['', 'index'=>'int', 'col'=>''],
'birdstep_rollback' => ['bool', 'index'=>'int'],
'blenc_encrypt' => ['string', 'plaintext'=>'string', 'encodedfile'=>'string', 'encryption_key='=>'string'],
'boolval' => ['bool', 'var'=>'mixed'],
'BSON\Binary::__construct' => ['void', 'data'=>'string', 'subtype'=>'string'],
'BSON\Binary::getSubType' => [''],
'BSON\fromArray' => ['string', 'array'=>'string'],
'BSON\fromJSON' => ['string', 'json'=>'string'],
'BSON\Javascript::__construct' => ['void', 'javascript'=>'string', 'scope='=>'string'],
'BSON\ObjectID::__construct' => ['void', 'id='=>'string'],
'BSON\ObjectID::__toString' => ['string'],
'BSON\Regex::__construct' => ['void', 'pattern'=>'string', 'flags'=>'string'],
'BSON\Regex::__toString' => ['string'],
'BSON\Regex::getFlags' => [''],
'BSON\Regex::getPattern' => [''],
'BSON\Serializable::bsonSerialize' => ['string'],
'BSON\Timestamp::__construct' => ['void', 'increment'=>'string', 'timestamp'=>'string'],
'BSON\Timestamp::__toString' => ['string'],
'BSON\toArray' => ['array', 'bson'=>'string'],
'BSON\toJSON' => ['string', 'bson'=>'string'],
'BSON\Unserializable::bsonUnserialize' => ['', 'data'=>'array'],
'BSON\UTCDatetime::__construct' => ['void', 'milliseconds'=>'string'],
'BSON\UTCDatetime::__toString' => ['string'],
'BSON\UTCDatetime::toDateTime' => [''],
'bson_decode' => ['array', 'bson'=>'string'],
'bson_encode' => ['string', 'anything'=>'mixed'],
'bzclose' => ['int', 'bz'=>'resource'],
'bzcompress' => ['string', 'source'=>'string', 'blocksize100k='=>'int', 'workfactor='=>'int'],
'bzdecompress' => ['string', 'source'=>'string', 'small='=>'int'],
'bzerrno' => ['int', 'bz'=>'resource'],
'bzerror' => ['array', 'bz'=>'resource'],
'bzerrstr' => ['string', 'bz'=>'resource'],
'bzflush' => ['int', 'bz'=>'resource'],
'bzopen' => ['resource', 'file'=>'string|resource', 'mode'=>'string'],
'bzread' => ['string', 'bz'=>'resource', 'length='=>'int'],
'bzwrite' => ['int', 'bz'=>'resource', 'data'=>'string', 'length='=>'int'],
'CachingIterator::__construct' => ['void', 'it'=>'iterator', 'flags='=>''],
'CachingIterator::__toString' => ['string'],
'CachingIterator::count' => ['int'],
'CachingIterator::current' => ['void'],
'CachingIterator::getCache' => ['array'],
'CachingIterator::getFlags' => ['int'],
'CachingIterator::getInnerIterator' => ['Iterator'],
'CachingIterator::hasNext' => ['bool'],
'CachingIterator::key' => ['int|string|float|bool'],
'CachingIterator::next' => ['void'],
'CachingIterator::offsetExists' => ['bool', 'index'=>'string'],
'CachingIterator::offsetGet' => ['string', 'index'=>'string'],
'CachingIterator::offsetSet' => ['void', 'index'=>'string', 'newval'=>'string'],
'CachingIterator::offsetUnset' => ['void', 'index'=>'string'],
'CachingIterator::rewind' => ['void'],
'CachingIterator::setFlags' => ['void', 'flags'=>'int'],
'CachingIterator::valid' => ['bool'],
'Cairo::availableFonts' => ['array'],
'Cairo::availableSurfaces' => ['array'],
'Cairo::statusToString' => ['string', 'status'=>'int'],
'Cairo::version' => ['int'],
'Cairo::versionString' => ['string'],
'cairo_append_path' => ['', 'path'=>'cairopath', 'context'=>'cairocontext'],
'cairo_arc' => ['', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'angle1'=>'float', 'angle2'=>'float', 'context'=>'cairocontext'],
'cairo_arc_negative' => ['', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'angle1'=>'float', 'angle2'=>'float', 'context'=>'cairocontext'],
'cairo_available_fonts' => ['array'],
'cairo_available_surfaces' => ['array'],
'cairo_clip' => ['', 'context'=>'cairocontext'],
'cairo_clip_extents' => ['array', 'context'=>'cairocontext'],
'cairo_clip_preserve' => ['', 'context'=>'cairocontext'],
'cairo_clip_rectangle_list' => ['array', 'context'=>'cairocontext'],
'cairo_close_path' => ['', 'context'=>'cairocontext'],
'cairo_copy_page' => ['', 'context'=>'cairocontext'],
'cairo_copy_path' => ['CairoPath', 'context'=>'cairocontext'],
'cairo_copy_path_flat' => ['CairoPath', 'context'=>'cairocontext'],
'cairo_create' => ['CairoContext', 'surface'=>'cairosurface'],
'cairo_curve_to' => ['', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float', 'context'=>'cairocontext'],
'cairo_device_to_user' => ['array', 'x'=>'float', 'y'=>'float', 'context'=>'cairocontext'],
'cairo_device_to_user_distance' => ['array', 'x'=>'float', 'y'=>'float', 'context'=>'cairocontext'],
'cairo_fill' => ['', 'context'=>'cairocontext'],
'cairo_fill_extents' => ['array', 'context'=>'cairocontext'],
'cairo_fill_preserve' => ['', 'context'=>'cairocontext'],
'cairo_font_extents' => ['array', 'context'=>'cairocontext'],
'cairo_font_face_get_type' => ['int', 'fontface'=>'cairofontface'],
'cairo_font_face_status' => ['int', 'fontface'=>'cairofontface'],
'cairo_font_options_create' => ['CairoFontOptions'],
'cairo_font_options_equal' => ['bool', 'options'=>'cairofontoptions', 'other'=>'cairofontoptions'],
'cairo_font_options_get_antialias' => ['int', 'options'=>'cairofontoptions'],
'cairo_font_options_get_hint_metrics' => ['int', 'options'=>'cairofontoptions'],
'cairo_font_options_get_hint_style' => ['int', 'options'=>'cairofontoptions'],
'cairo_font_options_get_subpixel_order' => ['int', 'options'=>'cairofontoptions'],
'cairo_font_options_hash' => ['int', 'options'=>'cairofontoptions'],
'cairo_font_options_merge' => ['void', 'options'=>'cairofontoptions', 'other'=>'cairofontoptions'],
'cairo_font_options_set_antialias' => ['void', 'options'=>'cairofontoptions', 'antialias'=>'int'],
'cairo_font_options_set_hint_metrics' => ['void', 'options'=>'cairofontoptions', 'hint_metrics'=>'int'],
'cairo_font_options_set_hint_style' => ['void', 'options'=>'cairofontoptions', 'hint_style'=>'int'],
'cairo_font_options_set_subpixel_order' => ['void', 'options'=>'cairofontoptions', 'subpixel_order'=>'int'],
'cairo_font_options_status' => ['int', 'options'=>'cairofontoptions'],
'cairo_format_stride_for_width' => ['int', 'format'=>'int', 'width'=>'int'],
'cairo_get_antialias' => ['int', 'context'=>'cairocontext'],
'cairo_get_current_point' => ['array', 'context'=>'cairocontext'],
'cairo_get_dash' => ['array', 'context'=>'cairocontext'],
'cairo_get_dash_count' => ['int', 'context'=>'cairocontext'],
'cairo_get_fill_rule' => ['int', 'context'=>'cairocontext'],
'cairo_get_font_face' => ['', 'context'=>'cairocontext'],
'cairo_get_font_matrix' => ['', 'context'=>'cairocontext'],
'cairo_get_font_options' => ['', 'context'=>'cairocontext'],
'cairo_get_group_target' => ['', 'context'=>'cairocontext'],
'cairo_get_line_cap' => ['int', 'context'=>'cairocontext'],
'cairo_get_line_join' => ['int', 'context'=>'cairocontext'],
'cairo_get_line_width' => ['float', 'context'=>'cairocontext'],
'cairo_get_matrix' => ['', 'context'=>'cairocontext'],
'cairo_get_miter_limit' => ['float', 'context'=>'cairocontext'],
'cairo_get_operator' => ['int', 'context'=>'cairocontext'],
'cairo_get_scaled_font' => ['', 'context'=>'cairocontext'],
'cairo_get_source' => ['', 'context'=>'cairocontext'],
'cairo_get_target' => ['', 'context'=>'cairocontext'],
'cairo_get_tolerance' => ['float', 'context'=>'cairocontext'],
'cairo_glyph_path' => ['', 'glyphs'=>'array', 'context'=>'cairocontext'],
'cairo_has_current_point' => ['bool', 'context'=>'cairocontext'],
'cairo_identity_matrix' => ['', 'context'=>'cairocontext'],
'cairo_image_surface_create' => ['CairoImageSurface', 'format'=>'int', 'width'=>'int', 'height'=>'int'],
'cairo_image_surface_create_for_data' => ['CairoImageSurface', 'data'=>'string', 'format'=>'int', 'width'=>'int', 'height'=>'int', 'stride='=>'int'],
'cairo_image_surface_create_from_png' => ['CairoImageSurface', 'file'=>'string'],
'cairo_image_surface_get_data' => ['string', 'surface'=>'cairoimagesurface'],
'cairo_image_surface_get_format' => ['int', 'surface'=>'cairoimagesurface'],
'cairo_image_surface_get_height' => ['int', 'surface'=>'cairoimagesurface'],
'cairo_image_surface_get_stride' => ['int', 'surface'=>'cairoimagesurface'],
'cairo_image_surface_get_width' => ['int', 'surface'=>'cairoimagesurface'],
'cairo_in_fill' => ['bool', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'cairo_in_stroke' => ['bool', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'cairo_line_to' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'cairo_mask' => ['', 'pattern'=>'cairopattern', 'context'=>'cairocontext'],
'cairo_mask_surface' => ['', 'surface'=>'cairosurface', 'x='=>'string', 'y='=>'string', 'context='=>'cairocontext'],
'cairo_matrix_create_scale' => ['object', 'sx'=>'float', 'sy'=>'float'],
'cairo_matrix_init' => ['object', 'xx='=>'float', 'yx='=>'float', 'xy='=>'float', 'yy='=>'float', 'x0='=>'float', 'y0='=>'float'],
'cairo_matrix_init_identity' => ['object'],
'cairo_matrix_init_rotate' => ['object', 'radians'=>'float'],
'cairo_matrix_init_scale' => ['object', 'sx'=>'float', 'sy'=>'float'],
'cairo_matrix_init_translate' => ['object', 'tx'=>'float', 'ty'=>'float'],
'cairo_matrix_invert' => ['void', 'matrix'=>'cairomatrix'],
'cairo_matrix_multiply' => ['CairoMatrix', 'matrix1'=>'cairomatrix', 'matrix2'=>'cairomatrix'],
'cairo_matrix_rotate' => ['', 'matrix'=>'cairomatrix', 'radians'=>'float'],
'cairo_matrix_scale' => ['', 'sx'=>'float', 'sy'=>'float', 'context'=>'cairocontext'],
'cairo_matrix_transform_distance' => ['array', 'matrix'=>'cairomatrix', 'dx'=>'float', 'dy'=>'float'],
'cairo_matrix_transform_point' => ['array', 'matrix'=>'cairomatrix', 'dx'=>'float', 'dy'=>'float'],
'cairo_matrix_translate' => ['void', 'matrix'=>'cairomatrix', 'tx'=>'float', 'ty'=>'float'],
'cairo_move_to' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'cairo_new_path' => ['', 'context'=>'cairocontext'],
'cairo_new_sub_path' => ['', 'context'=>'cairocontext'],
'cairo_paint' => ['', 'context'=>'cairocontext'],
'cairo_paint_with_alpha' => ['', 'alpha'=>'string', 'context'=>'cairocontext'],
'cairo_path_extents' => ['array', 'context'=>'cairocontext'],
'cairo_pattern_add_color_stop_rgb' => ['void', 'pattern'=>'cairogradientpattern', 'offset'=>'float', 'red'=>'float', 'green'=>'float', 'blue'=>'float'],
'cairo_pattern_add_color_stop_rgba' => ['void', 'pattern'=>'cairogradientpattern', 'offset'=>'float', 'red'=>'float', 'green'=>'float', 'blue'=>'float', 'alpha'=>'float'],
'cairo_pattern_create_for_surface' => ['CairoPattern', 'surface'=>'cairosurface'],
'cairo_pattern_create_linear' => ['CairoPattern', 'x0'=>'float', 'y0'=>'float', 'x1'=>'float', 'y1'=>'float'],
'cairo_pattern_create_radial' => ['CairoPattern', 'x0'=>'float', 'y0'=>'float', 'r0'=>'float', 'x1'=>'float', 'y1'=>'float', 'r1'=>'float'],
'cairo_pattern_create_rgb' => ['CairoPattern', 'red'=>'float', 'green'=>'float', 'blue'=>'float'],
'cairo_pattern_create_rgba' => ['CairoPattern', 'red'=>'float', 'green'=>'float', 'blue'=>'float', 'alpha'=>'float'],
'cairo_pattern_get_color_stop_count' => ['int', 'pattern'=>'cairogradientpattern'],
'cairo_pattern_get_color_stop_rgba' => ['array', 'pattern'=>'cairogradientpattern', 'index'=>'int'],
'cairo_pattern_get_extend' => ['int', 'pattern'=>'string'],
'cairo_pattern_get_filter' => ['int', 'pattern'=>'cairosurfacepattern'],
'cairo_pattern_get_linear_points' => ['array', 'pattern'=>'cairolineargradient'],
'cairo_pattern_get_matrix' => ['CairoMatrix', 'pattern'=>'cairopattern'],
'cairo_pattern_get_radial_circles' => ['array', 'pattern'=>'cairoradialgradient'],
'cairo_pattern_get_rgba' => ['array', 'pattern'=>'cairosolidpattern'],
'cairo_pattern_get_surface' => ['CairoSurface', 'pattern'=>'cairosurfacepattern'],
'cairo_pattern_get_type' => ['int', 'pattern'=>'cairopattern'],
'cairo_pattern_set_extend' => ['void', 'pattern'=>'string', 'extend'=>'string'],
'cairo_pattern_set_filter' => ['void', 'pattern'=>'cairosurfacepattern', 'filter'=>'int'],
'cairo_pattern_set_matrix' => ['void', 'pattern'=>'cairopattern', 'matrix'=>'cairomatrix'],
'cairo_pattern_status' => ['int', 'pattern'=>'cairopattern'],
'cairo_pdf_surface_create' => ['CairoPdfSurface', 'file'=>'string', 'width'=>'float', 'height'=>'float'],
'cairo_pdf_surface_set_size' => ['void', 'surface'=>'cairopdfsurface', 'width'=>'float', 'height'=>'float'],
'cairo_pop_group' => ['', 'context'=>'cairocontext'],
'cairo_pop_group_to_source' => ['', 'context'=>'cairocontext'],
'cairo_ps_get_levels' => ['array'],
'cairo_ps_level_to_string' => ['string', 'level'=>'int'],
'cairo_ps_surface_create' => ['CairoPsSurface', 'file'=>'string', 'width'=>'float', 'height'=>'float'],
'cairo_ps_surface_dsc_begin_page_setup' => ['void', 'surface'=>'cairopssurface'],
'cairo_ps_surface_dsc_begin_setup' => ['void', 'surface'=>'cairopssurface'],
'cairo_ps_surface_dsc_comment' => ['void', 'surface'=>'cairopssurface', 'comment'=>'string'],
'cairo_ps_surface_get_eps' => ['bool', 'surface'=>'cairopssurface'],
'cairo_ps_surface_restrict_to_level' => ['void', 'surface'=>'cairopssurface', 'level'=>'int'],
'cairo_ps_surface_set_eps' => ['void', 'surface'=>'cairopssurface', 'level'=>'bool'],
'cairo_ps_surface_set_size' => ['void', 'surface'=>'cairopssurface', 'width'=>'float', 'height'=>'float'],
'cairo_push_group' => ['', 'context'=>'cairocontext'],
'cairo_push_group_with_content' => ['', 'content'=>'string', 'context'=>'cairocontext'],
'cairo_rectangle' => ['', 'x'=>'string', 'y'=>'string', 'width'=>'string', 'height'=>'string', 'context'=>'cairocontext'],
'cairo_rel_curve_to' => ['', 'x1'=>'string', 'y1'=>'string', 'x2'=>'string', 'y2'=>'string', 'x3'=>'string', 'y3'=>'string', 'context'=>'cairocontext'],
'cairo_rel_line_to' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'cairo_rel_move_to' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'cairo_reset_clip' => ['', 'context'=>'cairocontext'],
'cairo_restore' => ['', 'context'=>'cairocontext'],
'cairo_rotate' => ['', 'sx'=>'string', 'sy'=>'string', 'context'=>'cairocontext', 'angle'=>'string'],
'cairo_save' => ['', 'context'=>'cairocontext'],
'cairo_scale' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'cairo_scaled_font_create' => ['CairoScaledFont', 'fontface'=>'cairofontface', 'matrix'=>'cairomatrix', 'ctm'=>'cairomatrix', 'fontoptions'=>'cairofontoptions'],
'cairo_scaled_font_extents' => ['array', 'scaledfont'=>'cairoscaledfont'],
'cairo_scaled_font_get_ctm' => ['CairoMatrix', 'scaledfont'=>'cairoscaledfont'],
'cairo_scaled_font_get_font_face' => ['CairoFontFace', 'scaledfont'=>'cairoscaledfont'],
'cairo_scaled_font_get_font_matrix' => ['CairoFontOptions', 'scaledfont'=>'cairoscaledfont'],
'cairo_scaled_font_get_font_options' => ['CairoFontOptions', 'scaledfont'=>'cairoscaledfont'],
'cairo_scaled_font_get_scale_matrix' => ['CairoMatrix', 'scaledfont'=>'cairoscaledfont'],
'cairo_scaled_font_get_type' => ['int', 'scaledfont'=>'cairoscaledfont'],
'cairo_scaled_font_glyph_extents' => ['array', 'scaledfont'=>'cairoscaledfont', 'glyphs'=>'array'],
'cairo_scaled_font_status' => ['int', 'scaledfont'=>'cairoscaledfont'],
'cairo_scaled_font_text_extents' => ['array', 'scaledfont'=>'cairoscaledfont', 'text'=>'string'],
'cairo_select_font_face' => ['', 'family'=>'string', 'slant='=>'string', 'weight='=>'string', 'context='=>'cairocontext'],
'cairo_set_antialias' => ['', 'antialias='=>'string', 'context='=>'cairocontext'],
'cairo_set_dash' => ['', 'dashes'=>'array', 'offset='=>'string', 'context='=>'cairocontext'],
'cairo_set_fill_rule' => ['', 'setting'=>'string', 'context'=>'cairocontext'],
'cairo_set_font_face' => ['', 'fontface'=>'cairofontface', 'context'=>'cairocontext'],
'cairo_set_font_matrix' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'],
'cairo_set_font_options' => ['', 'fontoptions'=>'cairofontoptions', 'context'=>'cairocontext'],
'cairo_set_font_size' => ['', 'size'=>'string', 'context'=>'cairocontext'],
'cairo_set_line_cap' => ['', 'setting'=>'string', 'context'=>'cairocontext'],
'cairo_set_line_join' => ['', 'setting'=>'string', 'context'=>'cairocontext'],
'cairo_set_line_width' => ['', 'width'=>'string', 'context'=>'cairocontext'],
'cairo_set_matrix' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'],
'cairo_set_miter_limit' => ['', 'limit'=>'string', 'context'=>'cairocontext'],
'cairo_set_operator' => ['', 'setting'=>'string', 'context'=>'cairocontext'],
'cairo_set_scaled_font' => ['', 'scaledfont'=>'cairoscaledfont', 'context'=>'cairocontext'],
'cairo_set_source' => ['', 'red'=>'string', 'green'=>'string', 'blue'=>'string', 'alpha'=>'string', 'context'=>'cairocontext', 'pattern'=>'cairopattern'],
'cairo_set_source_surface' => ['', 'surface'=>'cairosurface', 'x='=>'string', 'y='=>'string', 'context='=>'cairocontext'],
'cairo_set_tolerance' => ['', 'tolerance'=>'string', 'context'=>'cairocontext'],
'cairo_show_page' => ['', 'context'=>'cairocontext'],
'cairo_show_text' => ['', 'text'=>'string', 'context'=>'cairocontext'],
'cairo_status' => ['int', 'context'=>'cairocontext'],
'cairo_status_to_string' => ['string', 'status'=>'int'],
'cairo_stroke' => ['', 'context'=>'cairocontext'],
'cairo_stroke_extents' => ['array', 'context'=>'cairocontext'],
'cairo_stroke_preserve' => ['', 'context'=>'cairocontext'],
'cairo_surface_copy_page' => ['void', 'surface'=>'cairosurface'],
'cairo_surface_create_similar' => ['CairoSurface', 'surface'=>'cairosurface', 'content'=>'int', 'width'=>'float', 'height'=>'float'],
'cairo_surface_finish' => ['void', 'surface'=>'cairosurface'],
'cairo_surface_flush' => ['void', 'surface'=>'cairosurface'],
'cairo_surface_get_content' => ['int', 'surface'=>'cairosurface'],
'cairo_surface_get_device_offset' => ['array', 'surface'=>'cairosurface'],
'cairo_surface_get_font_options' => ['CairoFontOptions', 'surface'=>'cairosurface'],
'cairo_surface_get_type' => ['int', 'surface'=>'cairosurface'],
'cairo_surface_mark_dirty' => ['void', 'surface'=>'cairosurface'],
'cairo_surface_mark_dirty_rectangle' => ['void', 'surface'=>'cairosurface', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'],
'cairo_surface_set_device_offset' => ['void', 'surface'=>'cairosurface', 'x'=>'float', 'y'=>'float'],
'cairo_surface_set_fallback_resolution' => ['void', 'surface'=>'cairosurface', 'x'=>'float', 'y'=>'float'],
'cairo_surface_show_page' => ['void', 'surface'=>'cairosurface'],
'cairo_surface_status' => ['int', 'surface'=>'cairosurface'],
'cairo_surface_write_to_png' => ['void', 'surface'=>'cairosurface', 'stream'=>'resource'],
'cairo_svg_get_versions' => ['array'],
'cairo_svg_surface_create' => ['CairoSvgSurface', 'file'=>'string', 'width'=>'float', 'height'=>'float'],
'cairo_svg_surface_get_versions' => ['array'],
'cairo_svg_surface_restrict_to_version' => ['void', 'surface'=>'cairosvgsurface', 'version'=>'int'],
'cairo_svg_version_to_string' => ['string', 'version'=>'int'],
'cairo_text_extents' => ['array', 'text'=>'string', 'context'=>'cairocontext'],
'cairo_text_path' => ['', 'string'=>'string', 'context'=>'cairocontext', 'text'=>'string'],
'cairo_transform' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'],
'cairo_translate' => ['', 'tx'=>'string', 'ty'=>'string', 'context'=>'cairocontext', 'x'=>'string', 'y'=>'string'],
'cairo_user_to_device' => ['array', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'cairo_user_to_device_distance' => ['array', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'cairo_version' => ['int'],
'cairo_version_string' => ['string'],
'CairoContext::__construct' => ['void', 'surface'=>'CairoSurface'],
'CairoContext::appendPath' => ['', 'path'=>'cairopath', 'context'=>'cairocontext'],
'CairoContext::arc' => ['', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'angle1'=>'float', 'angle2'=>'float', 'context'=>'cairocontext'],
'CairoContext::arcNegative' => ['', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'angle1'=>'float', 'angle2'=>'float', 'context'=>'cairocontext'],
'CairoContext::clip' => ['', 'context'=>'cairocontext'],
'CairoContext::clipExtents' => ['array', 'context'=>'cairocontext'],
'CairoContext::clipPreserve' => ['', 'context'=>'cairocontext'],
'CairoContext::clipRectangleList' => ['array', 'context'=>'cairocontext'],
'CairoContext::closePath' => ['', 'context'=>'cairocontext'],
'CairoContext::copyPage' => ['', 'context'=>'cairocontext'],
'CairoContext::copyPath' => ['CairoPath', 'context'=>'cairocontext'],
'CairoContext::copyPathFlat' => ['CairoPath', 'context'=>'cairocontext'],
'CairoContext::curveTo' => ['', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float', 'context'=>'cairocontext'],
'CairoContext::deviceToUser' => ['array', 'x'=>'float', 'y'=>'float', 'context'=>'cairocontext'],
'CairoContext::deviceToUserDistance' => ['array', 'x'=>'float', 'y'=>'float', 'context'=>'cairocontext'],
'CairoContext::fill' => ['', 'context'=>'cairocontext'],
'CairoContext::fillExtents' => ['array', 'context'=>'cairocontext'],
'CairoContext::fillPreserve' => ['', 'context'=>'cairocontext'],
'CairoContext::fontExtents' => ['array', 'context'=>'cairocontext'],
'CairoContext::getAntialias' => ['int', 'context'=>'cairocontext'],
'CairoContext::getCurrentPoint' => ['array', 'context'=>'cairocontext'],
'CairoContext::getDash' => ['array', 'context'=>'cairocontext'],
'CairoContext::getDashCount' => ['int', 'context'=>'cairocontext'],
'CairoContext::getFillRule' => ['int', 'context'=>'cairocontext'],
'CairoContext::getFontFace' => ['', 'context'=>'cairocontext'],
'CairoContext::getFontMatrix' => ['', 'context'=>'cairocontext'],
'CairoContext::getFontOptions' => ['', 'context'=>'cairocontext'],
'CairoContext::getGroupTarget' => ['', 'context'=>'cairocontext'],
'CairoContext::getLineCap' => ['int', 'context'=>'cairocontext'],
'CairoContext::getLineJoin' => ['int', 'context'=>'cairocontext'],
'CairoContext::getLineWidth' => ['float', 'context'=>'cairocontext'],
'CairoContext::getMatrix' => ['', 'context'=>'cairocontext'],
'CairoContext::getMiterLimit' => ['float', 'context'=>'cairocontext'],
'CairoContext::getOperator' => ['int', 'context'=>'cairocontext'],
'CairoContext::getScaledFont' => ['', 'context'=>'cairocontext'],
'CairoContext::getSource' => ['', 'context'=>'cairocontext'],
'CairoContext::getTarget' => ['', 'context'=>'cairocontext'],
'CairoContext::getTolerance' => ['float', 'context'=>'cairocontext'],
'CairoContext::glyphPath' => ['', 'glyphs'=>'array', 'context'=>'cairocontext'],
'CairoContext::hasCurrentPoint' => ['bool', 'context'=>'cairocontext'],
'CairoContext::identityMatrix' => ['', 'context'=>'cairocontext'],
'CairoContext::inFill' => ['bool', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'CairoContext::inStroke' => ['bool', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'CairoContext::lineTo' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'CairoContext::mask' => ['', 'pattern'=>'cairopattern', 'context'=>'cairocontext'],
'CairoContext::maskSurface' => ['', 'surface'=>'cairosurface', 'x='=>'string', 'y='=>'string', 'context='=>'cairocontext'],
'CairoContext::moveTo' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'CairoContext::newPath' => ['', 'context'=>'cairocontext'],
'CairoContext::newSubPath' => ['', 'context'=>'cairocontext'],
'CairoContext::paint' => ['', 'context'=>'cairocontext'],
'CairoContext::paintWithAlpha' => ['', 'alpha'=>'string', 'context'=>'cairocontext'],
'CairoContext::pathExtents' => ['array', 'context'=>'cairocontext'],
'CairoContext::popGroup' => ['', 'context'=>'cairocontext'],
'CairoContext::popGroupToSource' => ['', 'context'=>'cairocontext'],
'CairoContext::pushGroup' => ['', 'context'=>'cairocontext'],
'CairoContext::pushGroupWithContent' => ['', 'content'=>'string', 'context'=>'cairocontext'],
'CairoContext::rectangle' => ['', 'x'=>'string', 'y'=>'string', 'width'=>'string', 'height'=>'string', 'context'=>'cairocontext'],
'CairoContext::relCurveTo' => ['', 'x1'=>'string', 'y1'=>'string', 'x2'=>'string', 'y2'=>'string', 'x3'=>'string', 'y3'=>'string', 'context'=>'cairocontext'],
'CairoContext::relLineTo' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'CairoContext::relMoveTo' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'CairoContext::resetClip' => ['', 'context'=>'cairocontext'],
'CairoContext::restore' => ['', 'context'=>'cairocontext'],
'CairoContext::rotate' => ['', 'angle'=>'string', 'context'=>'cairocontext'],
'CairoContext::save' => ['', 'context'=>'cairocontext'],
'CairoContext::scale' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'CairoContext::selectFontFace' => ['', 'family'=>'string', 'slant='=>'string', 'weight='=>'string', 'context='=>'cairocontext'],
'CairoContext::setAntialias' => ['', 'antialias='=>'string', 'context='=>'cairocontext'],
'CairoContext::setDash' => ['', 'dashes'=>'array', 'offset='=>'string', 'context='=>'cairocontext'],
'CairoContext::setFillRule' => ['', 'setting'=>'string', 'context'=>'cairocontext'],
'CairoContext::setFontFace' => ['', 'fontface'=>'cairofontface', 'context'=>'cairocontext'],
'CairoContext::setFontMatrix' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'],
'CairoContext::setFontOptions' => ['', 'fontoptions'=>'cairofontoptions', 'context'=>'cairocontext'],
'CairoContext::setFontSize' => ['', 'size'=>'string', 'context'=>'cairocontext'],
'CairoContext::setLineCap' => ['', 'setting'=>'string', 'context'=>'cairocontext'],
'CairoContext::setLineJoin' => ['', 'setting'=>'string', 'context'=>'cairocontext'],
'CairoContext::setLineWidth' => ['', 'width'=>'string', 'context'=>'cairocontext'],
'CairoContext::setMatrix' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'],
'CairoContext::setMiterLimit' => ['', 'limit'=>'string', 'context'=>'cairocontext'],
'CairoContext::setOperator' => ['', 'setting'=>'string', 'context'=>'cairocontext'],
'CairoContext::setScaledFont' => ['', 'scaledfont'=>'cairoscaledfont', 'context'=>'cairocontext'],
'CairoContext::setSource' => ['', 'pattern'=>'cairopattern', 'context'=>'cairocontext'],
'CairoContext::setSourceRGB' => ['', 'red'=>'string', 'green'=>'string', 'blue'=>'string', 'context'=>'cairocontext', 'pattern'=>'cairopattern'],
'CairoContext::setSourceRGBA' => ['', 'red'=>'string', 'green'=>'string', 'blue'=>'string', 'alpha'=>'string', 'context'=>'cairocontext', 'pattern'=>'cairopattern'],
'CairoContext::setSourceSurface' => ['', 'surface'=>'cairosurface', 'x='=>'string', 'y='=>'string', 'context='=>'cairocontext'],
'CairoContext::setTolerance' => ['', 'tolerance'=>'string', 'context'=>'cairocontext'],
'CairoContext::showPage' => ['', 'context'=>'cairocontext'],
'CairoContext::showText' => ['', 'text'=>'string', 'context'=>'cairocontext'],
'CairoContext::status' => ['int', 'context'=>'cairocontext'],
'CairoContext::stroke' => ['', 'context'=>'cairocontext'],
'CairoContext::strokeExtents' => ['array', 'context'=>'cairocontext'],
'CairoContext::strokePreserve' => ['', 'context'=>'cairocontext'],
'CairoContext::textExtents' => ['array', 'text'=>'string', 'context'=>'cairocontext'],
'CairoContext::textPath' => ['', 'string'=>'string', 'context'=>'cairocontext', 'text'=>'string'],
'CairoContext::transform' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'],
'CairoContext::translate' => ['', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'CairoContext::userToDevice' => ['array', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'CairoContext::userToDeviceDistance' => ['array', 'x'=>'string', 'y'=>'string', 'context'=>'cairocontext'],
'CairoFontFace::__construct' => ['void'],
'CairoFontFace::getType' => ['int'],
'CairoFontFace::status' => ['int', 'fontface'=>'cairofontface'],
'CairoFontOptions::__construct' => ['void'],
'CairoFontOptions::equal' => ['bool', 'other'=>'string'],
'CairoFontOptions::getAntialias' => ['int', 'context'=>'cairocontext'],
'CairoFontOptions::getHintMetrics' => ['int'],
'CairoFontOptions::getHintStyle' => ['int'],
'CairoFontOptions::getSubpixelOrder' => ['int'],
'CairoFontOptions::hash' => ['int'],
'CairoFontOptions::merge' => ['void', 'other'=>'string'],
'CairoFontOptions::setAntialias' => ['', 'antialias='=>'string', 'context='=>'cairocontext'],
'CairoFontOptions::setHintMetrics' => ['void', 'hint_metrics'=>'string'],
'CairoFontOptions::setHintStyle' => ['void', 'hint_style'=>'string'],
'CairoFontOptions::setSubpixelOrder' => ['void', 'subpixel_order'=>'string'],
'CairoFontOptions::status' => ['int', 'context'=>'cairocontext'],
'CairoFormat::strideForWidth' => ['int', 'format'=>'int', 'width'=>'int'],
'CairoGradientPattern::addColorStopRgb' => ['void', 'offset'=>'string', 'red'=>'string', 'green'=>'string', 'blue'=>'string'],
'CairoGradientPattern::addColorStopRgba' => ['void', 'offset'=>'string', 'red'=>'string', 'green'=>'string', 'blue'=>'string', 'alpha'=>'string'],
'CairoGradientPattern::getColorStopCount' => ['int'],
'CairoGradientPattern::getColorStopRgba' => ['array', 'index'=>'string'],
'CairoGradientPattern::getExtend' => ['int'],
'CairoGradientPattern::setExtend' => ['void', 'extend'=>'int'],
'CairoImageSurface::__construct' => ['void', 'format'=>'int', 'width'=>'int', 'height'=>'int'],
'CairoImageSurface::createForData' => ['void', 'data'=>'string', 'format'=>'int', 'width'=>'int', 'height'=>'int', 'stride='=>'int'],
'CairoImageSurface::createFromPng' => ['CairoImageSurface', 'file'=>'string'],
'CairoImageSurface::getData' => ['string'],
'CairoImageSurface::getFormat' => ['int'],
'CairoImageSurface::getHeight' => ['int'],
'CairoImageSurface::getStride' => ['int'],
'CairoImageSurface::getWidth' => ['int'],
'CairoLinearGradient::__construct' => ['void', 'x0'=>'float', 'y0'=>'float', 'x1'=>'float', 'y1'=>'float'],
'CairoLinearGradient::getPoints' => ['array'],
'CairoMatrix::__construct' => ['void', 'xx='=>'float', 'yx='=>'float', 'xy='=>'float', 'yy='=>'float', 'x0='=>'float', 'y0='=>'float'],
'CairoMatrix::initIdentity' => ['object'],
'CairoMatrix::initRotate' => ['object', 'radians'=>'float'],
'CairoMatrix::initScale' => ['object', 'sx'=>'float', 'sy'=>'float'],
'CairoMatrix::initTranslate' => ['object', 'tx'=>'float', 'ty'=>'float'],
'CairoMatrix::invert' => ['void'],
'CairoMatrix::multiply' => ['CairoMatrix', 'matrix1'=>'cairomatrix', 'matrix2'=>'cairomatrix'],
'CairoMatrix::rotate' => ['', 'sx'=>'string', 'sy'=>'string', 'context'=>'cairocontext', 'angle'=>'string'],
'CairoMatrix::scale' => ['', 'sx'=>'float', 'sy'=>'float', 'context'=>'cairocontext'],
'CairoMatrix::transformDistance' => ['array', 'dx'=>'string', 'dy'=>'string'],
'CairoMatrix::transformPoint' => ['array', 'dx'=>'string', 'dy'=>'string'],
'CairoMatrix::translate' => ['', 'tx'=>'string', 'ty'=>'string', 'context'=>'cairocontext', 'x'=>'string', 'y'=>'string'],
'CairoPattern::__construct' => ['void'],
'CairoPattern::getMatrix' => ['', 'context'=>'cairocontext'],
'CairoPattern::getType' => ['int'],
'CairoPattern::setMatrix' => ['', 'matrix'=>'cairomatrix', 'context'=>'cairocontext'],
'CairoPattern::status' => ['int', 'context'=>'cairocontext'],
'CairoPdfSurface::__construct' => ['void', 'file'=>'string', 'width'=>'float', 'height'=>'float'],
'CairoPdfSurface::setSize' => ['void', 'width'=>'string', 'height'=>'string'],
'CairoPsSurface::__construct' => ['void', 'file'=>'string', 'width'=>'float', 'height'=>'float'],
'CairoPsSurface::dscBeginPageSetup' => ['void'],
'CairoPsSurface::dscBeginSetup' => ['void'],
'CairoPsSurface::dscComment' => ['void', 'comment'=>'string'],
'CairoPsSurface::getEps' => ['bool'],
'CairoPsSurface::getLevels' => ['array'],
'CairoPsSurface::levelToString' => ['string', 'level'=>'int'],
'CairoPsSurface::restrictToLevel' => ['void', 'level'=>'string'],
'CairoPsSurface::setEps' => ['void', 'level'=>'string'],
'CairoPsSurface::setSize' => ['void', 'width'=>'string', 'height'=>'string'],
'CairoRadialGradient::__construct' => ['void', 'x0'=>'float', 'y0'=>'float', 'r0'=>'float', 'x1'=>'float', 'y1'=>'float', 'r1'=>'float'],
'CairoRadialGradient::getCircles' => ['array'],
'CairoScaledFont::__construct' => ['void', 'font_face'=>'CairoFontFace', 'matrix'=>'CairoMatrix', 'ctm'=>'CairoMatrix', 'options'=>'CairoFontOptions'],
'CairoScaledFont::extents' => ['array'],
'CairoScaledFont::getCtm' => ['CairoMatrix'],
'CairoScaledFont::getFontFace' => ['', 'context'=>'cairocontext'],
'CairoScaledFont::getFontMatrix' => ['', 'context'=>'cairocontext'],
'CairoScaledFont::getFontOptions' => ['', 'context'=>'cairocontext'],
'CairoScaledFont::getScaleMatrix' => ['void'],
'CairoScaledFont::getType' => ['int'],
'CairoScaledFont::glyphExtents' => ['array', 'glyphs'=>'string'],
'CairoScaledFont::status' => ['int', 'context'=>'cairocontext'],
'CairoScaledFont::textExtents' => ['array', 'text'=>'string', 'context'=>'cairocontext'],
'CairoSolidPattern::__construct' => ['void', 'red'=>'float', 'green'=>'float', 'blue'=>'float', 'alpha='=>'float'],
'CairoSolidPattern::getRgba' => ['array'],
'CairoSurface::__construct' => ['void'],
'CairoSurface::copyPage' => ['', 'context'=>'cairocontext'],
'CairoSurface::createSimilar' => ['void', 'other'=>'cairosurface', 'content'=>'int', 'width'=>'string', 'height'=>'string'],
'CairoSurface::finish' => ['void'],
'CairoSurface::flush' => ['void'],
'CairoSurface::getContent' => ['int'],
'CairoSurface::getDeviceOffset' => ['array'],
'CairoSurface::getFontOptions' => ['', 'context'=>'cairocontext'],
'CairoSurface::getType' => ['int'],
'CairoSurface::markDirty' => ['void'],
'CairoSurface::markDirtyRectangle' => ['void', 'x'=>'string', 'y'=>'string', 'width'=>'string', 'height'=>'string'],
'CairoSurface::setDeviceOffset' => ['void', 'x'=>'string', 'y'=>'string'],
'CairoSurface::setFallbackResolution' => ['void', 'x'=>'string', 'y'=>'string'],
'CairoSurface::showPage' => ['', 'context'=>'cairocontext'],
'CairoSurface::status' => ['int', 'context'=>'cairocontext'],
'CairoSurface::writeToPng' => ['void', 'file'=>'string'],
'CairoSurfacePattern::__construct' => ['void', 'surface'=>'CairoSurface'],
'CairoSurfacePattern::getExtend' => ['int'],
'CairoSurfacePattern::getFilter' => ['int'],
'CairoSurfacePattern::getSurface' => ['void'],
'CairoSurfacePattern::setExtend' => ['void', 'extend'=>'int'],
'CairoSurfacePattern::setFilter' => ['void', 'filter'=>'string'],
'CairoSvgSurface::__construct' => ['void', 'file'=>'string', 'width'=>'float', 'height'=>'float'],
'CairoSvgSurface::getVersions' => ['array'],
'CairoSvgSurface::restrictToVersion' => ['void', 'version'=>'string'],
'CairoSvgSurface::versionToString' => ['string', 'version'=>'int'],
'cal_days_in_month' => ['int', 'calendar'=>'int', 'month'=>'int', 'year'=>'int'],
'cal_from_jd' => ['array', 'jd'=>'int', 'calendar'=>'int'],
'cal_info' => ['array', 'calendar='=>'int'],
'cal_to_jd' => ['int', 'calendar'=>'int', 'month'=>'int', 'day'=>'int', 'year'=>'int'],
'calcul_hmac' => ['string', 'clent'=>'string', 'siretcode'=>'string', 'price'=>'string', 'reference'=>'string', 'validity'=>'string', 'taxation'=>'string', 'devise'=>'string', 'language'=>'string'],
'calculhmac' => ['string', 'clent'=>'string', 'data'=>'string'],
'call_user_func' => ['mixed', 'function'=>'callable', '...parameters='=>'mixed'],
'call_user_func_array' => ['mixed', 'function'=>'callable', 'parameters'=>'array<int,mixed>'],
'call_user_method' => ['mixed', 'method_name'=>'string', 'obj'=>'object', 'parameter='=>'mixed', '...args='=>'mixed'],
'call_user_method_array' => ['mixed', 'method_name'=>'string', 'obj'=>'object', 'params'=>'array<int,mixed>'],
'CallbackFilterIterator::__construct' => ['void', 'it'=>'iterator', 'func'=>'callable(mixed):bool|callable(mixed,mixed):bool|callable(mixed,mixed,mixed):bool'],
'CallbackFilterIterator::accept' => ['bool'],
'CallbackFilterIterator::current' => ['mixed'],
'CallbackFilterIterator::getInnerIterator' => ['iterator'],
'CallbackFilterIterator::key' => ['int|string|float|bool'],
'CallbackFilterIterator::next' => ['void'],
'CallbackFilterIterator::rewind' => ['void'],
'CallbackFilterIterator::valid' => ['bool'],
'ceil' => ['float|int', 'number'=>'float'],
'chdb::__construct' => ['void', 'pathname'=>'string'],
'chdb::get' => ['string', 'key'=>'string'],
'chdb_create' => ['bool', 'pathname'=>'string', 'data'=>'array'],
'chdir' => ['bool', 'directory'=>'string'],
'checkdate' => ['bool', 'month'=>'int', 'day'=>'int', 'year'=>'int'],
'checkdnsrr' => ['bool', 'host'=>'string', 'type='=>'string'],
'chgrp' => ['bool', 'filename'=>'string', 'group'=>'string|int'],
'chmod' => ['bool', 'filename'=>'string', 'mode'=>'int'],
'chop' => ['string', 'str'=>'string', 'character_mask='=>'string'],
'chown' => ['bool', 'filename'=>'string', 'user'=>'string|int'],
'chr' => ['string', 'ascii'=>'int'],
'chroot' => ['bool', 'directory'=>'string'],
'chunk_split' => ['string', 'str'=>'string', 'chunklen='=>'int', 'ending='=>'string'],
'class_alias' => ['bool', 'user_class_name'=>'string', 'alias_name'=>'string', 'autoload='=>'bool'],
'class_exists' => ['bool', 'classname'=>'string', 'autoload='=>'bool'],
'class_implements' => ['array<string,string>', 'what'=>'object|string', 'autoload='=>'bool'],
'class_parents' => ['array<string,string>', 'instance'=>'object|string', 'autoload='=>'bool'],
'class_uses' => ['array<string,string>', 'what'=>'object|string', 'autoload='=>'bool'],
'classkit_import' => ['array', 'filename'=>'string'],
'classkit_method_add' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'args'=>'string', 'code'=>'string', 'flags='=>'int'],
'classkit_method_copy' => ['bool', 'dclass'=>'string', 'dmethod'=>'string', 'sclass'=>'string', 'smethod='=>'string'],
'classkit_method_redefine' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'args'=>'string', 'code'=>'string', 'flags='=>'int'],
'classkit_method_remove' => ['bool', 'classname'=>'string', 'methodname'=>'string'],
'classkit_method_rename' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'newname'=>'string'],
'classObj::__construct' => ['void', 'layer'=>'layerObj', 'class'=>'classObj'],
'classObj::addLabel' => ['int', 'label'=>'labelObj'],
'classObj::convertToString' => ['string'],
'classObj::createLegendIcon' => ['imageObj', 'width'=>'int', 'height'=>'int'],
'classObj::deletestyle' => ['int', 'index'=>'int'],
'classObj::drawLegendIcon' => ['int', 'width'=>'int', 'height'=>'int', 'im'=>'imageObj', 'dstX'=>'int', 'dstY'=>'int'],
'classObj::free' => ['void'],
'classObj::getExpressionString' => ['string'],
'classObj::getLabel' => ['labelObj', 'index'=>'int'],
'classObj::getMetaData' => ['int', 'name'=>'string'],
'classObj::getStyle' => ['styleObj', 'index'=>'int'],
'classObj::getTextString' => ['string'],
'classObj::movestyledown' => ['int', 'index'=>'int'],
'classObj::movestyleup' => ['int', 'index'=>'int'],
'classObj::ms_newClassObj' => ['classObj', 'layer'=>'layerObj', 'class'=>'classObj'],
'classObj::removeLabel' => ['labelObj', 'index'=>'int'],
'classObj::removeMetaData' => ['int', 'name'=>'string'],
'classObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'classObj::setExpression' => ['int', 'expression'=>'string'],
'classObj::setMetaData' => ['int', 'name'=>'string', 'value'=>'string'],
'classObj::settext' => ['int', 'text'=>'string'],
'classObj::updateFromString' => ['int', 'snippet'=>'string'],
'clearstatcache' => ['void', 'clear_realpath_cache='=>'bool', 'filename='=>'string'],
'cli_get_process_title' => ['string'],
'cli_set_process_title' => ['bool', 'arg'=>'string'],
'ClosedGeneratorException::__clone' => ['void'],
'ClosedGeneratorException::__toString' => ['string'],
'ClosedGeneratorException::getCode' => ['int'],
'ClosedGeneratorException::getFile' => ['string'],
'ClosedGeneratorException::getLine' => ['int'],
'ClosedGeneratorException::getMessage' => ['string'],
'ClosedGeneratorException::getPrevious' => ['Throwable|ClosedGeneratorException'],
'ClosedGeneratorException::getTrace' => ['array'],
'ClosedGeneratorException::getTraceAsString' => ['string'],
'closedir' => ['void', 'dir_handle='=>'resource'],
'closelog' => ['bool'],
'Closure::__construct' => ['void'],
'Closure::__invoke' => ['', '...args='=>''],
'Closure::bind' => ['Closure', 'old'=>'Closure', 'to'=>'?object', 'scope='=>'object|string'],
'Closure::bindTo' => ['Closure', 'new'=>'?object', 'newscope='=>'object|string'],
'Closure::call' => ['', 'to'=>'object', '...parameters='=>''],
'Closure::fromCallable' => ['Closure', 'callable'=>'callable'],
'clusterObj::convertToString' => ['string'],
'clusterObj::getFilterString' => ['string'],
'clusterObj::getGroupString' => ['string'],
'clusterObj::setFilter' => ['int', 'expression'=>'string'],
'clusterObj::setGroup' => ['int', 'expression'=>'string'],
'Collator::__construct' => ['void', 'locale'=>'string'],
'Collator::asort' => ['bool', '&rw_arr'=>'array', 'sort_flag='=>'int'],
'Collator::compare' => ['int', 'str1'=>'string', 'str2'=>'string'],
'Collator::create' => ['Collator', 'locale'=>'string'],
'Collator::getAttribute' => ['int', 'attr'=>'int'],
'Collator::getErrorCode' => ['int'],
'Collator::getErrorMessage' => ['string'],
'Collator::getLocale' => ['string', 'type'=>'int'],
'Collator::getSortKey' => ['string', 'str'=>'string'],
'Collator::getStrength' => ['int'],
'Collator::setAttribute' => ['bool', 'attr'=>'int', 'val'=>'int'],
'Collator::setStrength' => ['bool', 'strength'=>'int'],
'Collator::sort' => ['bool', '&rw_arr'=>'array', 'sort_flags='=>'int'],
'Collator::sortWithSortKeys' => ['bool', '&rw_arr'=>'array'],
'collator_asort' => ['bool', 'coll'=>'collator', '&rw_arr'=>'array', 'sort_flag='=>'int'],
'collator_compare' => ['int', 'coll'=>'collator', 'str1'=>'string', 'str2'=>'string'],
'collator_create' => ['Collator', 'locale'=>'string'],
'collator_get_attribute' => ['int', 'coll'=>'collator', 'attr'=>'int'],
'collator_get_error_code' => ['int', 'coll'=>'collator'],
'collator_get_error_message' => ['string', 'coll'=>'collator'],
'collator_get_locale' => ['string', 'coll'=>'collator', 'type'=>'int'],
'collator_get_sort_key' => ['string', 'coll'=>'collator', 'str'=>'string'],
'collator_get_strength' => ['int', 'coll'=>'collator'],
'collator_set_attribute' => ['bool', 'coll'=>'collator', 'attr'=>'int', 'val'=>'int'],
'collator_set_strength' => ['bool', 'coll'=>'collator', 'strength'=>'int'],
'collator_sort' => ['bool', 'coll'=>'collator', '&rw_arr'=>'array', 'sort_flag='=>'int'],
'collator_sort_with_sort_keys' => ['bool', 'coll'=>'collator', '&rw_arr'=>'array'],
'Collectable::isGarbage' => ['bool'],
'Collectable::setGarbage' => ['void'],
'colorObj::setHex' => ['int', 'hex'=>'string'],
'colorObj::toHex' => ['string'],
'COM::__call' => ['', 'name'=>'', 'args'=>''],
'COM::__construct' => ['void', 'module_name'=>'string', 'server_name='=>'mixed', 'codepage='=>'int', 'typelib='=>'string'],
'COM::__get' => ['', 'name'=>''],
'COM::__set' => ['', 'name'=>'', 'value'=>''],
'com_addref' => [''],
'com_create_guid' => ['string'],
'com_event_sink' => ['bool', 'comobject'=>'object', 'sinkobject'=>'object', 'sinkinterface='=>'mixed'],
'com_get_active_object' => ['object', 'progid'=>'string', 'code_page='=>'int'],
'com_isenum' => ['bool', 'com_module'=>'variant'],
'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'int'],
'com_message_pump' => ['bool', 'timeoutms='=>'int'],
'com_print_typeinfo' => ['bool', 'comobject_or_typelib'=>'object', 'dispinterface='=>'string', 'wantsink='=>'bool'],
'com_release' => [''],
'compact' => ['array', '...var_names='=>'string|array'],
'COMPersistHelper::__construct' => ['void', 'com_object'=>'object'],
'COMPersistHelper::GetCurFile' => ['string'],
'COMPersistHelper::GetMaxStreamSize' => ['int'],
'COMPersistHelper::InitNew' => ['int'],
'COMPersistHelper::LoadFromFile' => ['bool', 'filename'=>'string', 'flags'=>'int'],
'COMPersistHelper::LoadFromStream' => ['', 'stream'=>''],
'COMPersistHelper::SaveToFile' => ['bool', 'filename'=>'string', 'remember'=>'bool'],
'COMPersistHelper::SaveToStream' => ['int', 'stream'=>''],
'componere\cast' => ['Type', 'arg1'=>'', 'object'=>''],
'componere\cast_by_ref' => ['Type', 'arg1'=>'', 'object'=>''],
'Cond::broadcast' => ['bool', 'condition'=>'long'],
'Cond::create' => ['long'],
'Cond::destroy' => ['bool', 'condition'=>'long'],
'Cond::signal' => ['bool', 'condition'=>'long'],
'Cond::wait' => ['bool', 'condition'=>'long', 'mutex'=>'long', 'timeout='=>'long'],
'confirm_pdo_ibm_compiled' => [''],
'connection_aborted' => ['int'],
'connection_status' => ['int'],
'connection_timeout' => ['int'],
'constant' => ['mixed', 'const_name'=>'string'],
'convert_cyr_string' => ['string', 'str'=>'string', 'from'=>'string', 'to'=>'string'],
'convert_uudecode' => ['string', 'data'=>'string'],
'convert_uuencode' => ['string', 'data'=>'string'],
'copy' => ['bool', 'source_file'=>'string', 'destination_file'=>'string', 'context='=>'resource'],
'cos' => ['float', 'number'=>'float'],
'cosh' => ['float', 'number'=>'float'],
'Couchbase\AnalyticsQuery::__construct' => ['void'],
'Couchbase\AnalyticsQuery::fromString' => ['Couchbase\AnalyticsQuery', 'statement'=>'string'],
'Couchbase\basicDecoderV1' => ['mixed', 'bytes'=>'string', 'flags'=>'int', 'datatype'=>'int', 'options'=>'array'],
'Couchbase\basicEncoderV1' => ['array', 'value'=>'mixed', 'options'=>'array'],
'Couchbase\BooleanFieldSearchQuery::__construct' => ['void'],
'Couchbase\BooleanFieldSearchQuery::boost' => ['Couchbase\BooleanFieldSearchQuery', 'boost'=>'float'],
'Couchbase\BooleanFieldSearchQuery::field' => ['Couchbase\BooleanFieldSearchQuery', 'field'=>'string'],
'Couchbase\BooleanFieldSearchQuery::jsonSerialize' => ['array'],
'Couchbase\BooleanSearchQuery::__construct' => ['void'],
'Couchbase\BooleanSearchQuery::boost' => ['Couchbase\BooleanSearchQuery', 'boost'=>'float'],
'Couchbase\BooleanSearchQuery::jsonSerialize' => ['array'],
'Couchbase\BooleanSearchQuery::must' => ['Couchbase\BooleanSearchQuery', '...queries='=>'array<int,Couchbase\SearchQueryPart>'],
'Couchbase\BooleanSearchQuery::mustNot' => ['Couchbase\BooleanSearchQuery', '...queries='=>'array<int,Couchbase\SearchQueryPart>'],
'Couchbase\BooleanSearchQuery::should' => ['Couchbase\BooleanSearchQuery', '...queries='=>'array<int,Couchbase\SearchQueryPart>'],
'Couchbase\Bucket::__construct' => ['void'],
'Couchbase\Bucket::__get' => ['int', 'name'=>'string'],
'Couchbase\Bucket::__set' => ['int', 'name'=>'string', 'value'=>'int'],
'Couchbase\Bucket::append' => ['Couchbase\Document|array', 'ids'=>'array|string', 'value'=>'mixed', 'options='=>'array'],
'Couchbase\Bucket::counter' => ['Couchbase\Document|array', 'ids'=>'array|string', 'delta='=>'int', 'options='=>'array'],
'Couchbase\Bucket::diag' => ['array', 'reportId='=>'string'],
'Couchbase\Bucket::get' => ['Couchbase\Document|array', 'ids'=>'array|string', 'options='=>'array'],
'Couchbase\Bucket::getAndLock' => ['Couchbase\Document|array', 'ids'=>'array|string', 'lockTime'=>'int', 'options='=>'array'],
'Couchbase\Bucket::getAndTouch' => ['Couchbase\Document|array', 'ids'=>'array|string', 'expiry'=>'int', 'options='=>'array'],
'Couchbase\Bucket::getFromReplica' => ['Couchbase\Document|array', 'ids'=>'array|string', 'options='=>'array'],
'Couchbase\Bucket::insert' => ['Couchbase\Document|array', 'ids'=>'array|string', 'value'=>'mixed', 'options='=>'array'],
'Couchbase\Bucket::listExists' => ['bool', 'id'=>'string', 'value'=>'mixed'],
'Couchbase\Bucket::listGet' => ['mixed', 'id'=>'string', 'index'=>'int'],
'Couchbase\Bucket::listPush' => ['', 'id'=>'string', 'value'=>'mixed'],
'Couchbase\Bucket::listRemove' => ['', 'id'=>'string', 'index'=>'int'],
'Couchbase\Bucket::listSet' => ['', 'id'=>'string', 'index'=>'int', 'value'=>'mixed'],
'Couchbase\Bucket::listShift' => ['', 'id'=>'string', 'value'=>'mixed'],
'Couchbase\Bucket::listSize' => ['int', 'id'=>'string'],
'Couchbase\Bucket::lookupIn' => ['Couchbase\LookupInBuilder', 'id'=>'string'],
'Couchbase\Bucket::manager' => ['Couchbase\BucketManager'],
'Couchbase\Bucket::mapAdd' => ['', 'id'=>'string', 'key'=>'string', 'value'=>'mixed'],
'Couchbase\Bucket::mapGet' => ['mixed', 'id'=>'string', 'key'=>'string'],
'Couchbase\Bucket::mapRemove' => ['', 'id'=>'string', 'key'=>'string'],
'Couchbase\Bucket::mapSize' => ['int', 'id'=>'string'],
'Couchbase\Bucket::mutateIn' => ['Couchbase\MutateInBuilder', 'id'=>'string', 'cas'=>'string'],
'Couchbase\Bucket::ping' => ['array', 'services='=>'int', 'reportId='=>'string'],
'Couchbase\Bucket::prepend' => ['Couchbase\Document|array', 'ids'=>'array|string', 'value'=>'mixed', 'options='=>'array'],
'Couchbase\Bucket::query' => ['object', 'query'=>'Couchbase\AnalyticsQuery|Couchbase\N1qlQuery|Couchbase\SearchQuery|Couchbase\SpatialViewQuery|Couchbase\ViewQuery', 'jsonAsArray='=>'bool|false'],
'Couchbase\Bucket::queueAdd' => ['', 'id'=>'string', 'value'=>'mixed'],
'Couchbase\Bucket::queueExists' => ['bool', 'id'=>'string', 'value'=>'mixed'],
'Couchbase\Bucket::queueRemove' => ['mixed', 'id'=>'string'],
'Couchbase\Bucket::queueSize' => ['int', 'id'=>'string'],
'Couchbase\Bucket::remove' => ['Couchbase\Document|array', 'ids'=>'array|string', 'options='=>'array'],
'Couchbase\Bucket::replace' => ['Couchbase\Document|array', 'ids'=>'array|string', 'value'=>'mixed', 'options='=>'array'],
'Couchbase\Bucket::retrieveIn' => ['Couchbase\DocumentFragment', 'id'=>'string', '...paths='=>'array<int,string>'],
'Couchbase\Bucket::setAdd' => ['', 'id'=>'string', 'value'=>'bool|float|int|string'],
'Couchbase\Bucket::setExists' => ['bool', 'id'=>'string', 'value'=>'bool|float|int|string'],
'Couchbase\Bucket::setRemove' => ['', 'id'=>'string', 'value'=>'bool|float|int|string'],
'Couchbase\Bucket::setSize' => ['int', 'id'=>'string'],
'Couchbase\Bucket::setTranscoder' => ['', 'encoder'=>'callable', 'decoder'=>'callable'],
'Couchbase\Bucket::touch' => ['Couchbase\Document|array', 'ids'=>'array|string', 'expiry'=>'int', 'options='=>'array'],
'Couchbase\Bucket::unlock' => ['Couchbase\Document|array', 'ids'=>'array|string', 'options='=>'array'],
'Couchbase\Bucket::upsert' => ['Couchbase\Document|array', 'ids'=>'array|string', 'value'=>'mixed', 'options='=>'array'],
'Couchbase\BucketManager::__construct' => ['void'],
'Couchbase\BucketManager::createN1qlIndex' => ['', 'name'=>'string', 'fields'=>'array', 'whereClause='=>'string', 'ignoreIfExist='=>'bool|false', 'defer='=>'bool|false'],
'Couchbase\BucketManager::createN1qlPrimaryIndex' => ['', 'customName='=>'string', 'ignoreIfExist='=>'bool|false', 'defer='=>'bool|false'],
'Couchbase\BucketManager::dropN1qlIndex' => ['', 'name'=>'string', 'ignoreIfNotExist='=>'bool|false'],
'Couchbase\BucketManager::dropN1qlPrimaryIndex' => ['', 'customName='=>'string', 'ignoreIfNotExist='=>'bool|false'],
'Couchbase\BucketManager::flush' => [''],
'Couchbase\BucketManager::getDesignDocument' => ['array', 'name'=>'string'],
'Couchbase\BucketManager::info' => ['array'],
'Couchbase\BucketManager::insertDesignDocument' => ['', 'name'=>'string', 'document'=>'array'],
'Couchbase\BucketManager::listDesignDocuments' => ['array'],
'Couchbase\BucketManager::listN1qlIndexes' => ['array'],
'Couchbase\BucketManager::removeDesignDocument' => ['', 'name'=>'string'],
'Couchbase\BucketManager::upsertDesignDocument' => ['', 'name'=>'string', 'document'=>'array'],
'Couchbase\ClassicAuthenticator::bucket' => ['', 'name'=>'string', 'password'=>'string'],
'Couchbase\ClassicAuthenticator::cluster' => ['', 'username'=>'string', 'password'=>'string'],
'Couchbase\Cluster::__construct' => ['void', 'connstr'=>'string'],
'Couchbase\Cluster::authenticate' => ['null', 'authenticator'=>'Couchbase\Authenticator'],
'Couchbase\Cluster::authenticateAs' => ['null', 'username'=>'string', 'password'=>'string'],
'Couchbase\Cluster::manager' => ['Couchbase\ClusterManager', 'username='=>'string', 'password='=>'string'],
'Couchbase\Cluster::openBucket' => ['Couchbase\Bucket', 'name='=>'string', 'password='=>'string'],
'Couchbase\ClusterManager::__construct' => ['void'],
'Couchbase\ClusterManager::createBucket' => ['', 'name'=>'string', 'options='=>'array'],
'Couchbase\ClusterManager::getUser' => ['array', 'username'=>'string', 'domain='=>'int'],
'Couchbase\ClusterManager::info' => ['array'],
'Couchbase\ClusterManager::listBuckets' => ['array'],
'Couchbase\ClusterManager::listUsers' => ['array', 'domain='=>'int'],
'Couchbase\ClusterManager::removeBucket' => ['', 'name'=>'string'],
'Couchbase\ClusterManager::removeUser' => ['', 'name'=>'string', 'domain='=>'int'],
'Couchbase\ClusterManager::upsertUser' => ['', 'name'=>'string', 'settings'=>'Couchbase\UserSettings', 'domain='=>'int'],
'Couchbase\ConjunctionSearchQuery::__construct' => ['void'],
'Couchbase\ConjunctionSearchQuery::boost' => ['Couchbase\ConjunctionSearchQuery', 'boost'=>'float'],
'Couchbase\ConjunctionSearchQuery::every' => ['Couchbase\ConjunctionSearchQuery', '...queries='=>'array<int,Couchbase\SearchQueryPart>'],
'Couchbase\ConjunctionSearchQuery::jsonSerialize' => ['array'],
'Couchbase\DateRangeSearchFacet::__construct' => ['void'],
'Couchbase\DateRangeSearchFacet::addRange' => ['Couchbase\DateSearchFacet', 'name'=>'string', 'start'=>'int|string', 'end'=>'int|string'],
'Couchbase\DateRangeSearchFacet::jsonSerialize' => ['array'],
'Couchbase\DateRangeSearchQuery::__construct' => ['void'],
'Couchbase\DateRangeSearchQuery::boost' => ['Couchbase\DateRangeSearchQuery', 'boost'=>'float'],
'Couchbase\DateRangeSearchQuery::dateTimeParser' => ['Couchbase\DateRangeSearchQuery', 'dateTimeParser'=>'string'],
'Couchbase\DateRangeSearchQuery::end' => ['Couchbase\DateRangeSearchQuery', 'end'=>'int|string', 'inclusive='=>'bool|false'],
'Couchbase\DateRangeSearchQuery::field' => ['Couchbase\DateRangeSearchQuery', 'field'=>'string'],
'Couchbase\DateRangeSearchQuery::jsonSerialize' => ['array'],
'Couchbase\DateRangeSearchQuery::start' => ['Couchbase\DateRangeSearchQuery', 'start'=>'int|string', 'inclusive='=>'bool|true'],
'Couchbase\defaultDecoder' => ['mixed', 'bytes'=>'string', 'flags'=>'int', 'datatype'=>'int'],
'Couchbase\defaultEncoder' => ['array', 'value'=>'mixed'],
'Couchbase\DisjunctionSearchQuery::__construct' => ['void'],
'Couchbase\DisjunctionSearchQuery::boost' => ['Couchbase\DisjunctionSearchQuery', 'boost'=>'float'],
'Couchbase\DisjunctionSearchQuery::either' => ['Couchbase\DisjunctionSearchQuery', '...queries='=>'array<int,Couchbase\SearchQueryPart>'],
'Couchbase\DisjunctionSearchQuery::jsonSerialize' => ['array'],
'Couchbase\DisjunctionSearchQuery::min' => ['Couchbase\DisjunctionSearchQuery', 'min'=>'int'],
'Couchbase\DocIdSearchQuery::__construct' => ['void'],
'Couchbase\DocIdSearchQuery::boost' => ['Couchbase\DocIdSearchQuery', 'boost'=>'float'],
'Couchbase\DocIdSearchQuery::docIds' => ['Couchbase\DocIdSearchQuery', '...documentIds='=>'array<int,string>'],
'Couchbase\DocIdSearchQuery::field' => ['Couchbase\DocIdSearchQuery', 'field'=>'string'],
'Couchbase\DocIdSearchQuery::jsonSerialize' => ['array'],
'Couchbase\fastlzCompress' => ['string', 'data'=>'string'],
'Couchbase\fastlzDecompress' => ['string', 'data'=>'string'],
'Couchbase\GeoBoundingBoxSearchQuery::__construct' => ['void'],
'Couchbase\GeoBoundingBoxSearchQuery::boost' => ['Couchbase\GeoBoundingBoxSearchQuery', 'boost'=>'float'],
'Couchbase\GeoBoundingBoxSearchQuery::field' => ['Couchbase\GeoBoundingBoxSearchQuery', 'field'=>'string'],
'Couchbase\GeoBoundingBoxSearchQuery::jsonSerialize' => ['array'],
'Couchbase\GeoDistanceSearchQuery::__construct' => ['void'],
'Couchbase\GeoDistanceSearchQuery::boost' => ['Couchbase\GeoDistanceSearchQuery', 'boost'=>'float'],
'Couchbase\GeoDistanceSearchQuery::field' => ['Couchbase\GeoDistanceSearchQuery', 'field'=>'string'],
'Couchbase\GeoDistanceSearchQuery::jsonSerialize' => ['array'],
'Couchbase\LookupInBuilder::__construct' => ['void'],
'Couchbase\LookupInBuilder::execute' => ['Couchbase\DocumentFragment'],
'Couchbase\LookupInBuilder::exists' => ['Couchbase\LookupInBuilder', 'path'=>'string', 'options='=>'array'],
'Couchbase\LookupInBuilder::get' => ['Couchbase\LookupInBuilder', 'path'=>'string', 'options='=>'array'],
'Couchbase\LookupInBuilder::getCount' => ['Couchbase\LookupInBuilder', 'path'=>'string', 'options='=>'array'],
'Couchbase\MatchAllSearchQuery::__construct' => ['void'],
'Couchbase\MatchAllSearchQuery::boost' => ['Couchbase\MatchAllSearchQuery', 'boost'=>'float'],
'Couchbase\MatchAllSearchQuery::jsonSerialize' => ['array'],
'Couchbase\MatchNoneSearchQuery::__construct' => ['void'],
'Couchbase\MatchNoneSearchQuery::boost' => ['Couchbase\MatchNoneSearchQuery', 'boost'=>'float'],
'Couchbase\MatchNoneSearchQuery::jsonSerialize' => ['array'],
'Couchbase\MatchPhraseSearchQuery::__construct' => ['void'],
'Couchbase\MatchPhraseSearchQuery::analyzer' => ['Couchbase\MatchPhraseSearchQuery', 'analyzer'=>'string'],
'Couchbase\MatchPhraseSearchQuery::boost' => ['Couchbase\MatchPhraseSearchQuery', 'boost'=>'float'],
'Couchbase\MatchPhraseSearchQuery::field' => ['Couchbase\MatchPhraseSearchQuery', 'field'=>'string'],
'Couchbase\MatchPhraseSearchQuery::jsonSerialize' => ['array'],
'Couchbase\MatchSearchQuery::__construct' => ['void'],
'Couchbase\MatchSearchQuery::analyzer' => ['Couchbase\MatchSearchQuery', 'analyzer'=>'string'],
'Couchbase\MatchSearchQuery::boost' => ['Couchbase\MatchSearchQuery', 'boost'=>'float'],
'Couchbase\MatchSearchQuery::field' => ['Couchbase\MatchSearchQuery', 'field'=>'string'],
'Couchbase\MatchSearchQuery::fuzziness' => ['Couchbase\MatchSearchQuery', 'fuzziness'=>'int'],
'Couchbase\MatchSearchQuery::jsonSerialize' => ['array'],
'Couchbase\MatchSearchQuery::prefixLength' => ['Couchbase\MatchSearchQuery', 'prefixLength'=>'int'],
'Couchbase\MutateInBuilder::__construct' => ['void'],
'Couchbase\MutateInBuilder::arrayAddUnique' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array|bool'],
'Couchbase\MutateInBuilder::arrayAppend' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array|bool'],
'Couchbase\MutateInBuilder::arrayAppendAll' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'values'=>'array', 'options='=>'array|bool'],
'Couchbase\MutateInBuilder::arrayInsert' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array'],
'Couchbase\MutateInBuilder::arrayInsertAll' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'values'=>'array', 'options='=>'array'],
'Couchbase\MutateInBuilder::arrayPrepend' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array|bool'],
'Couchbase\MutateInBuilder::arrayPrependAll' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'values'=>'array', 'options='=>'array|bool'],
'Couchbase\MutateInBuilder::counter' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'delta'=>'int', 'options='=>'array|bool'],
'Couchbase\MutateInBuilder::execute' => ['Couchbase\DocumentFragment'],
'Couchbase\MutateInBuilder::insert' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array|bool'],
'Couchbase\MutateInBuilder::modeDocument' => ['', 'mode'=>'int'],
'Couchbase\MutateInBuilder::remove' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'options='=>'array'],
'Couchbase\MutateInBuilder::replace' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array'],
'Couchbase\MutateInBuilder::upsert' => ['Couchbase\MutateInBuilder', 'path'=>'string', 'value'=>'mixed', 'options='=>'array|bool'],
'Couchbase\MutateInBuilder::withExpiry' => ['Couchbase\MutateInBuilder', 'expiry'=>'Couchbase\expiry'],
'Couchbase\MutationState::__construct' => ['void'],
'Couchbase\MutationState::add' => ['', 'source'=>'Couchbase\Document|Couchbase\DocumentFragment|array'],
'Couchbase\MutationState::from' => ['Couchbase\MutationState', 'source'=>'Couchbase\Document|Couchbase\DocumentFragment|array'],
'Couchbase\MutationToken::__construct' => ['void'],
'Couchbase\MutationToken::bucketName' => ['string'],
'Couchbase\MutationToken::from' => ['', 'bucketName'=>'string', 'vbucketId'=>'int', 'vbucketUuid'=>'string', 'sequenceNumber'=>'string'],
'Couchbase\MutationToken::sequenceNumber' => ['string'],
'Couchbase\MutationToken::vbucketId' => ['int'],
'Couchbase\MutationToken::vbucketUuid' => ['string'],
'Couchbase\N1qlIndex::__construct' => ['void'],
'Couchbase\N1qlQuery::__construct' => ['void'],
'Couchbase\N1qlQuery::adhoc' => ['Couchbase\N1qlQuery', 'adhoc'=>'bool'],
'Couchbase\N1qlQuery::consistency' => ['Couchbase\N1qlQuery', 'consistency'=>'int'],
'Couchbase\N1qlQuery::consistentWith' => ['Couchbase\N1qlQuery', 'state'=>'Couchbase\MutationState'],
'Couchbase\N1qlQuery::crossBucket' => ['Couchbase\N1qlQuery', 'crossBucket'=>'bool'],
'Couchbase\N1qlQuery::fromString' => ['Couchbase\N1qlQuery', 'statement'=>'string'],
'Couchbase\N1qlQuery::maxParallelism' => ['Couchbase\N1qlQuery', 'maxParallelism'=>'int'],
'Couchbase\N1qlQuery::namedParams' => ['Couchbase\N1qlQuery', 'params'=>'array'],
'Couchbase\N1qlQuery::pipelineBatch' => ['Couchbase\N1qlQuery', 'pipelineBatch'=>'int'],
'Couchbase\N1qlQuery::pipelineCap' => ['Couchbase\N1qlQuery', 'pipelineCap'=>'int'],
'Couchbase\N1qlQuery::positionalParams' => ['Couchbase\N1qlQuery', 'params'=>'array'],
'Couchbase\N1qlQuery::readonly' => ['Couchbase\N1qlQuery', 'readonly'=>'bool'],
'Couchbase\N1qlQuery::scanCap' => ['Couchbase\N1qlQuery', 'scanCap'=>'int'],
'Couchbase\NumericRangeSearchFacet::__construct' => ['void'],
'Couchbase\NumericRangeSearchFacet::addRange' => ['Couchbase\NumericSearchFacet', 'name'=>'string', 'min'=>'float', 'max'=>'float'],
'Couchbase\NumericRangeSearchFacet::jsonSerialize' => ['array'],
'Couchbase\NumericRangeSearchQuery::__construct' => ['void'],
'Couchbase\NumericRangeSearchQuery::boost' => ['Couchbase\NumericRangeSearchQuery', 'boost'=>'float'],
'Couchbase\NumericRangeSearchQuery::field' => ['Couchbase\NumericRangeSearchQuery', 'field'=>'string'],
'Couchbase\NumericRangeSearchQuery::jsonSerialize' => ['array'],
'Couchbase\NumericRangeSearchQuery::max' => ['Couchbase\NumericRangeSearchQuery', 'max'=>'float', 'inclusive='=>'bool|false'],
'Couchbase\NumericRangeSearchQuery::min' => ['Couchbase\NumericRangeSearchQuery', 'min'=>'float', 'inclusive='=>'bool|true'],
'Couchbase\passthruDecoder' => ['string', 'bytes'=>'string', 'flags'=>'int', 'datatype'=>'int'],
'Couchbase\passthruEncoder' => ['array', 'value'=>'string'],
'Couchbase\PasswordAuthenticator::password' => ['Couchbase\PasswordAuthenticator', 'password'=>'string'],
'Couchbase\PasswordAuthenticator::username' => ['Couchbase\PasswordAuthenticator', 'username'=>'string'],
'Couchbase\PhraseSearchQuery::__construct' => ['void'],
'Couchbase\PhraseSearchQuery::boost' => ['Couchbase\PhraseSearchQuery', 'boost'=>'float'],
'Couchbase\PhraseSearchQuery::field' => ['Couchbase\PhraseSearchQuery', 'field'=>'string'],
'Couchbase\PhraseSearchQuery::jsonSerialize' => ['array'],
'Couchbase\PrefixSearchQuery::__construct' => ['void'],
'Couchbase\PrefixSearchQuery::boost' => ['Couchbase\PrefixSearchQuery', 'boost'=>'float'],
'Couchbase\PrefixSearchQuery::field' => ['Couchbase\PrefixSearchQuery', 'field'=>'string'],
'Couchbase\PrefixSearchQuery::jsonSerialize' => ['array'],
'Couchbase\QueryStringSearchQuery::__construct' => ['void'],
'Couchbase\QueryStringSearchQuery::boost' => ['Couchbase\QueryStringSearchQuery', 'boost'=>'float'],
'Couchbase\QueryStringSearchQuery::jsonSerialize' => ['array'],
'Couchbase\RegexpSearchQuery::__construct' => ['void'],
'Couchbase\RegexpSearchQuery::boost' => ['Couchbase\RegexpSearchQuery', 'boost'=>'float'],
'Couchbase\RegexpSearchQuery::field' => ['Couchbase\RegexpSearchQuery', 'field'=>'string'],
'Couchbase\RegexpSearchQuery::jsonSerialize' => ['array'],
'Couchbase\SearchQuery::__construct' => ['void', 'indexName'=>'string', 'queryPart'=>'Couchbase\SearchQueryPart'],
'Couchbase\SearchQuery::addFacet' => ['Couchbase\SearchQuery', 'name'=>'string', 'facet'=>'Couchbase\SearchFacet'],
'Couchbase\SearchQuery::boolean' => ['Couchbase\BooleanSearchQuery'],
'Couchbase\SearchQuery::booleanField' => ['Couchbase\BooleanFieldSearchQuery', 'value'=>'bool'],
'Couchbase\SearchQuery::conjuncts' => ['Couchbase\ConjunctionSearchQuery', '...queries='=>'array<int,Couchbase\SearchQueryPart>'],
'Couchbase\SearchQuery::consistentWith' => ['Couchbase\SearchQuery', 'state'=>'Couchbase\MutationState'],
'Couchbase\SearchQuery::dateRange' => ['Couchbase\DateRangeSearchQuery'],
'Couchbase\SearchQuery::dateRangeFacet' => ['Couchbase\DateRangeSearchFacet', 'field'=>'string', 'limit'=>'int'],
'Couchbase\SearchQuery::disjuncts' => ['Couchbase\DisjunctionSearchQuery', '...queries='=>'array<int,Couchbase\SearchQueryPart>'],
'Couchbase\SearchQuery::docId' => ['Couchbase\DocIdSearchQuery', '...documentIds='=>'array<int,string>'],
'Couchbase\SearchQuery::explain' => ['Couchbase\SearchQuery', 'explain'=>'bool'],
'Couchbase\SearchQuery::fields' => ['Couchbase\SearchQuery', '...fields='=>'array<int,string>'],
'Couchbase\SearchQuery::geoBoundingBox' => ['Couchbase\GeoBoundingBoxSearchQuery', 'topLeftLongitude'=>'float', 'topLeftLatitude'=>'float', 'bottomRightLongitude'=>'float', 'bottomRightLatitude'=>'float'],
'Couchbase\SearchQuery::geoDistance' => ['Couchbase\GeoDistanceSearchQuery', 'longitude'=>'float', 'latitude'=>'float', 'distance'=>'string'],
'Couchbase\SearchQuery::highlight' => ['Couchbase\SearchQuery', 'style'=>'string', '...fields='=>'array<int,string>'],
'Couchbase\SearchQuery::jsonSerialize' => ['array'],
'Couchbase\SearchQuery::limit' => ['Couchbase\SearchQuery', 'limit'=>'int'],
'Couchbase\SearchQuery::match' => ['Couchbase\MatchSearchQuery', 'match'=>'string'],
'Couchbase\SearchQuery::matchAll' => ['Couchbase\MatchAllSearchQuery'],
'Couchbase\SearchQuery::matchNone' => ['Couchbase\MatchNoneSearchQuery'],
'Couchbase\SearchQuery::matchPhrase' => ['Couchbase\MatchPhraseSearchQuery', '...terms='=>'array<int,string>'],
'Couchbase\SearchQuery::numericRange' => ['Couchbase\NumericRangeSearchQuery'],
'Couchbase\SearchQuery::numericRangeFacet' => ['Couchbase\NumericRangeSearchFacet', 'field'=>'string', 'limit'=>'int'],
'Couchbase\SearchQuery::prefix' => ['Couchbase\PrefixSearchQuery', 'prefix'=>'string'],
'Couchbase\SearchQuery::queryString' => ['Couchbase\QueryStringSearchQuery', 'queryString'=>'string'],
'Couchbase\SearchQuery::regexp' => ['Couchbase\RegexpSearchQuery', 'regexp'=>'string'],
'Couchbase\SearchQuery::serverSideTimeout' => ['Couchbase\SearchQuery', 'serverSideTimeout'=>'int'],
'Couchbase\SearchQuery::skip' => ['Couchbase\SearchQuery', 'skip'=>'int'],
'Couchbase\SearchQuery::sort' => ['Couchbase\SearchQuery', '...sort='=>'array<int,Couchbase\sort>'],
'Couchbase\SearchQuery::term' => ['Couchbase\TermSearchQuery', 'term'=>'string'],
'Couchbase\SearchQuery::termFacet' => ['Couchbase\TermSearchFacet', 'field'=>'string', 'limit'=>'int'],
'Couchbase\SearchQuery::termRange' => ['Couchbase\TermRangeSearchQuery'],
'Couchbase\SearchQuery::wildcard' => ['Couchbase\WildcardSearchQuery', 'wildcard'=>'string'],
'Couchbase\SpatialViewQuery::__construct' => ['void'],
'Couchbase\SpatialViewQuery::bbox' => ['Couchbase\SpatialViewQuery', 'bbox'=>'array'],
'Couchbase\SpatialViewQuery::consistency' => ['Couchbase\SpatialViewQuery', 'consistency'=>'int'],
'Couchbase\SpatialViewQuery::custom' => ['', 'customParameters'=>'array'],
'Couchbase\SpatialViewQuery::encode' => ['array'],
'Couchbase\SpatialViewQuery::endRange' => ['Couchbase\SpatialViewQuery', 'range'=>'array'],
'Couchbase\SpatialViewQuery::limit' => ['Couchbase\SpatialViewQuery', 'limit'=>'int'],
'Couchbase\SpatialViewQuery::order' => ['Couchbase\SpatialViewQuery', 'order'=>'int'],
'Couchbase\SpatialViewQuery::skip' => ['Couchbase\SpatialViewQuery', 'skip'=>'int'],
'Couchbase\SpatialViewQuery::startRange' => ['Couchbase\SpatialViewQuery', 'range'=>'array'],
'Couchbase\TermRangeSearchQuery::__construct' => ['void'],
'Couchbase\TermRangeSearchQuery::boost' => ['Couchbase\TermRangeSearchQuery', 'boost'=>'float'],
'Couchbase\TermRangeSearchQuery::field' => ['Couchbase\TermRangeSearchQuery', 'field'=>'string'],
'Couchbase\TermRangeSearchQuery::jsonSerialize' => ['array'],
'Couchbase\TermRangeSearchQuery::max' => ['Couchbase\TermRangeSearchQuery', 'max'=>'string', 'inclusive='=>'bool|false'],
'Couchbase\TermRangeSearchQuery::min' => ['Couchbase\TermRangeSearchQuery', 'min'=>'string', 'inclusive='=>'bool|true'],
'Couchbase\TermSearchFacet::__construct' => ['void'],
'Couchbase\TermSearchFacet::jsonSerialize' => ['array'],
'Couchbase\TermSearchQuery::__construct' => ['void'],
'Couchbase\TermSearchQuery::boost' => ['Couchbase\TermSearchQuery', 'boost'=>'float'],
'Couchbase\TermSearchQuery::field' => ['Couchbase\TermSearchQuery', 'field'=>'string'],
'Couchbase\TermSearchQuery::fuzziness' => ['Couchbase\TermSearchQuery', 'fuzziness'=>'int'],
'Couchbase\TermSearchQuery::jsonSerialize' => ['array'],
'Couchbase\TermSearchQuery::prefixLength' => ['Couchbase\TermSearchQuery', 'prefixLength'=>'int'],
'Couchbase\UserSettings::fullName' => ['Couchbase\UserSettings', 'fullName'=>'string'],
'Couchbase\UserSettings::password' => ['Couchbase\UserSettings', 'password'=>'string'],
'Couchbase\UserSettings::role' => ['Couchbase\UserSettings', 'role'=>'string', 'bucket='=>'string'],
'Couchbase\ViewQuery::__construct' => ['void'],
'Couchbase\ViewQuery::consistency' => ['Couchbase\ViewQuery', 'consistency'=>'int'],
'Couchbase\ViewQuery::custom' => ['Couchbase\ViewQuery', 'customParameters'=>'array'],
'Couchbase\ViewQuery::encode' => ['array'],
'Couchbase\ViewQuery::from' => ['Couchbase\ViewQuery', 'designDocumentName'=>'string', 'viewName'=>'string'],
'Couchbase\ViewQuery::fromSpatial' => ['Couchbase\SpatialViewQuery', 'designDocumentName'=>'string', 'viewName'=>'string'],
'Couchbase\ViewQuery::group' => ['Couchbase\ViewQuery', 'group'=>'bool'],
'Couchbase\ViewQuery::groupLevel' => ['Couchbase\ViewQuery', 'groupLevel'=>'int'],
'Couchbase\ViewQuery::idRange' => ['Couchbase\ViewQuery', 'startKeyDocumentId'=>'string', 'endKeyDocumentId'=>'string'],
'Couchbase\ViewQuery::key' => ['Couchbase\ViewQuery', 'key'=>'mixed'],
'Couchbase\ViewQuery::keys' => ['Couchbase\ViewQuery', 'keys'=>'array'],
'Couchbase\ViewQuery::limit' => ['Couchbase\ViewQuery', 'limit'=>'int'],
'Couchbase\ViewQuery::order' => ['Couchbase\ViewQuery', 'order'=>'int'],
'Couchbase\ViewQuery::range' => ['Couchbase\ViewQuery', 'startKey'=>'mixed', 'endKey'=>'mixed', 'inclusiveEnd='=>'bool|false'],
'Couchbase\ViewQuery::reduce' => ['Couchbase\ViewQuery', 'reduce'=>'bool'],
'Couchbase\ViewQuery::skip' => ['Couchbase\ViewQuery', 'skip'=>'int'],
'Couchbase\ViewQueryEncodable::encode' => ['array'],
'Couchbase\WildcardSearchQuery::__construct' => ['void'],
'Couchbase\WildcardSearchQuery::boost' => ['Couchbase\WildcardSearchQuery', 'boost'=>'float'],
'Couchbase\WildcardSearchQuery::field' => ['Couchbase\WildcardSearchQuery', 'field'=>'string'],
'Couchbase\WildcardSearchQuery::jsonSerialize' => ['array'],
'Couchbase\zlibCompress' => ['string', 'data'=>'string'],
'Couchbase\zlibDecompress' => ['string', 'data'=>'string'],
'count' => ['int', 'var'=>'Countable|array', 'mode='=>'int'],
'count_chars' => ['mixed', 'input'=>'string', 'mode='=>'int'],
'Countable::count' => ['int'],
'crack_check' => ['bool', 'dictionary'=>'', 'password'=>'string'],
'crack_closedict' => ['bool', 'dictionary='=>'resource'],
'crack_getlastmessage' => ['string'],
'crack_opendict' => ['resource', 'dictionary'=>'string'],
'crash' => [''],
'crc32' => ['int', 'str'=>'string'],
'create_function' => ['string', 'args'=>'string', 'code'=>'string'],
'crypt' => ['string', 'str'=>'string', 'salt='=>'string'],
'ctype_alnum' => ['bool', 'c'=>'string'],
'ctype_alpha' => ['bool', 'c'=>'string'],
'ctype_cntrl' => ['bool', 'c'=>'string'],
'ctype_digit' => ['bool', 'c'=>'string'],
'ctype_graph' => ['bool', 'c'=>'string'],
'ctype_lower' => ['bool', 'c'=>'string'],
'ctype_print' => ['bool', 'c'=>'string'],
'ctype_punct' => ['bool', 'c'=>'string'],
'ctype_space' => ['bool', 'c'=>'string'],
'ctype_upper' => ['bool', 'c'=>'string'],
'ctype_xdigit' => ['bool', 'c'=>'string'],
'cubrid_affected_rows' => ['int', 'req_identifier='=>''],
'cubrid_bind' => ['bool', 'req_identifier'=>'resource', 'bind_param'=>'int', 'bind_value'=>'mixed', 'bind_value_type='=>'string'],
'cubrid_client_encoding' => ['string', 'conn_identifier='=>''],
'cubrid_close' => ['bool', 'conn_identifier='=>''],
'cubrid_close_prepare' => ['int', 'req_identifier'=>'resource'],
'cubrid_close_request' => ['bool', 'req_identifier'=>'resource'],
'cubrid_col_get' => ['array', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string'],
'cubrid_col_size' => ['int', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string'],
'cubrid_column_names' => ['array', 'req_identifier'=>'resource'],
'cubrid_column_types' => ['array', 'req_identifier'=>'resource'],
'cubrid_commit' => ['bool', 'conn_identifier'=>'resource'],
'cubrid_connect' => ['resource', 'host'=>'string', 'port'=>'int', 'dbname'=>'string', 'userid='=>'string', 'passwd='=>'string'],
'cubrid_connect_with_url' => ['resource', 'conn_url'=>'string', 'userid='=>'string', 'passwd='=>'string'],
'cubrid_current_oid' => ['string', 'req_identifier'=>'resource'],
'cubrid_data_seek' => ['bool', 'req_identifier'=>'', 'row_number'=>'int'],
'cubrid_db_name' => ['string', 'result'=>'array', 'index'=>'int'],
'cubrid_db_parameter' => ['array', 'conn_identifier'=>'resource'],
'cubrid_disconnect' => ['bool', 'conn_identifier'=>'resource'],
'cubrid_drop' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string'],
'cubrid_errno' => ['int', 'conn_identifier='=>''],
'cubrid_error' => ['string', 'connection='=>''],
'cubrid_error_code' => ['int'],
'cubrid_error_code_facility' => ['int'],
'cubrid_error_msg' => ['string'],
'cubrid_execute' => ['bool', 'conn_identifier'=>'', 'sql'=>'string', 'option='=>'int', 'request_identifier='=>''],
'cubrid_fetch' => ['mixed', 'result'=>'resource', 'type='=>'int'],
'cubrid_fetch_array' => ['array', 'result'=>'', 'type='=>'int'],
'cubrid_fetch_assoc' => ['array', 'result'=>''],
'cubrid_fetch_field' => ['object', 'result'=>'', 'field_offset='=>'int'],
'cubrid_fetch_lengths' => ['array', 'result'=>''],
'cubrid_fetch_object' => ['object', 'result'=>'', 'class_name='=>'string', 'params='=>'array'],
'cubrid_fetch_row' => ['array', 'result'=>''],
'cubrid_field_flags' => ['string', 'result'=>'', 'field_offset'=>'int'],
'cubrid_field_len' => ['int', 'result'=>'', 'field_offset'=>'int'],
'cubrid_field_name' => ['string', 'result'=>'', 'field_offset'=>'int'],
'cubrid_field_seek' => ['bool', 'result'=>'', 'field_offset='=>'int'],
'cubrid_field_table' => ['string', 'result'=>'', 'field_offset'=>'int'],
'cubrid_field_type' => ['string', 'result'=>'', 'field_offset'=>'int'],
'cubrid_free_result' => ['bool', 'req_identifier'=>'resource'],
'cubrid_get' => ['mixed', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr='=>'mixed'],
'cubrid_get_autocommit' => ['bool', 'conn_identifier'=>'resource'],
'cubrid_get_charset' => ['string', 'conn_identifier'=>'resource'],
'cubrid_get_class_name' => ['string', 'conn_identifier'=>'resource', 'oid'=>'string'],
'cubrid_get_client_info' => ['string'],
'cubrid_get_db_parameter' => ['array', 'conn_identifier'=>'resource'],
'cubrid_get_query_timeout' => ['int', 'req_identifier'=>'resource'],
'cubrid_get_server_info' => ['string', 'conn_identifier'=>'resource'],
'cubrid_insert_id' => ['string', 'conn_identifier='=>'resource'],
'cubrid_is_instance' => ['int', 'conn_identifier'=>'resource', 'oid'=>'string'],
'cubrid_list_dbs' => ['array', 'conn_identifier'=>''],
'cubrid_load_from_glo' => ['int', 'conn_identifier'=>'', 'oid'=>'string', 'file_name'=>'string'],
'cubrid_lob2_bind' => ['bool', 'req_identifier'=>'resource', 'bind_index'=>'int', 'bind_value'=>'mixed', 'bind_value_type='=>'string'],
'cubrid_lob2_close' => ['bool', 'lob_identifier'=>'resource'],
'cubrid_lob2_export' => ['bool', 'lob_identifier'=>'resource', 'file_name'=>'string'],
'cubrid_lob2_import' => ['bool', 'lob_identifier'=>'resource', 'file_name'=>'string'],
'cubrid_lob2_new' => ['resource', 'conn_identifier='=>'resource', 'type='=>'string'],
'cubrid_lob2_read' => ['string', 'lob_identifier'=>'resource', 'len'=>'int'],
'cubrid_lob2_seek' => ['bool', 'lob_identifier'=>'resource', 'offset'=>'int', 'origin='=>'int'],
'cubrid_lob2_seek64' => ['bool', 'lob_identifier'=>'resource', 'offset'=>'string', 'origin='=>'int'],
'cubrid_lob2_size' => ['int', 'lob_identifier'=>'resource'],
'cubrid_lob2_size64' => ['string', 'lob_identifier'=>'resource'],
'cubrid_lob2_tell' => ['int', 'lob_identifier'=>'resource'],
'cubrid_lob2_tell64' => ['string', 'lob_identifier'=>'resource'],
'cubrid_lob2_write' => ['bool', 'lob_identifier'=>'resource', 'buf'=>'string'],
'cubrid_lob_close' => ['bool', 'lob_identifier_array'=>'array'],
'cubrid_lob_export' => ['bool', 'conn_identifier'=>'resource', 'lob_identifier'=>'resource', 'path_name'=>'string'],
'cubrid_lob_get' => ['array', 'conn_identifier'=>'resource', 'sql'=>'string'],
'cubrid_lob_send' => ['bool', 'conn_identifier'=>'resource', 'lob_identifier'=>'resource'],
'cubrid_lob_size' => ['string', 'lob_identifier'=>'resource'],
'cubrid_lock_read' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string'],
'cubrid_lock_write' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string'],
'cubrid_move_cursor' => ['int', 'req_identifier'=>'resource', 'offset'=>'int', 'origin='=>'int'],
'cubrid_new_glo' => ['string', 'conn_identifier'=>'', 'class_name'=>'string', 'file_name'=>'string'],
'cubrid_next_result' => ['bool', 'result'=>'resource'],
'cubrid_num_cols' => ['int', 'req_identifier'=>'resource'],
'cubrid_num_fields' => ['int', 'result'=>''],
'cubrid_num_rows' => ['int', 'req_identifier'=>'resource'],
'cubrid_pconnect' => ['resource', 'host'=>'string', 'port'=>'int', 'dbname'=>'string', 'userid='=>'string', 'passwd='=>'string'],
'cubrid_pconnect_with_url' => ['resource', 'conn_url'=>'string', 'userid='=>'string', 'passwd='=>'string'],
'cubrid_ping' => ['bool', 'conn_identifier='=>''],
'cubrid_prepare' => ['resource', 'conn_identifier'=>'resource', 'prepare_stmt'=>'string', 'option='=>'int'],
'cubrid_put' => ['int', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr='=>'string', 'value='=>'mixed'],
'cubrid_query' => ['resource', 'query'=>'string', 'conn_identifier='=>''],
'cubrid_real_escape_string' => ['string', 'unescaped_string'=>'string', 'conn_identifier='=>''],
'cubrid_result' => ['string', 'result'=>'', 'row'=>'int', 'field='=>''],
'cubrid_rollback' => ['bool', 'conn_identifier'=>'resource'],
'cubrid_save_to_glo' => ['int', 'conn_identifier'=>'', 'oid'=>'string', 'file_name'=>'string'],
'cubrid_schema' => ['array', 'conn_identifier'=>'resource', 'schema_type'=>'int', 'class_name='=>'string', 'attr_name='=>'string'],
'cubrid_send_glo' => ['int', 'conn_identifier'=>'', 'oid'=>'string'],
'cubrid_seq_drop' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'index'=>'int'],
'cubrid_seq_insert' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'index'=>'int', 'seq_element'=>'string'],
'cubrid_seq_put' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'index'=>'int', 'seq_element'=>'string'],
'cubrid_set_add' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'set_element'=>'string'],
'cubrid_set_autocommit' => ['bool', 'conn_identifier'=>'resource', 'mode'=>'bool'],
'cubrid_set_db_parameter' => ['bool', 'conn_identifier'=>'resource', 'param_type'=>'int', 'param_value'=>'int'],
'cubrid_set_drop' => ['bool', 'conn_identifier'=>'resource', 'oid'=>'string', 'attr_name'=>'string', 'set_element'=>'string'],
'cubrid_set_query_timeout' => ['bool', 'req_identifier'=>'resource', 'timeout'=>'int'],
'cubrid_unbuffered_query' => ['resource', 'query'=>'string', 'conn_identifier='=>''],
'cubrid_version' => ['string'],
'curl_close' => ['void', 'ch'=>'resource'],
'curl_copy_handle' => ['resource', 'ch'=>'resource'],
'curl_errno' => ['int', 'ch'=>'resource'],
'curl_error' => ['string', 'ch'=>'resource'],
'curl_escape' => ['string', 'ch'=>'resource', 'str'=>'string'],
'curl_exec' => ['bool|string', 'ch'=>'resource'],
'curl_file_create' => ['CURLFile', 'filename'=>'string', 'mimetype='=>'string', 'postfilename='=>'string'],
'curl_getinfo' => ['mixed', 'ch'=>'resource', 'option='=>'int'],
'curl_init' => ['resource|false', 'url='=>'string'],
'curl_multi_add_handle' => ['int', 'mh'=>'resource', 'ch'=>'resource'],
'curl_multi_close' => ['void', 'mh'=>'resource'],
'curl_multi_errno' => ['int', 'mh'=>'resource'],
'curl_multi_exec' => ['int', 'mh'=>'resource', '&w_still_running'=>'int'],
'curl_multi_getcontent' => ['string', 'ch'=>'resource'],
'curl_multi_info_read' => ['array|false', 'mh'=>'resource', 'msgs_in_queue='=>'int'],
'curl_multi_init' => ['resource|false'],
'curl_multi_remove_handle' => ['int', 'mh'=>'resource', 'ch'=>'resource'],
'curl_multi_select' => ['int', 'mh'=>'resource', 'timeout='=>'float'],
'curl_multi_setopt' => ['bool', 'mh'=>'resource', 'option'=>'int', 'value'=>'mixed'],
'curl_multi_strerror' => ['string', 'code'=>'int'],
'curl_pause' => ['int', 'ch'=>'resource', 'bitmask'=>'int'],
'curl_reset' => ['void', 'ch'=>'resource'],
'curl_setopt' => ['bool', 'ch'=>'resource', 'option'=>'int', 'value'=>'mixed'],
'curl_setopt_array' => ['bool', 'ch'=>'resource', 'options'=>'array'],
'curl_share_close' => ['void', 'sh'=>'resource'],
'curl_share_errno' => ['int', 'sh'=>'resource'],
'curl_share_init' => ['resource'],
'curl_share_setopt' => ['bool', 'sh'=>'resource', 'option'=>'int', 'value'=>'string'],
'curl_share_strerror' => ['string', 'code'=>'int'],
'curl_strerror' => ['string', 'code'=>'int'],
'curl_unescape' => ['string', 'ch'=>'resource', 'str'=>'string'],
'curl_version' => ['array', 'version='=>'int'],
'CURLFile::__construct' => ['void', 'filename'=>'string', 'mimetype='=>'string', 'postfilename='=>'string'],
'CURLFile::__wakeup' => ['void'],
'CURLFile::getFilename' => ['string'],
'CURLFile::getMimeType' => ['string'],
'CURLFile::getPostFilename' => ['string'],
'CURLFile::setMimeType' => ['void', 'mime'=>'string'],
'CURLFile::setPostFilename' => ['void', 'name'=>'string'],
'current' => ['mixed', 'array_arg'=>'array'],
'cyrus_authenticate' => ['void', 'connection'=>'resource', 'mechlist='=>'string', 'service='=>'string', 'user='=>'string', 'minssf='=>'int', 'maxssf='=>'int', 'authname='=>'string', 'password='=>'string'],
'cyrus_bind' => ['bool', 'connection'=>'resource', 'callbacks'=>'array'],
'cyrus_close' => ['bool', 'connection'=>'resource'],
'cyrus_connect' => ['resource', 'host='=>'string', 'port='=>'string', 'flags='=>'int'],
'cyrus_query' => ['array', 'connection'=>'resource', 'query'=>'string'],
'cyrus_unbind' => ['bool', 'connection'=>'resource', 'trigger_name'=>'string'],
'date' => ['string', 'format'=>'string', 'timestamp='=>'int'],
'date_add' => ['DateTime|false', 'object'=>'', 'interval'=>''],
'date_create' => ['DateTime|false', 'time='=>'string', 'timezone='=>'?\DateTimeZone'],
'date_create_from_format' => ['DateTime|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'DateTimeZone'],
'date_create_immutable' => ['DateTimeImmutable|false', 'time='=>'string', 'timezone='=>'?\DateTimeZone'],
'date_create_immutable_from_format' => ['DateTimeImmutable', 'format'=>'string', 'time'=>'string', 'timezone='=>'?\DateTimeZone'],
'date_date_set' => ['DateTime|false', 'object'=>'', 'year'=>'', 'month'=>'', 'day'=>''],
'date_default_timezone_get' => ['string'],
'date_default_timezone_set' => ['bool', 'timezone_identifier'=>'string'],
'date_diff' => ['DateInterval', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'],
'date_format' => ['string', 'obj'=>'DateTimeInterface', 'format'=>'string'],
'date_get_last_errors' => ['array'],
'date_interval_create_from_date_string' => ['DateInterval', 'time'=>''],
'date_interval_format' => ['DateInterval', 'object'=>'', 'format'=>''],
'date_isodate_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'day='=>'int|mixed'],
'date_modify' => ['DateTime|false', 'object'=>'DateTime', 'modify'=>'string'],
'date_offset_get' => ['int', 'obj'=>'DateTimeInterface'],
'date_parse' => ['array', 'date'=>'string'],
'date_parse_from_format' => ['array', 'format'=>'string', 'date'=>'string'],
'date_sub' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'],
'date_sun_info' => ['array', 'time'=>'int', 'latitude'=>'float', 'longitude'=>'float'],
'date_sunrise' => ['mixed', 'time'=>'int', 'format='=>'int', 'latitude='=>'float', 'longitude='=>'float', 'zenith='=>'float', 'gmt_offset='=>'float'],
'date_sunset' => ['mixed', 'time'=>'int', 'format='=>'int', 'latitude='=>'float', 'longitude='=>'float', 'zenith='=>'float', 'gmt_offset='=>'float'],
'date_time_set' => ['DateTime|false', 'object'=>'', 'hour'=>'', 'minute'=>'', 'second'=>'', 'microseconds'=>''],
'date_timestamp_get' => ['int', 'obj'=>'DateTimeInterface'],
'date_timestamp_set' => ['DateTime|false', 'object'=>'DateTime', 'unixtimestamp'=>'int'],
'date_timezone_get' => ['DateTimeZone', 'obj'=>'DateTimeInterface'],
'date_timezone_set' => ['DateTime|false', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'],
'datefmt_create' => ['IntlDateFormatter', 'locale'=>'string', 'datetype'=>'int', 'timetype'=>'int', 'timezone='=>'int', 'calendar='=>'int|IntlCalendar', 'pattern='=>'string'],
'datefmt_format' => ['string', 'fmt'=>'IntlDateFormatter', 'value'=>'DateTime|IntlCalendar|array|int'],
'datefmt_format_object' => ['string', 'object'=>'object', 'format='=>'mixed', 'locale='=>'string'],
'datefmt_get_calendar' => ['int', 'fmt'=>'IntlDateFormatter'],
'datefmt_get_calendar_object' => ['IntlCalendar', 'fmt'=>'IntlDateFormatter'],
'datefmt_get_datetype' => ['int', 'fmt'=>'IntlDateFormatter'],
'datefmt_get_error_code' => ['int', 'fmt'=>'IntlDateFormatter'],
'datefmt_get_error_message' => ['string', 'fmt'=>'IntlDateFormatter'],
'datefmt_get_locale' => ['string', 'fmt'=>'IntlDateFormatter', 'which='=>'int'],
'datefmt_get_pattern' => ['string', 'fmt'=>'IntlDateFormatter'],
'datefmt_get_timetype' => ['int', 'fmt'=>'IntlDateFormatter'],
'datefmt_get_timezone' => ['IntlTimeZone'],
'datefmt_get_timezone_id' => ['string', 'fmt'=>'IntlDateFormatter'],
'datefmt_is_lenient' => ['bool', 'fmt'=>'IntlDateFormatter'],
'datefmt_localtime' => ['array|bool', 'fmt'=>'IntlDateFormatter', 'text_to_parse='=>'string', '&rw_parse_pos='=>'int'],
'datefmt_parse' => ['int|false', 'fmt'=>'IntlDateFormatter', 'text_to_parse='=>'string', '&rw_parse_pos='=>'int'],
'datefmt_set_calendar' => ['bool', 'fmt'=>'IntlDateFormatter', 'which'=>'int'],
'datefmt_set_lenient' => ['?bool', 'fmt'=>'IntlDateFormatter', 'lenient'=>'bool'],
'datefmt_set_pattern' => ['bool', 'fmt'=>'IntlDateFormatter', 'pattern'=>'string'],
'datefmt_set_timezone' => ['bool', 'zone'=>'mixed'],
'datefmt_set_timezone_id' => ['bool', 'fmt'=>'IntlDateFormatter', 'zone'=>'string'],
'DateInterval::__construct' => ['void', 'spec'=>'string'],
'DateInterval::__set_state' => ['DateInterval', 'array'=>'array'],
'DateInterval::__wakeup' => ['void'],
'DateInterval::createFromDateString' => ['DateInterval', 'time'=>'string'],
'DateInterval::format' => ['string', 'format'=>'string'],
'DatePeriod::__construct' => ['void', 'start'=>'DateTimeInterface', 'interval'=>'DateInterval', 'recur'=>'int', 'options='=>'int'],
'DatePeriod::__construct\'1' => ['void', 'start'=>'DateTimeInterface', 'interval'=>'DateInterval', 'end'=>'DateTimeInterface', 'options='=>'int'],
'DatePeriod::__construct\'2' => ['void', 'iso'=>'string', 'options='=>'int'],
'DatePeriod::__wakeup' => ['void'],
'DatePeriod::getDateInterval' => ['DateInterval'],
'DatePeriod::getEndDate' => ['DateTimeInterface'],
'DatePeriod::getStartDate' => ['DateTimeInterface'],
'DateTime::__construct' => ['void', 'time='=>'string', 'timezone='=>'?DateTimeZone'],
'DateTime::__set_state' => ['static', 'array'=>'array'],
'DateTime::__wakeup' => ['void'],
'DateTime::add' => ['static', 'interval'=>'DateInterval'],
'DateTime::createFromFormat' => ['static|false', 'format'=>'string', 'time'=>'string', 'timezone='=>'DateTimeZone'],
'DateTime::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'],
'DateTime::format' => ['string', 'format'=>'string'],
'DateTime::getLastErrors' => ['array'],
'DateTime::getOffset' => ['int'],
'DateTime::getTimestamp' => ['int'],
'DateTime::getTimezone' => ['DateTimeZone'],
'DateTime::modify' => ['static', 'modify'=>'string'],
'DateTime::setDate' => ['static', 'year'=>'int', 'month'=>'int', 'day'=>'int'],
'DateTime::setISODate' => ['static', 'year'=>'int', 'week'=>'int', 'day='=>'int'],
'DateTime::setTime' => ['static', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'],
'DateTime::setTimestamp' => ['static', 'unixtimestamp'=>'int'],
'DateTime::setTimezone' => ['static', 'timezone'=>'DateTimeZone'],
'DateTime::sub' => ['static', 'interval'=>'DateInterval'],
'DateTimeImmutable::__construct' => ['void', 'time='=>'string', 'timezone='=>'?DateTimeZone'],
'DateTimeImmutable::__set_state' => ['static', 'array'=>'array'],
'DateTimeImmutable::__wakeup' => ['void'],
'DateTimeImmutable::add' => ['static', 'interval'=>'DateInterval'],
'DateTimeImmutable::createFromFormat' => ['static', 'format'=>'string', 'time'=>'string', 'timezone='=>'DateTimeZone'],
'DateTimeImmutable::createFromMutable' => ['static', 'datetime'=>'DateTime'],
'DateTimeImmutable::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'],
'DateTimeImmutable::format' => ['string', 'format'=>'string'],
'DateTimeImmutable::getLastErrors' => ['array'],
'DateTimeImmutable::getOffset' => ['int'],
'DateTimeImmutable::getTimestamp' => ['int'],
'DateTimeImmutable::getTimezone' => ['DateTimeZone'],
'DateTimeImmutable::modify' => ['static', 'modify'=>'string'],
'DateTimeImmutable::setDate' => ['static', 'year'=>'int', 'month'=>'int', 'day'=>'int'],
'DateTimeImmutable::setISODate' => ['static', 'year'=>'int', 'week'=>'int', 'day='=>'int'],
'DateTimeImmutable::setTime' => ['static', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'],
'DateTimeImmutable::setTimestamp' => ['static', 'unixtimestamp'=>'int'],
'DateTimeImmutable::setTimezone' => ['static', 'timezone'=>'DateTimeZone'],
'DateTimeImmutable::sub' => ['static', 'interval'=>'DateInterval'],
'DateTimeInterface::diff' => ['DateInterval', 'datetime2'=>'DateTimeInterface', 'absolute='=>'bool'],
'DateTimeInterface::format' => ['string', 'format'=>'string'],
'DateTimeInterface::getOffset' => ['int'],
'DateTimeInterface::getTimestamp' => ['int'],
'DateTimeInterface::getTimezone' => ['DateTimeZone'],
'DateTimeZone::__construct' => ['void', 'timezone'=>'string'],
'DateTimeZone::__set_state' => ['DateTimeZone', 'array'=>'array'],
'DateTimeZone::__wakeup' => ['void'],
'DateTimeZone::getLocation' => ['array'],
'DateTimeZone::getName' => ['string'],
'DateTimeZone::getOffset' => ['int', 'datetime'=>'DateTimeInterface'],
'DateTimeZone::getTransitions' => ['array', 'timestamp_begin='=>'int', 'timestamp_end='=>'int'],
'DateTimeZone::listAbbreviations' => ['array'],
'DateTimeZone::listIdentifiers' => ['array', 'what='=>'int', 'country='=>'string'],
'db2_autocommit' => ['mixed', 'connection'=>'resource', 'value='=>'bool'],
'db2_bind_param' => ['bool', 'stmt'=>'resource', 'parameter-number'=>'int', 'variable-name'=>'string', 'parameter-type='=>'int', 'data-type='=>'int', 'precision='=>'int', 'scale='=>'int'],
'db2_client_info' => ['object', 'connection'=>'resource'],
'db2_close' => ['bool', 'connection'=>'resource'],
'db2_column_privileges' => ['resource', 'connection'=>'resource', 'qualifier='=>'string', 'schema='=>'string', 'table-name='=>'string', 'column-name='=>'string'],
'db2_columns' => ['resource', 'connection'=>'resource', 'qualifier='=>'string', 'schema='=>'string', 'table-name='=>'string', 'column-name='=>'string'],
'db2_commit' => ['bool', 'connection'=>'resource'],
'db2_conn_error' => ['string', 'connection='=>'resource'],
'db2_conn_errormsg' => ['string', 'connection='=>'resource'],
'db2_connect' => ['resource', 'database'=>'string', 'username'=>'string', 'password'=>'string', 'options='=>'array'],
'db2_cursor_type' => ['int', 'stmt'=>'resource'],
'db2_escape_string' => ['string', 'string_literal'=>'string'],
'db2_exec' => ['resource', 'connection'=>'resource', 'statement'=>'string', 'options='=>'array'],
'db2_execute' => ['bool', 'stmt'=>'resource', 'parameters='=>'array'],
'db2_fetch_array' => ['array', 'stmt'=>'resource', 'row_number='=>'int'],
'db2_fetch_assoc' => ['array', 'stmt'=>'resource', 'row_number='=>'int'],
'db2_fetch_both' => ['array', 'stmt'=>'resource', 'row_number='=>'int'],
'db2_fetch_object' => ['object', 'stmt'=>'resource', 'row_number='=>'int'],
'db2_fetch_row' => ['bool', 'stmt'=>'resource', 'row_number='=>'int'],
'db2_field_display_size' => ['int', 'stmt'=>'resource', 'column'=>'mixed'],
'db2_field_name' => ['string', 'stmt'=>'resource', 'column'=>'mixed'],
'db2_field_num' => ['int', 'stmt'=>'resource', 'column'=>'mixed'],
'db2_field_precision' => ['int', 'stmt'=>'resource', 'column'=>'mixed'],
'db2_field_scale' => ['int', 'stmt'=>'resource', 'column'=>'mixed'],
'db2_field_type' => ['string', 'stmt'=>'resource', 'column'=>'mixed'],
'db2_field_width' => ['int', 'stmt'=>'resource', 'column'=>'mixed'],
'db2_foreign_keys' => ['resource', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'table-name'=>'string'],
'db2_free_result' => ['bool', 'stmt'=>'resource'],
'db2_free_stmt' => ['bool', 'stmt'=>'resource'],
'db2_get_option' => ['string', 'resource'=>'resource', 'option'=>'string'],
'db2_last_insert_id' => ['string', 'resource'=>'resource'],
'db2_lob_read' => ['string', 'stmt'=>'resource', 'colnum'=>'int', 'length'=>'int'],
'db2_next_result' => ['resource', 'stmt'=>'resource'],
'db2_num_fields' => ['int', 'stmt'=>'resource'],
'db2_num_rows' => ['bool', 'stmt'=>'resource'],
'db2_pclose' => ['bool', 'resource'=>'resource'],
'db2_pconnect' => ['resource', 'database'=>'string', 'username'=>'string', 'password'=>'string', 'options='=>'array'],
'db2_prepare' => ['resource', 'connection'=>'resource', 'statement'=>'string', 'options='=>'array'],
'db2_primary_keys' => ['resource', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'table-name'=>'string'],
'db2_primarykeys' => [''],
'db2_procedure_columns' => ['resource', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'procedure'=>'string', 'parameter'=>'string'],
'db2_procedurecolumns' => [''],
'db2_procedures' => ['resource', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'procedure'=>'string'],
'db2_result' => ['mixed', 'stmt'=>'resource', 'column'=>'mixed'],
'db2_rollback' => ['bool', 'connection'=>'resource'],
'db2_server_info' => ['object', 'connection'=>'resource'],
'db2_set_option' => ['bool', 'resource'=>'resource', 'options'=>'array', 'type'=>'int'],
'db2_setoption' => [''],
'db2_special_columns' => ['resource', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'table_name'=>'string', 'scope'=>'int'],
'db2_specialcolumns' => [''],
'db2_statistics' => ['resource', 'connection'=>'resource', 'qualifier'=>'string', 'schema'=>'string', 'table-name'=>'string', 'unique'=>'bool'],
'db2_stmt_error' => ['string', 'stmt='=>'resource'],
'db2_stmt_errormsg' => ['string', 'stmt='=>'resource'],
'db2_table_privileges' => ['resource', 'connection'=>'resource', 'qualifier='=>'string', 'schema='=>'string', 'table_name='=>'string'],
'db2_tableprivileges' => [''],
'db2_tables' => ['resource', 'connection'=>'resource', 'qualifier='=>'string', 'schema='=>'string', 'table-name='=>'string', 'table-type='=>'string'],
'dba_close' => ['void', 'handle'=>'resource'],
'dba_delete' => ['bool', 'key'=>'string', 'handle'=>'resource'],
'dba_exists' => ['bool', 'key'=>'string', 'handle'=>'resource'],
'dba_fetch' => ['string', 'key'=>'string', 'skip'=>'int', 'handle'=>'resource'],
'dba_fetch\'1' => ['string', 'key'=>'string', 'handle'=>'resource'],
'dba_firstkey' => ['string', 'handle'=>'resource'],
'dba_handlers' => ['array', 'full_info='=>'bool'],
'dba_insert' => ['bool', 'key'=>'string', 'value'=>'string', 'handle'=>'resource'],
'dba_key_split' => ['array|false', 'key'=>'string'],
'dba_list' => ['array'],
'dba_nextkey' => ['string', 'handle'=>'resource'],
'dba_open' => ['resource', 'path'=>'string', 'mode'=>'string', 'handlername='=>'string', '...args='=>'string'],
'dba_optimize' => ['bool', 'handle'=>'resource'],
'dba_popen' => ['resource', 'path'=>'string', 'mode'=>'string', 'handlername='=>'string', '...args='=>'string'],
'dba_replace' => ['bool', 'key'=>'string', 'value'=>'string', 'handle'=>'resource'],
'dba_sync' => ['bool', 'handle'=>'resource'],
'dbase_add_record' => ['bool', 'dbase_identifier'=>'int', 'record'=>'array'],
'dbase_close' => ['bool', 'dbase_identifier'=>'int'],
'dbase_create' => ['int', 'filename'=>'string', 'fields'=>'array'],
'dbase_delete_record' => ['bool', 'dbase_identifier'=>'int', 'record_number'=>'int'],
'dbase_get_header_info' => ['array', 'dbase_identifier'=>'int'],
'dbase_get_record' => ['array', 'dbase_identifier'=>'int', 'record_number'=>'int'],
'dbase_get_record_with_names' => ['array', 'dbase_identifier'=>'int', 'record_number'=>'int'],
'dbase_numfields' => ['int', 'dbase_identifier'=>'int'],
'dbase_numrecords' => ['int', 'dbase_identifier'=>'int'],
'dbase_open' => ['int', 'filename'=>'string', 'mode'=>'int'],
'dbase_pack' => ['bool', 'dbase_identifier'=>'int'],
'dbase_replace_record' => ['bool', 'dbase_identifier'=>'int', 'record'=>'array', 'record_number'=>'int'],
'dbplus_add' => ['int', 'relation'=>'resource', 'tuple'=>'array'],
'dbplus_aql' => ['resource', 'query'=>'string', 'server='=>'string', 'dbpath='=>'string'],
'dbplus_chdir' => ['string', 'newdir='=>'string'],
'dbplus_close' => ['mixed', 'relation'=>'resource'],
'dbplus_curr' => ['int', 'relation'=>'resource', 'tuple'=>'array'],
'dbplus_errcode' => ['string', 'errno='=>'int'],
'dbplus_errno' => ['int'],
'dbplus_find' => ['int', 'relation'=>'resource', 'constraints'=>'array', 'tuple'=>'mixed'],
'dbplus_first' => ['int', 'relation'=>'resource', 'tuple'=>'array'],
'dbplus_flush' => ['int', 'relation'=>'resource'],
'dbplus_freealllocks' => ['int'],
'dbplus_freelock' => ['int', 'relation'=>'resource', 'tuple'=>'string'],
'dbplus_freerlocks' => ['int', 'relation'=>'resource'],
'dbplus_getlock' => ['int', 'relation'=>'resource', 'tuple'=>'string'],
'dbplus_getunique' => ['int', 'relation'=>'resource', 'uniqueid'=>'int'],
'dbplus_info' => ['int', 'relation'=>'resource', 'key'=>'string', 'result'=>'array'],
'dbplus_last' => ['int', 'relation'=>'resource', 'tuple'=>'array'],
'dbplus_lockrel' => ['int', 'relation'=>'resource'],
'dbplus_next' => ['int', 'relation'=>'resource', 'tuple'=>'array'],
'dbplus_open' => ['resource', 'name'=>'string'],
'dbplus_prev' => ['int', 'relation'=>'resource', 'tuple'=>'array'],
'dbplus_rchperm' => ['int', 'relation'=>'resource', 'mask'=>'int', 'user'=>'string', 'group'=>'string'],
'dbplus_rcreate' => ['resource', 'name'=>'string', 'domlist'=>'mixed', 'overwrite='=>'bool'],
'dbplus_rcrtexact' => ['mixed', 'name'=>'string', 'relation'=>'resource', 'overwrite='=>'bool'],
'dbplus_rcrtlike' => ['mixed', 'name'=>'string', 'relation'=>'resource', 'overwrite='=>'int'],
'dbplus_resolve' => ['array', 'relation_name'=>'string'],
'dbplus_restorepos' => ['int', 'relation'=>'resource', 'tuple'=>'array'],
'dbplus_rkeys' => ['mixed', 'relation'=>'resource', 'domlist'=>'mixed'],
'dbplus_ropen' => ['resource', 'name'=>'string'],
'dbplus_rquery' => ['resource', 'query'=>'string', 'dbpath='=>'string'],
'dbplus_rrename' => ['int', 'relation'=>'resource', 'name'=>'string'],
'dbplus_rsecindex' => ['mixed', 'relation'=>'resource', 'domlist'=>'mixed', 'type'=>'int'],
'dbplus_runlink' => ['int', 'relation'=>'resource'],
'dbplus_rzap' => ['int', 'relation'=>'resource'],
'dbplus_savepos' => ['int', 'relation'=>'resource'],
'dbplus_setindex' => ['int', 'relation'=>'resource', 'idx_name'=>'string'],
'dbplus_setindexbynumber' => ['int', 'relation'=>'resource', 'idx_number'=>'int'],
'dbplus_sql' => ['resource', 'query'=>'string', 'server='=>'string', 'dbpath='=>'string'],
'dbplus_tcl' => ['string', 'sid'=>'int', 'script'=>'string'],
'dbplus_tremove' => ['int', 'relation'=>'resource', 'tuple'=>'array', 'current='=>'array'],
'dbplus_undo' => ['int', 'relation'=>'resource'],
'dbplus_undoprepare' => ['int', 'relation'=>'resource'],
'dbplus_unlockrel' => ['int', 'relation'=>'resource'],
'dbplus_unselect' => ['int', 'relation'=>'resource'],
'dbplus_update' => ['int', 'relation'=>'resource', 'old'=>'array', 'new'=>'array'],
'dbplus_xlockrel' => ['int', 'relation'=>'resource'],
'dbplus_xunlockrel' => ['int', 'relation'=>'resource'],
'dbx_close' => ['int', 'link_identifier'=>'object'],
'dbx_compare' => ['int', 'row_a'=>'array', 'row_b'=>'array', 'column_key'=>'string', 'flags='=>'int'],
'dbx_connect' => ['object', 'module'=>'mixed', 'host'=>'string', 'database'=>'string', 'username'=>'string', 'password'=>'string', 'persistent='=>'int'],
'dbx_error' => ['string', 'link_identifier'=>'object'],
'dbx_escape_string' => ['string', 'link_identifier'=>'object', 'text'=>'string'],
'dbx_fetch_row' => ['mixed', 'result_identifier'=>'object'],
'dbx_query' => ['mixed', 'link_identifier'=>'object', 'sql_statement'=>'string', 'flags='=>'int'],
'dbx_sort' => ['bool', 'result'=>'object', 'user_compare_function'=>'string'],
'dcgettext' => ['string', 'domain_name'=>'string', 'msgid'=>'string', 'category'=>'int'],
'dcngettext' => ['string', 'domain'=>'string', 'msgid1'=>'string', 'msgid2'=>'string', 'n'=>'int', 'category'=>'int'],
'deaggregate' => ['', 'object'=>'object', 'class_name='=>'string'],
'debug_backtrace' => ['array<int,array>', 'options='=>'int|bool', 'limit='=>'int'],
'debug_print_backtrace' => ['void', 'options='=>'int|bool', 'limit='=>'int'],
'debug_zval_dump' => ['void', '...var'=>'mixed'],
'debugger_connect' => [''],
'debugger_connector_pid' => [''],
'debugger_get_server_start_time' => [''],
'debugger_print' => [''],
'debugger_start_debug' => [''],
'decbin' => ['string', 'decimal_number'=>'int'],
'dechex' => ['string', 'decimal_number'=>'int'],
'decoct' => ['string', 'decimal_number'=>'int'],
'define' => ['bool', 'constant_name'=>'string', 'value'=>'mixed', 'case_insensitive='=>'bool'],
'define_syslog_variables' => ['void'],
'defined' => ['bool', 'name'=>'string'],
'deflate_add' => ['string', 'context'=>'resource', 'data'=>'string', 'flush_mode='=>'int'],
'deflate_init' => ['resource', 'encoding'=>'int', 'options='=>'array'],
'deg2rad' => ['float', 'number'=>'float'],
'dgettext' => ['string', 'domain_name'=>'string', 'msgid'=>'string'],
'dio_close' => ['void', 'fd'=>'resource'],
'dio_fcntl' => ['mixed', 'fd'=>'resource', 'cmd'=>'int', 'args='=>'mixed'],
'dio_open' => ['resource', 'filename'=>'string', 'flags'=>'int', 'mode='=>'int'],
'dio_read' => ['string', 'fd'=>'resource', 'len='=>'int'],
'dio_seek' => ['int', 'fd'=>'resource', 'pos'=>'int', 'whence='=>'int'],
'dio_stat' => ['array', 'fd'=>'resource'],
'dio_tcsetattr' => ['bool', 'fd'=>'resource', 'options'=>'array'],
'dio_truncate' => ['bool', 'fd'=>'resource', 'offset'=>'int'],
'dio_write' => ['int', 'fd'=>'resource', 'data'=>'string', 'len='=>'int'],
'dir' => ['object', 'directory'=>'string', 'context='=>'resource'],
'Directory::close' => ['void', 'dir_handle='=>'resource'],
'Directory::read' => ['string', 'dir_handle='=>'resource'],
'Directory::rewind' => ['void', 'dir_handle='=>'resource'],
'DirectoryIterator::__construct' => ['void', 'path'=>'string'],
'DirectoryIterator::__toString' => ['string'],
'DirectoryIterator::current' => ['DirectoryIterator'],
'DirectoryIterator::getATime' => ['int'],
'DirectoryIterator::getBasename' => ['string', 'suffix='=>'string'],
'DirectoryIterator::getChildren' => ['RecursiveDirectoryIterator'],
'DirectoryIterator::getCTime' => ['int'],
'DirectoryIterator::getExtension' => ['string'],
'DirectoryIterator::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'],
'DirectoryIterator::getFilename' => ['string'],
'DirectoryIterator::getGroup' => ['int'],
'DirectoryIterator::getInode' => ['int'],
'DirectoryIterator::getLinkTarget' => ['string'],
'DirectoryIterator::getMTime' => ['int'],
'DirectoryIterator::getOwner' => ['int'],
'DirectoryIterator::getPath' => ['string'],
'DirectoryIterator::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'],
'DirectoryIterator::getPathname' => ['string'],
'DirectoryIterator::getPerms' => ['int'],
'DirectoryIterator::getRealPath' => ['string'],
'DirectoryIterator::getSize' => ['int'],
'DirectoryIterator::getType' => ['string'],
'DirectoryIterator::isDir' => ['bool'],
'DirectoryIterator::isDot' => ['bool'],
'DirectoryIterator::isExecutable' => ['bool'],
'DirectoryIterator::isFile' => ['bool'],
'DirectoryIterator::isLink' => ['bool'],
'DirectoryIterator::isReadable' => ['bool'],
'DirectoryIterator::isWritable' => ['bool'],
'DirectoryIterator::key' => ['string'],
'DirectoryIterator::next' => ['void'],
'DirectoryIterator::openFile' => ['SplFileObject', 'mode='=>'string', 'use_include_path='=>'bool', 'context='=>'resource'],
'DirectoryIterator::rewind' => ['void'],
'DirectoryIterator::seek' => ['void', 'position'=>'int'],
'DirectoryIterator::setFileClass' => ['void', 'class_name='=>'string'],
'DirectoryIterator::setInfoClass' => ['void', 'class_name='=>'string'],
'DirectoryIterator::valid' => ['bool'],
'dirname' => ['string', 'path'=>'string', 'levels='=>'int'],
'disk_free_space' => ['float|false', 'path'=>'string'],
'disk_total_space' => ['float', 'path'=>'string'],
'diskfreespace' => ['', 'path'=>''],
'display_disabled_function' => [''],
'dl' => ['int', 'extension_filename'=>'string'],
'dngettext' => ['string', 'domain'=>'string', 'msgid1'=>'string', 'msgid2'=>'string', 'count'=>'int'],
'dns_check_record' => ['bool', 'host'=>'string', 'type'=>'string'],
'dns_get_mx' => ['bool', 'hostname'=>'string', '&w_mxhosts'=>'array', '&w_weight'=>'array'],
'dns_get_record' => ['array|false', 'hostname'=>'string', 'type='=>'int', '&w_authns='=>'array', '&w_addtl='=>'array', 'raw='=>'bool'],
'dom_document_relaxNG_validate_file' => ['bool', 'filename'=>'string'],
'dom_document_relaxNG_validate_xml' => ['bool', 'source'=>'string'],
'dom_document_schema_validate' => ['bool', 'source'=>'string', 'flags'=>'int'],
'dom_document_schema_validate_file' => ['bool', 'filename'=>'string', 'flags'=>'int'],
'dom_document_xinclude' => ['int', 'options'=>'int'],
'dom_import_simplexml' => ['DOMElement|false', 'node'=>'SimpleXMLElement'],
'dom_xpath_evaluate' => ['', 'expr'=>'string', 'context'=>'DOMNode', 'registernodens'=>'bool'],
'dom_xpath_query' => ['DOMNodeList', 'expr'=>'string', 'context'=>'DOMNode', 'registernodens'=>'bool'],
'dom_xpath_register_ns' => ['bool', 'prefix'=>'string', 'uri'=>'string'],
'dom_xpath_register_php_functions' => [''],
'DomainException::__clone' => ['void'],
'DomainException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?DomainException'],
'DomainException::__toString' => ['string'],
'DomainException::__wakeup' => ['void'],
'DomainException::getCode' => ['int'],
'DomainException::getFile' => ['string'],
'DomainException::getLine' => ['int'],
'DomainException::getMessage' => ['string'],
'DomainException::getPrevious' => ['Throwable|DomainException'],
'DomainException::getTrace' => ['array'],
'DomainException::getTraceAsString' => ['string'],
'DOMAttr::__construct' => ['void', 'name'=>'string', 'value='=>'string'],
'DOMAttr::isId' => ['bool'],
'DomAttribute::name' => ['string'],
'DomAttribute::set_value' => ['bool', 'content'=>'string'],
'DomAttribute::specified' => ['bool'],
'DomAttribute::value' => ['string'],
'DOMCdataSection::__construct' => ['void', 'value'=>'string'],
'DOMCharacterData::appendData' => ['void', 'data'=>'string'],
'DOMCharacterData::deleteData' => ['void', 'offset'=>'int', 'count'=>'int'],
'DOMCharacterData::insertData' => ['void', 'offset'=>'int', 'data'=>'string'],
'DOMCharacterData::replaceData' => ['void', 'offset'=>'int', 'count'=>'int', 'data'=>'string'],
'DOMCharacterData::substringData' => ['string', 'offset'=>'int', 'count'=>'int'],
'DOMComment::__construct' => ['void', 'value='=>'string'],
'DOMDocument::__construct' => ['void', 'version='=>'string', 'encoding='=>'string'],
'DOMDocument::createAttribute' => ['DOMAttr', 'name'=>'string'],
'DOMDocument::createAttributeNS' => ['DOMAttr', 'namespaceuri'=>'string', 'qualifiedname'=>'string'],
'DOMDocument::createCDATASection' => ['DOMCDATASection', 'data'=>'string'],
'DOMDocument::createComment' => ['DOMComment', 'data'=>'string'],
'DOMDocument::createDocumentFragment' => ['DOMDocumentFragment'],
'DOMDocument::createElement' => ['DOMElement', 'name'=>'string', 'value='=>'string'],
'DOMDocument::createElementNS' => ['DOMElement', 'namespaceuri'=>'string', 'qualifiedname'=>'string', 'value='=>'string'],
'DOMDocument::createEntityReference' => ['DOMEntityReference', 'name'=>'string'],
'DOMDocument::createProcessingInstruction' => ['DOMProcessingInstruction', 'target'=>'string', 'data='=>'string'],
'DOMDocument::createTextNode' => ['DOMText', 'content'=>'string'],
'DOMDocument::getElementById' => ['DOMElement', 'elementid'=>'string'],
'DOMDocument::getElementsByTagName' => ['DOMNodeList', 'name'=>'string'],
'DOMDocument::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string', 'localname'=>'string'],
'DOMDocument::importNode' => ['DOMNode', 'importednode'=>'DOMNode', 'deep='=>'bool'],
'DOMDocument::load' => ['mixed', 'filename'=>'string', 'options='=>'int'],
'DOMDocument::loadHTML' => ['bool', 'source'=>'string', 'options='=>'int'],
'DOMDocument::loadHTMLFile' => ['bool', 'filename'=>'string', 'options='=>'int'],
'DOMDocument::loadXML' => ['mixed', 'source'=>'string', 'options='=>'int'],
'DOMDocument::normalizeDocument' => ['void'],
'DOMDocument::registerNodeClass' => ['bool', 'baseclass'=>'string', 'extendedclass'=>'string'],
'DOMDocument::relaxNGValidate' => ['bool', 'filename'=>'string'],
'DOMDocument::relaxNGValidateSource' => ['bool', 'source'=>'string'],
'DOMDocument::save' => ['int', 'filename'=>'string', 'options='=>'int'],
'DOMDocument::saveHTML' => ['string', 'node='=>'DOMNode'],
'DOMDocument::saveHTMLFile' => ['int', 'filename'=>'string'],
'DOMDocument::saveXML' => ['string', 'node='=>'DOMNode', 'options='=>'int'],
'DOMDocument::schemaValidate' => ['bool', 'filename'=>'string'],
'DOMDocument::schemaValidateSource' => ['bool', 'source'=>'string'],
'DOMDocument::validate' => ['bool'],
'DOMDocument::xinclude' => ['int', 'options='=>'int'],
'DOMDocumentFragment::__construct' => ['void'],
'DOMDocumentFragment::appendXML' => ['bool', 'data'=>'string'],
'DomDocumentType::entities' => ['array'],
'DomDocumentType::internal_subset' => ['bool'],
'DomDocumentType::name' => ['string'],
'DomDocumentType::notations' => ['array'],
'DomDocumentType::public_id' => ['string'],
'DomDocumentType::system_id' => ['string'],
'DOMElement::__construct' => ['void', 'name'=>'string', 'value='=>'string', 'uri='=>'string'],
'DOMElement::get_attribute' => ['string', 'name'=>'string'],
'DOMElement::get_attribute_node' => ['DomAttribute', 'name'=>'string'],
'DOMElement::get_elements_by_tagname' => ['array', 'name'=>'string'],
'DOMElement::getAttribute' => ['string', 'name'=>'string'],
'DOMElement::getAttributeNode' => ['DOMAttr', 'name'=>'string'],
'DOMElement::getAttributeNodeNS' => ['DOMAttr', 'namespaceuri'=>'string', 'localname'=>'string'],
'DOMElement::getAttributeNS' => ['string', 'namespaceuri'=>'string', 'localname'=>'string'],
'DOMElement::getElementsByTagName' => ['DOMNodeList', 'name'=>'string'],
'DOMElement::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string', 'localname'=>'string'],
'DOMElement::has_attribute' => ['bool', 'name'=>'string'],
'DOMElement::hasAttribute' => ['bool', 'name'=>'string'],
'DOMElement::hasAttributeNS' => ['bool', 'namespaceuri'=>'string', 'localname'=>'string'],
'DOMElement::remove_attribute' => ['bool', 'name'=>'string'],
'DOMElement::removeAttribute' => ['bool', 'name'=>'string'],
'DOMElement::removeAttributeNode' => ['bool', 'oldnode'=>'DOMAttr'],
'DOMElement::removeAttributeNS' => ['bool', 'namespaceuri'=>'string', 'localname'=>'string'],
'DOMElement::set_attribute' => ['DomAttribute', 'name'=>'string', 'value'=>'string'],
'DOMElement::set_attribute_node' => ['DomNode', 'attr'=>'DOMNode'],
'DOMElement::setAttribute' => ['DOMAttr', 'name'=>'string', 'value'=>'string'],
'DOMElement::setAttributeNode' => ['DOMAttr', 'attr'=>'DOMAttr'],
'DOMElement::setAttributeNodeNS' => ['DOMAttr', 'attr'=>'DOMAttr'],
'DOMElement::setAttributeNS' => ['void', 'namespaceuri'=>'string', 'qualifiedname'=>'string', 'value'=>'string'],
'DOMElement::setIdAttribute' => ['void', 'name'=>'string', 'isid'=>'bool'],
'DOMElement::setIdAttributeNode' => ['void', 'attr'=>'DOMAttr', 'isid'=>'bool'],
'DOMElement::setIdAttributeNS' => ['void', 'namespaceuri'=>'string', 'localname'=>'string', 'isid'=>'bool'],
'DOMElement::tagname' => ['string'],
'DOMEntityReference::__construct' => ['void', 'name'=>'string'],
'DOMImplementation::__construct' => ['void'],
'DOMImplementation::createDocument' => ['DOMDocument', 'namespaceuri='=>'string', 'qualifiedname='=>'string', 'doctype='=>'DOMDocumentType'],
'DOMImplementation::createDocumentType' => ['DOMDocumentType', 'qualifiedname='=>'string', 'publicid='=>'string', 'systemid='=>'string'],
'DOMImplementation::hasFeature' => ['bool', 'feature'=>'string', 'version'=>'string'],
'DOMNamedNodeMap::getNamedItem' => ['DOMNode', 'name'=>'string'],
'DOMNamedNodeMap::getNamedItemNS' => ['DOMNode', 'namespaceuri'=>'string', 'localname'=>'string'],
'DOMNamedNodeMap::item' => ['DOMNode', 'index'=>'int'],
'DomNode::add_namespace' => ['bool', 'uri'=>'string', 'prefix'=>'string'],
'DomNode::append_child' => ['DOMNode', 'newnode'=>'DOMNode'],
'DOMNode::appendChild' => ['DOMNode', 'newnode'=>'DOMNode'],
'DOMNode::C14N' => ['string', 'exclusive='=>'bool', 'with_comments='=>'bool', 'xpath='=>'array', 'ns_prefixes='=>'array'],
'DOMNode::C14NFile' => ['int', 'uri='=>'string', 'exclusive='=>'bool', 'with_comments='=>'bool', 'xpath='=>'array', 'ns_prefixes='=>'array'],
'DOMNode::cloneNode' => ['DOMNode', 'deep='=>'bool'],
'DOMNode::getLineNo' => ['int'],
'DOMNode::getNodePath' => ['?string'],
'DOMNode::hasAttributes' => ['bool'],
'DOMNode::hasChildNodes' => ['bool'],
'DOMNode::insertBefore' => ['DOMNode', 'newnode'=>'DOMNode', 'refnode='=>'DOMNode'],
'DOMNode::isDefaultNamespace' => ['bool', 'namespaceuri'=>'string'],
'DOMNode::isSameNode' => ['bool', 'node'=>'DOMNode'],
'DOMNode::isSupported' => ['bool', 'feature'=>'string', 'version'=>'string'],
'DOMNode::lookupNamespaceURI' => ['string', 'prefix'=>'string'],
'DOMNode::lookupPrefix' => ['string', 'namespaceuri'=>'string'],
'DOMNode::normalize' => ['void'],
'DOMNode::removeChild' => ['DOMNode', 'oldnode'=>'DOMNode'],
'DOMNode::replaceChild' => ['DOMNode', 'newnode'=>'DOMNode', 'oldnode'=>'DOMNode'],
'DOMNodeList::count' => ['int'],
'DOMNodeList::item' => ['?DOMNode', 'index'=>'int'],
'DOMProcessingInstruction::__construct' => ['void', 'name'=>'string', 'value'=>'string'],
'DomProcessingInstruction::data' => ['string'],
'DomProcessingInstruction::target' => ['string'],
'DOMText::__construct' => ['void', 'value='=>'string'],
'DOMText::isElementContentWhitespace' => ['bool'],
'DOMText::isWhitespaceInElementContent' => ['bool'],
'DOMText::splitText' => ['DOMText', 'offset'=>'int'],
'domxml_new_doc' => ['DomDocument', 'version'=>'string'],
'domxml_open_file' => ['DomDocument', 'filename'=>'string', 'mode='=>'int', 'error='=>'array'],
'domxml_open_mem' => ['DomDocument', 'str'=>'string', 'mode='=>'int', 'error='=>'array'],
'domxml_version' => ['string'],
'domxml_xmltree' => ['DomDocument', 'str'=>'string'],
'domxml_xslt_stylesheet' => ['DomXsltStylesheet', 'xsl_buf'=>'string'],
'domxml_xslt_stylesheet_doc' => ['DomXsltStylesheet', 'xsl_doc'=>'DOMDocument'],
'domxml_xslt_stylesheet_file' => ['DomXsltStylesheet', 'xsl_file'=>'string'],
'domxml_xslt_version' => ['int'],
'DOMXPath::__construct' => ['void', 'doc'=>'DOMDocument'],
'DOMXPath::evaluate' => ['mixed', 'expression'=>'string', 'contextnode='=>'DOMNode', 'registernodens='=>'bool'],
'DOMXPath::query' => ['DOMNodeList', 'expression'=>'string', 'contextnode='=>'DOMNode', 'registernodens='=>'bool'],
'DOMXPath::registerNamespace' => ['bool', 'prefix'=>'string', 'namespaceuri'=>'string'],
'DOMXPath::registerPhpFunctions' => ['void', 'restrict='=>'mixed'],
'DomXsltStylesheet::process' => ['DomDocument', 'xml_doc'=>'DOMDocument', 'xslt_params='=>'array', 'is_xpath_param='=>'bool', 'profile_filename='=>'string'],
'DomXsltStylesheet::result_dump_file' => ['string', 'xmldoc'=>'DOMDocument', 'filename'=>'string'],
'DomXsltStylesheet::result_dump_mem' => ['string', 'xmldoc'=>'DOMDocument'],
'DOTNET::__construct' => ['void', 'assembly_name'=>'string', 'class_name'=>'string', 'codepage='=>'int'],
'dotnet_load' => ['int', 'assembly_name'=>'string', 'datatype_name='=>'string', 'codepage='=>'int'],
'doubleval' => ['float', 'var'=>'mixed'],
'each' => ['array', '&rw_arr'=>'array'],
'easter_date' => ['int', 'year='=>'int'],
'easter_days' => ['int', 'year='=>'int', 'method='=>'int'],
'echo' => ['void', 'arg1'=>'string', '...args='=>'string'],
'eio_busy' => ['resource', 'delay'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_cancel' => ['void', 'req'=>'resource'],
'eio_chmod' => ['resource', 'path'=>'string', 'mode'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_chown' => ['resource', 'path'=>'string', 'uid'=>'int', 'gid='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_close' => ['resource', 'fd'=>'mixed', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_custom' => ['resource', 'execute'=>'callable', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'],
'eio_dup2' => ['resource', 'fd'=>'mixed', 'fd2'=>'mixed', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_event_loop' => ['bool'],
'eio_fallocate' => ['resource', 'fd'=>'mixed', 'mode'=>'int', 'offset'=>'int', 'length'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_fchmod' => ['resource', 'fd'=>'mixed', 'mode'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_fchown' => ['resource', 'fd'=>'mixed', 'uid'=>'int', 'gid='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_fdatasync' => ['resource', 'fd'=>'mixed', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_fstat' => ['resource', 'fd'=>'mixed', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'],
'eio_fstatvfs' => ['resource', 'fd'=>'mixed', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'],
'eio_fsync' => ['resource', 'fd'=>'mixed', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_ftruncate' => ['resource', 'fd'=>'mixed', 'offset='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_futime' => ['resource', 'fd'=>'mixed', 'atime'=>'float', 'mtime'=>'float', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_get_event_stream' => ['mixed'],
'eio_get_last_error' => ['string', 'req'=>'resource'],
'eio_grp' => ['resource', 'callback'=>'callable', 'data='=>'string'],
'eio_grp_add' => ['void', 'grp'=>'resource', 'req'=>'resource'],
'eio_grp_cancel' => ['void', 'grp'=>'resource'],
'eio_grp_limit' => ['void', 'grp'=>'resource', 'limit'=>'int'],
'eio_init' => ['void'],
'eio_link' => ['resource', 'path'=>'string', 'new_path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_lstat' => ['resource', 'path'=>'string', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'],
'eio_mkdir' => ['resource', 'path'=>'string', 'mode'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_mknod' => ['resource', 'path'=>'string', 'mode'=>'int', 'dev'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_nop' => ['resource', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_npending' => ['int'],
'eio_nready' => ['int'],
'eio_nreqs' => ['int'],
'eio_nthreads' => ['int'],
'eio_open' => ['resource', 'path'=>'string', 'flags'=>'int', 'mode'=>'int', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'],
'eio_poll' => ['int'],
'eio_read' => ['resource', 'fd'=>'mixed', 'length'=>'int', 'offset'=>'int', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'],
'eio_readahead' => ['resource', 'fd'=>'mixed', 'offset'=>'int', 'length'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_readdir' => ['resource', 'path'=>'string', 'flags'=>'int', 'pri'=>'int', 'callback'=>'callable', 'data='=>'string'],
'eio_readlink' => ['resource', 'path'=>'string', 'pri'=>'int', 'callback'=>'callable', 'data='=>'string'],
'eio_realpath' => ['resource', 'path'=>'string', 'pri'=>'int', 'callback'=>'callable', 'data='=>'string'],
'eio_rename' => ['resource', 'path'=>'string', 'new_path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_rmdir' => ['resource', 'path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_seek' => ['resource', 'fd'=>'mixed', 'offset'=>'int', 'whence'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_sendfile' => ['resource', 'out_fd'=>'mixed', 'in_fd'=>'mixed', 'offset'=>'int', 'length'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'string'],
'eio_set_max_idle' => ['void', 'nthreads'=>'int'],
'eio_set_max_parallel' => ['void', 'nthreads'=>'int'],
'eio_set_max_poll_reqs' => ['void', 'nreqs'=>'int'],
'eio_set_max_poll_time' => ['void', 'nseconds'=>'float'],
'eio_set_min_parallel' => ['void', 'nthreads'=>'string'],
'eio_stat' => ['resource', 'path'=>'string', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'],
'eio_statvfs' => ['resource', 'path'=>'string', 'pri'=>'int', 'callback'=>'callable', 'data='=>'mixed'],
'eio_symlink' => ['resource', 'path'=>'string', 'new_path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_sync' => ['resource', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_sync_file_range' => ['resource', 'fd'=>'mixed', 'offset'=>'int', 'nbytes'=>'int', 'flags'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_syncfs' => ['resource', 'fd'=>'mixed', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_truncate' => ['resource', 'path'=>'string', 'offset='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_unlink' => ['resource', 'path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_utime' => ['resource', 'path'=>'string', 'atime'=>'float', 'mtime'=>'float', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'eio_write' => ['resource', 'fd'=>'mixed', 'str'=>'string', 'length='=>'int', 'offset='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'],
'empty' => ['bool', 'var'=>'mixed'],
'EmptyIterator::current' => ['mixed'],
'EmptyIterator::key' => ['int|string|float|bool'],
'EmptyIterator::next' => ['void'],
'EmptyIterator::rewind' => ['void'],
'EmptyIterator::valid' => ['bool'],
'enchant_broker_describe' => ['array', 'broker'=>'resource'],
'enchant_broker_dict_exists' => ['bool', 'broker'=>'resource', 'tag'=>'string'],
'enchant_broker_free' => ['bool', 'broker'=>'resource'],
'enchant_broker_free_dict' => ['resource', 'dict'=>'resource'],
'enchant_broker_get_dict_path' => ['string', 'broker'=>'resource', 'dict_type'=>'int'],
'enchant_broker_get_error' => ['string', 'broker'=>'resource'],
'enchant_broker_init' => ['resource'],
'enchant_broker_list_dicts' => ['string', 'broker'=>'resource'],
'enchant_broker_request_dict' => ['resource', 'broker'=>'resource', 'tag'=>'string'],
'enchant_broker_request_pwl_dict' => ['resource', 'broker'=>'resource', 'filename'=>'string'],
'enchant_broker_set_dict_path' => ['bool', 'broker'=>'resource', 'dict_type'=>'int', 'value'=>'string'],
'enchant_broker_set_ordering' => ['bool', 'broker'=>'resource', 'tag'=>'string', 'ordering'=>'string'],
'enchant_dict_add_to_personal' => ['void', 'dict'=>'resource', 'word'=>'string'],
'enchant_dict_add_to_session' => ['void', 'dict'=>'resource', 'word'=>'string'],
'enchant_dict_check' => ['bool', 'dict'=>'resource', 'word'=>'string'],
'enchant_dict_describe' => ['array', 'dict'=>'resource'],
'enchant_dict_get_error' => ['string', 'dict'=>'resource'],
'enchant_dict_is_in_session' => ['bool', 'dict'=>'resource', 'word'=>'string'],
'enchant_dict_quick_check' => ['bool', 'dict'=>'resource', 'word'=>'string', 'suggestions='=>'array'],
'enchant_dict_store_replacement' => ['void', 'dict'=>'resource', 'mis'=>'string', 'cor'=>'string'],
'enchant_dict_suggest' => ['array', 'dict'=>'resource', 'word'=>'string'],
'end' => ['mixed', '&rw_array_arg'=>'array'],
'ereg' => ['int', 'pattern'=>'string', 'string'=>'string', 'regs='=>'array'],
'ereg_replace' => ['string', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string'],
'eregi' => ['int', 'pattern'=>'string', 'string'=>'string', 'regs='=>'array'],
'eregi_replace' => ['string', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string'],
'Error::__clone' => ['void'],
'Error::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?Error'],
'Error::__toString' => ['string'],
'Error::getCode' => ['int'],
'Error::getFile' => ['string'],
'Error::getLine' => ['int'],
'Error::getMessage' => ['string'],
'Error::getPrevious' => ['Throwable|Error'],
'Error::getTrace' => ['array'],
'Error::getTraceAsString' => ['string'],
'error_clear_last' => ['void'],
'error_get_last' => ['?array'],
'error_log' => ['bool', 'message'=>'string', 'message_type='=>'int', 'destination='=>'string', 'extra_headers='=>'string'],
'error_reporting' => ['int', 'new_error_level='=>'int'],
'ErrorException::__clone' => ['void'],
'ErrorException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'severity='=>'int', 'filename='=>'string', 'lineno='=>'int', 'previous='=>'?Throwable|?ErrorException'],
'ErrorException::__toString' => ['string'],
'ErrorException::getCode' => ['int'],
'ErrorException::getFile' => ['string'],
'ErrorException::getLine' => ['int'],
'ErrorException::getMessage' => ['string'],
'ErrorException::getPrevious' => ['Throwable|ErrorException'],
'ErrorException::getSeverity' => ['int'],
'ErrorException::getTrace' => ['array'],
'ErrorException::getTraceAsString' => ['string'],
'escapeshellarg' => ['string', 'arg'=>'string'],
'escapeshellcmd' => ['string', 'command'=>'string'],
'Ev::backend' => ['int'],
'Ev::depth' => ['int'],
'Ev::embeddableBackends' => ['void'],
'Ev::feedSignal' => ['void', 'signum'=>'int'],
'Ev::feedSignalEvent' => ['void', 'signum'=>'int'],
'Ev::iteration' => ['int'],
'Ev::now' => ['float'],
'Ev::nowUpdate' => ['void'],
'Ev::recommendedBackends' => ['void'],
'Ev::resume' => ['void'],
'Ev::run' => ['void', 'flags='=>'int'],
'Ev::sleep' => ['void', 'seconds'=>'float'],
'Ev::stop' => ['void', 'how='=>'int'],
'Ev::supportedBackends' => ['void'],
'Ev::suspend' => ['void'],
'Ev::time' => ['float'],
'Ev::verify' => ['void'],
'eval' => ['mixed', 'code_str'=>'string'],
'EvCheck::__construct' => ['void', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvCheck::createStopped' => ['object', 'callback'=>'string', 'data='=>'string', 'priority='=>'string'],
'EvChild::__construct' => ['void', 'pid'=>'int', 'trace'=>'bool', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvChild::createStopped' => ['object', 'pid'=>'int', 'trace'=>'bool', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvChild::set' => ['void', 'pid'=>'int', 'trace'=>'bool'],
'EvEmbed::__construct' => ['void', 'other'=>'object', 'callback='=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvEmbed::createStopped' => ['void', 'other'=>'object', 'callback='=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvEmbed::set' => ['void', 'other'=>'object'],
'EvEmbed::sweep' => ['void'],
'Event::__construct' => ['void', 'base'=>'EventBase', 'fd'=>'mixed', 'what'=>'int', 'cb'=>'callable', 'arg='=>'mixed'],
'Event::add' => ['bool', 'timeout='=>'float'],
'Event::addSignal' => ['bool', 'timeout='=>'float'],
'Event::addTimer' => ['bool', 'timeout='=>'float'],
'Event::del' => ['bool'],
'Event::delSignal' => ['bool'],
'Event::delTimer' => ['bool'],
'Event::free' => ['void'],
'Event::getSupportedMethods' => ['array'],
'Event::pending' => ['bool', 'flags'=>'int'],
'Event::set' => ['bool', 'base'=>'EventBase', 'fd'=>'mixed', 'what='=>'int', 'cb='=>'callable', 'arg='=>'mixed'],
'Event::setPriority' => ['bool', 'priority'=>'int'],
'Event::setTimer' => ['bool', 'base'=>'EventBase', 'cb'=>'callable', 'arg='=>'mixed'],
'Event::signal' => ['Event', 'base'=>'EventBase', 'signum'=>'int', 'cb'=>'callable', 'arg='=>'mixed'],
'Event::timer' => ['Event', 'base'=>'EventBase', 'cb'=>'callable', 'arg='=>'mixed'],
'event_add' => ['bool', 'event'=>'resource', 'timeout='=>'int'],
'event_base_free' => ['void', 'event_base'=>'resource'],
'event_base_loop' => ['int', 'event_base'=>'resource', 'flags='=>'int'],
'event_base_loopbreak' => ['bool', 'event_base'=>'resource'],
'event_base_loopexit' => ['bool', 'event_base'=>'resource', 'timeout='=>'int'],
'event_base_new' => ['resource'],
'event_base_priority_init' => ['bool', 'event_base'=>'resource', 'npriorities'=>'int'],
'event_base_reinit' => ['bool', 'event_base'=>'resource'],
'event_base_set' => ['bool', 'event'=>'resource', 'event_base'=>'resource'],
'event_buffer_base_set' => ['bool', 'bevent'=>'resource', 'event_base'=>'resource'],
'event_buffer_disable' => ['bool', 'bevent'=>'resource', 'events'=>'int'],
'event_buffer_enable' => ['bool', 'bevent'=>'resource', 'events'=>'int'],
'event_buffer_fd_set' => ['void', 'bevent'=>'resource', 'fd'=>'resource'],
'event_buffer_free' => ['void', 'bevent'=>'resource'],
'event_buffer_new' => ['resource', 'stream'=>'resource', 'readcb'=>'mixed', 'writecb'=>'mixed', 'errorcb'=>'mixed', 'arg='=>'mixed'],
'event_buffer_priority_set' => ['bool', 'bevent'=>'resource', 'priority'=>'int'],
'event_buffer_read' => ['string', 'bevent'=>'resource', 'data_size'=>'int'],
'event_buffer_set_callback' => ['bool', 'event'=>'resource', 'readcb'=>'mixed', 'writecb'=>'mixed', 'errorcb'=>'mixed', 'arg='=>'mixed'],
'event_buffer_timeout_set' => ['void', 'bevent'=>'resource', 'read_timeout'=>'int', 'write_timeout'=>'int'],
'event_buffer_watermark_set' => ['void', 'bevent'=>'resource', 'events'=>'int', 'lowmark'=>'int', 'highmark'=>'int'],
'event_buffer_write' => ['bool', 'bevent'=>'resource', 'data'=>'string', 'data_size='=>'int'],
'event_del' => ['bool', 'event'=>'resource'],
'event_free' => ['void', 'event'=>'resource'],
'event_new' => ['resource'],
'event_priority_set' => ['bool', 'event'=>'resource', 'priority'=>'int'],
'event_set' => ['bool', 'event'=>'resource', 'fd'=>'mixed', 'events'=>'int', 'callback'=>'mixed', 'arg='=>'mixed'],
'event_timer_add' => ['bool', 'event'=>'resource', 'timeout='=>'int'],
'event_timer_del' => ['bool', 'event'=>'resource'],
'event_timer_new' => ['bool|resource'],
'event_timer_pending' => ['bool', 'event'=>'resource', 'timeout='=>'int'],
'event_timer_set' => ['bool', 'event'=>'resource', 'callback'=>'callable', 'arg='=>'mixed'],
'EventBase::__construct' => ['void', 'cfg='=>'EventConfig'],
'EventBase::dispatch' => ['void'],
'EventBase::exit' => ['bool', 'timeout='=>'float'],
'EventBase::free' => ['void'],
'EventBase::getFeatures' => ['int'],
'EventBase::getMethod' => ['string', 'cfg='=>'EventConfig'],
'EventBase::getTimeOfDayCached' => ['float'],
'EventBase::gotExit' => ['bool'],
'EventBase::gotStop' => ['bool'],
'EventBase::loop' => ['bool', 'flags='=>'int'],
'EventBase::priorityInit' => ['bool', 'n_priorities'=>'int'],
'EventBase::reInit' => ['bool'],
'EventBase::stop' => ['bool'],
'EventBuffer::__construct' => ['void'],
'EventBuffer::add' => ['bool', 'data'=>'string'],
'EventBuffer::addBuffer' => ['bool', 'buf'=>'EventBuffer'],
'EventBuffer::appendFrom' => ['int', 'buf'=>'EventBuffer', 'len'=>'int'],
'EventBuffer::copyout' => ['int', '&w_data'=>'string', 'max_bytes'=>'int'],
'EventBuffer::drain' => ['bool', 'len'=>'int'],
'EventBuffer::enableLocking' => ['void'],
'EventBuffer::expand' => ['bool', 'len'=>'int'],
'EventBuffer::freeze' => ['bool', 'at_front'=>'bool'],
'EventBuffer::lock' => ['void'],
'EventBuffer::prepend' => ['bool', 'data'=>'string'],
'EventBuffer::prependBuffer' => ['bool', 'buf'=>'EventBuffer'],
'EventBuffer::pullup' => ['string', 'size'=>'int'],
'EventBuffer::read' => ['int', 'fd'=>'mixed', 'howmuch'=>'int'],
'EventBuffer::read\'1' => ['string', 'max_bytes'=>'int'],
'EventBuffer::readFrom' => ['int', 'fd'=>'mixed', 'howmuch'=>'int'],
'EventBuffer::readLine' => ['string', 'eol_style'=>'int'],
'EventBuffer::search' => ['mixed', 'what'=>'string', 'start='=>'int', 'end='=>'int'],
'EventBuffer::searchEol' => ['mixed', 'start='=>'int', 'eol_style='=>'int'],
'EventBuffer::substr' => ['string', 'start'=>'int', 'length='=>'int'],
'EventBuffer::unfreeze' => ['bool', 'at_front'=>'bool'],
'EventBuffer::unlock' => ['bool'],
'EventBuffer::write' => ['int', 'fd'=>'mixed', 'howmuch='=>'int'],
'EventBufferEvent::__construct' => ['void', 'base'=>'EventBase', 'socket='=>'mixed', 'options='=>'int', 'readcb='=>'callable', 'writecb='=>'callable', 'eventcb='=>'callable'],
'EventBufferEvent::close' => ['void'],
'EventBufferEvent::connect' => ['bool', 'addr'=>'string'],
'EventBufferEvent::connectHost' => ['bool', 'dns_base'=>'EventDnsBase', 'hostname'=>'string', 'port'=>'int', 'family='=>'int'],
'EventBufferEvent::createPair' => ['array', 'base'=>'EventBase', 'options='=>'int'],
'EventBufferEvent::disable' => ['bool', 'events'=>'int'],
'EventBufferEvent::enable' => ['bool', 'events'=>'int'],
'EventBufferEvent::free' => ['void'],
'EventBufferEvent::getDnsErrorString' => ['string'],
'EventBufferEvent::getEnabled' => ['int'],
'EventBufferEvent::getInput' => ['EventBuffer'],
'EventBufferEvent::getOutput' => ['EventBuffer'],
'EventBufferEvent::read' => ['string', 'size'=>'int'],
'EventBufferEvent::readBuffer' => ['bool', 'buf'=>'EventBuffer'],
'EventBufferEvent::setCallbacks' => ['void', 'readcb'=>'callable', 'writecb'=>'callable', 'eventcb'=>'callable', 'arg='=>'string'],
'EventBufferEvent::setPriority' => ['bool', 'priority'=>'int'],
'EventBufferEvent::setTimeouts' => ['bool', 'timeout_read'=>'float', 'timeout_write'=>'float'],
'EventBufferEvent::setWatermark' => ['void', 'events'=>'int', 'lowmark'=>'int', 'highmark'=>'int'],
'EventBufferEvent::sslError' => ['string'],
'EventBufferEvent::sslFilter' => ['EventBufferEvent', 'base'=>'EventBase', 'underlying'=>'EventBufferEvent', 'ctx'=>'EventSslContext', 'state'=>'int', 'options='=>'int'],
'EventBufferEvent::sslGetCipherInfo' => ['string'],
'EventBufferEvent::sslGetCipherName' => ['string'],
'EventBufferEvent::sslGetCipherVersion' => ['string'],
'EventBufferEvent::sslGetProtocol' => ['string'],
'EventBufferEvent::sslRenegotiate' => ['void'],
'EventBufferEvent::sslSocket' => ['EventBufferEvent', 'base'=>'EventBase', 'socket'=>'mixed', 'ctx'=>'EventSslContext', 'state'=>'int', 'options='=>'int'],
'EventBufferEvent::write' => ['bool', 'data'=>'string'],
'EventBufferEvent::writeBuffer' => ['bool', 'buf'=>'EventBuffer'],
'EventConfig::__construct' => ['void'],
'EventConfig::avoidMethod' => ['bool', 'method'=>'int'],
'EventConfig::requireFeatures' => ['bool', 'feature'=>'int'],
'EventConfig::setMaxDispatchInterval' => ['void', 'max_interval'=>'int', 'max_callbacks'=>'int', 'min_priority'=>'int'],
'EventDnsBase::__construct' => ['void', 'base'=>'EventBase', 'initialize'=>'bool'],
'EventDnsBase::addNameserverIp' => ['bool', 'ip'=>'string'],
'EventDnsBase::addSearch' => ['void', 'domain'=>'string'],
'EventDnsBase::clearSearch' => ['void'],
'EventDnsBase::countNameservers' => ['int'],
'EventDnsBase::loadHosts' => ['bool', 'hosts'=>'string'],
'EventDnsBase::parseResolvConf' => ['bool', 'flags'=>'int', 'filename'=>'string'],
'EventDnsBase::setOption' => ['bool', 'option'=>'string', 'value'=>'string'],
'EventDnsBase::setSearchNdots' => ['bool', 'ndots'=>'int'],
'EventHttp::__construct' => ['void', 'base'=>'EventBase', 'ctx='=>'EventSslContext'],
'EventHttp::accept' => ['bool', 'socket'=>'mixed'],
'EventHttp::addServerAlias' => ['bool', 'alias'=>'string'],
'EventHttp::bind' => ['void', 'address'=>'string', 'port'=>'int'],
'EventHttp::removeServerAlias' => ['bool', 'alias'=>'string'],
'EventHttp::setAllowedMethods' => ['void', 'methods'=>'int'],
'EventHttp::setCallback' => ['void', 'path'=>'string', 'cb'=>'string', 'arg='=>'string'],
'EventHttp::setDefaultCallback' => ['void', 'cb'=>'string', 'arg='=>'string'],
'EventHttp::setMaxBodySize' => ['void', 'value'=>'int'],
'EventHttp::setMaxHeadersSize' => ['void', 'value'=>'int'],
'EventHttp::setTimeout' => ['void', 'value'=>'int'],
'EventHttpConnection::__construct' => ['void', 'base'=>'EventBase', 'dns_base'=>'EventDnsBase', 'address'=>'string', 'port'=>'int', 'ctx='=>'EventSslContext'],
'EventHttpConnection::getBase' => ['EventBase'],
'EventHttpConnection::getPeer' => ['void', '&w_address'=>'string', '&w_port'=>'int'],
'EventHttpConnection::makeRequest' => ['bool', 'req'=>'EventHttpRequest', 'type'=>'int', 'uri'=>'string'],
'EventHttpConnection::setCloseCallback' => ['void', 'callback'=>'callable', 'data='=>'mixed'],
'EventHttpConnection::setLocalAddress' => ['void', 'address'=>'string'],
'EventHttpConnection::setLocalPort' => ['void', 'port'=>'int'],
'EventHttpConnection::setMaxBodySize' => ['void', 'max_size'=>'string'],
'EventHttpConnection::setMaxHeadersSize' => ['void', 'max_size'=>'string'],
'EventHttpConnection::setRetries' => ['void', 'retries'=>'int'],
'EventHttpConnection::setTimeout' => ['void', 'timeout'=>'int'],
'EventHttpRequest::__construct' => ['void', 'callback'=>'callable', 'data='=>'mixed'],
'EventHttpRequest::addHeader' => ['bool', 'key'=>'string', 'value'=>'string', 'type'=>'int'],
'EventHttpRequest::cancel' => ['void'],
'EventHttpRequest::clearHeaders' => ['void'],
'EventHttpRequest::closeConnection' => ['void'],
'EventHttpRequest::findHeader' => ['void', 'key'=>'string', 'type'=>'string'],
'EventHttpRequest::free' => ['void'],
'EventHttpRequest::getBufferEvent' => ['EventBufferEvent'],
'EventHttpRequest::getCommand' => ['void'],
'EventHttpRequest::getConnection' => ['EventHttpConnection'],
'EventHttpRequest::getHost' => ['string'],
'EventHttpRequest::getInputBuffer' => ['EventBuffer'],
'EventHttpRequest::getInputHeaders' => ['array'],
'EventHttpRequest::getOutputBuffer' => ['EventBuffer'],
'EventHttpRequest::getOutputHeaders' => ['void'],
'EventHttpRequest::getResponseCode' => ['int'],
'EventHttpRequest::getUri' => ['string'],
'EventHttpRequest::removeHeader' => ['void', 'key'=>'string', 'type'=>'string'],
'EventHttpRequest::sendError' => ['void', 'error'=>'int', 'reason='=>'string'],
'EventHttpRequest::sendReply' => ['void', 'code'=>'int', 'reason'=>'string', 'buf='=>'EventBuffer'],
'EventHttpRequest::sendReplyChunk' => ['void', 'buf'=>'EventBuffer'],
'EventHttpRequest::sendReplyEnd' => ['void'],
'EventHttpRequest::sendReplyStart' => ['void', 'code'=>'int', 'reason'=>'string'],
'EventListener::__construct' => ['void', 'base'=>'EventBase', 'cb'=>'callable', 'data'=>'mixed', 'flags'=>'int', 'backlog'=>'int', 'target'=>'mixed'],
'EventListener::disable' => ['bool'],
'EventListener::enable' => ['bool'],
'EventListener::getBase' => ['void'],
'EventListener::getSocketName' => ['bool', '&w_address'=>'string', '&w_port='=>'mixed'],
'EventListener::setCallback' => ['void', 'cb'=>'callable', 'arg='=>'mixed'],
'EventListener::setErrorCallback' => ['void', 'cb'=>'string'],
'EventSslContext::__construct' => ['void', 'method'=>'string', 'options'=>'string'],
'EventUtil::__construct' => ['void'],
'EventUtil::getLastSocketErrno' => ['int', 'socket='=>'mixed'],
'EventUtil::getLastSocketError' => ['string', 'socket='=>'mixed'],
'EventUtil::getSocketFd' => ['int', 'socket'=>'mixed'],
'EventUtil::getSocketName' => ['bool', 'socket'=>'mixed', '&w_address'=>'string', '&w_port='=>'mixed'],
'EventUtil::setSocketOption' => ['bool', 'socket'=>'mixed', 'level'=>'int', 'optname'=>'int', 'optval'=>'mixed'],
'EventUtil::sslRandPoll' => ['void'],
'EvFork::__construct' => ['void', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvFork::createStopped' => ['object', 'callback'=>'string', 'data='=>'string', 'priority='=>'string'],
'EvIdle::__construct' => ['void', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvIdle::createStopped' => ['object', 'callback'=>'string', 'data='=>'mixed', 'priority='=>'int'],
'EvIo::__construct' => ['void', 'fd'=>'mixed', 'events'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvIo::createStopped' => ['EvIo', 'fd'=>'mixed', 'events'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvIo::set' => ['void', 'fd'=>'mixed', 'events'=>'int'],
'EvLoop::__construct' => ['void', 'flags='=>'int', 'data='=>'mixed', 'io_interval='=>'float', 'timeout_interval='=>'float'],
'EvLoop::backend' => ['int'],
'EvLoop::check' => ['EvCheck', 'callback'=>'string', 'data='=>'string', 'priority='=>'string'],
'EvLoop::child' => ['EvChild', 'pid'=>'string', 'trace'=>'string', 'callback'=>'string', 'data='=>'string', 'priority='=>'string'],
'EvLoop::defaultLoop' => ['EvLoop', 'flags='=>'int', 'data='=>'mixed', 'io_interval='=>'float', 'timeout_interval='=>'float'],
'EvLoop::embed' => ['EvEmbed', 'other'=>'string', 'callback='=>'string', 'data='=>'string', 'priority='=>'string'],
'EvLoop::fork' => ['EvFork', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvLoop::idle' => ['EvIdle', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvLoop::invokePending' => ['void'],
'EvLoop::io' => ['EvIo', 'fd'=>'mixed', 'events'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvLoop::loopFork' => ['void'],
'EvLoop::now' => ['float'],
'EvLoop::nowUpdate' => ['void'],
'EvLoop::periodic' => ['EvPeriodic', 'offset'=>'float', 'interval'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvLoop::prepare' => ['EvPrepare', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvLoop::resume' => ['void'],
'EvLoop::run' => ['void', 'flags='=>'int'],
'EvLoop::signal' => ['EvSignal', 'signum'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvLoop::stat' => ['EvStat', 'path'=>'string', 'interval'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvLoop::stop' => ['void', 'how='=>'int'],
'EvLoop::suspend' => ['void'],
'EvLoop::timer' => ['EvTimer', 'after'=>'float', 'repeat'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvLoop::verify' => ['void'],
'EvPeriodic::__construct' => ['void', 'offset'=>'float', 'interval'=>'string', 'reschedule_cb'=>'callable', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvPeriodic::again' => ['void'],
'EvPeriodic::at' => ['float'],
'EvPeriodic::createStopped' => ['EvPeriodic', 'offset'=>'float', 'interval'=>'float', 'reschedule_cb'=>'callable', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvPeriodic::set' => ['void', 'offset'=>'float', 'interval'=>'float'],
'EvPrepare::__construct' => ['void', 'callback'=>'string', 'data='=>'string', 'priority='=>'string'],
'EvPrepare::createStopped' => ['EvPrepare', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvSignal::__construct' => ['void', 'signum'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvSignal::createStopped' => ['EvSignal', 'signum'=>'int', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvSignal::set' => ['void', 'signum'=>'int'],
'EvStat::__construct' => ['void', 'path'=>'string', 'interval'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvStat::attr' => ['array'],
'EvStat::createStopped' => ['void', 'path'=>'string', 'interval'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvStat::prev' => ['void'],
'EvStat::set' => ['void', 'path'=>'string', 'interval'=>'float'],
'EvStat::stat' => ['bool'],
'EvTimer::__construct' => ['void', 'after'=>'float', 'repeat'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvTimer::again' => ['void'],
'EvTimer::createStopped' => ['EvTimer', 'after'=>'float', 'repeat'=>'float', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'],
'EvTimer::set' => ['void', 'after'=>'float', 'repeat'=>'float'],
'EvWatcher::__construct' => ['void'],
'EvWatcher::clear' => ['int'],
'EvWatcher::feed' => ['void', 'revents'=>'int'],
'EvWatcher::getLoop' => ['EvLoop'],
'EvWatcher::invoke' => ['void', 'revents'=>'int'],
'EvWatcher::keepalive' => ['bool', 'value='=>'bool'],
'EvWatcher::setCallback' => ['void', 'callback'=>'callable'],
'EvWatcher::start' => ['void'],
'EvWatcher::stop' => ['void'],
'Exception::__clone' => ['void'],
'Exception::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?Exception'],
'Exception::__toString' => ['string'],
'Exception::getCode' => ['int'],
'Exception::getFile' => ['string'],
'Exception::getLine' => ['int'],
'Exception::getMessage' => ['string'],
'Exception::getPrevious' => ['Throwable|Exception'],
'Exception::getTrace' => ['array'],
'Exception::getTraceAsString' => ['string'],
'exec' => ['string', 'command'=>'string', '&w_output='=>'array', '&w_return_value='=>'int'],
'exif_imagetype' => ['int', 'imagefile'=>'string'],
'exif_read_data' => ['array', 'filename'=>'string', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'],
'exif_tagname' => ['string', 'index'=>'int'],
'exif_thumbnail' => ['string', 'filename'=>'string', '&w_width='=>'int', '&w_height='=>'int', '&w_imagetype='=>'int'],
'exit' => ['', 'status'=>'string|int'],
'exp' => ['float', 'number'=>'float'],
'expect_expectl' => ['int', 'expect'=>'resource', 'cases'=>'array', 'match='=>'array'],
'expect_popen' => ['resource', 'command'=>'string'],
'explode' => ['array<int,string>|false', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'],
'expm1' => ['float', 'number'=>'float'],
'extension_loaded' => ['bool', 'extension_name'=>'string'],
'extract' => ['int', '&rw_var_array'=>'array', 'extract_type='=>'int', 'prefix='=>'string'],
'ezmlm_hash' => ['int', 'addr'=>'string'],
'fam_cancel_monitor' => ['bool', 'fam'=>'resource', 'fam_monitor'=>'resource'],
'fam_close' => ['void', 'fam'=>'resource'],
'fam_monitor_collection' => ['resource', 'fam'=>'resource', 'dirname'=>'string', 'depth'=>'int', 'mask'=>'string'],
'fam_monitor_directory' => ['resource', 'fam'=>'resource', 'dirname'=>'string'],
'fam_monitor_file' => ['resource', 'fam'=>'resource', 'filename'=>'string'],
'fam_next_event' => ['array', 'fam'=>'resource'],
'fam_open' => ['resource', 'appname='=>'string'],
'fam_pending' => ['int', 'fam'=>'resource'],
'fam_resume_monitor' => ['bool', 'fam'=>'resource', 'fam_monitor'=>'resource'],
'fam_suspend_monitor' => ['bool', 'fam'=>'resource', 'fam_monitor'=>'resource'],
'fann_cascadetrain_on_data' => ['bool', 'ann'=>'resource', 'data'=>'resource', 'max_neurons'=>'int', 'neurons_between_reports'=>'int', 'desired_error'=>'float'],
'fann_cascadetrain_on_file' => ['bool', 'ann'=>'resource', 'filename'=>'string', 'max_neurons'=>'int', 'neurons_between_reports'=>'int', 'desired_error'=>'float'],
'fann_clear_scaling_params' => ['bool', 'ann'=>'resource'],
'fann_copy' => ['resource', 'ann'=>'resource'],
'fann_create_from_file' => ['resource', 'configuration_file'=>'string'],
'fann_create_shortcut' => ['reference', 'num_layers'=>'int', 'num_neurons1'=>'int', 'num_neurons2'=>'int', '...args='=>'int'],
'fann_create_shortcut_array' => ['resource', 'num_layers'=>'int', 'layers'=>'array'],
'fann_create_sparse' => ['resource|false', 'connection_rate'=>'float', 'num_layers'=>'int', 'num_neurons1'=>'int', 'num_neurons2'=>'int', '...args='=>'int'],
'fann_create_sparse_array' => ['resource|false', 'connection_rate'=>'float', 'num_layers'=>'int', 'layers'=>'array'],
'fann_create_standard' => ['resource', 'num_layers'=>'int', 'num_neurons1'=>'int', 'num_neurons2'=>'int', '...args='=>'int'],
'fann_create_standard_array' => ['resource', 'num_layers'=>'int', 'layers'=>'array'],
'fann_create_train' => ['resource', 'num_data'=>'int', 'num_input'=>'int', 'num_output'=>'int'],
'fann_create_train_from_callback' => ['resource', 'num_data'=>'int', 'num_input'=>'int', 'num_output'=>'int', 'user_function'=>'collable'],
'fann_descale_input' => ['bool', 'ann'=>'resource', 'input_vector'=>'array'],
'fann_descale_output' => ['bool', 'ann'=>'resource', 'output_vector'=>'array'],
'fann_descale_train' => ['bool', 'ann'=>'resource', 'train_data'=>'resource'],
'fann_destroy' => ['bool', 'ann'=>'resource'],
'fann_destroy_train' => ['bool', 'train_data'=>'resource'],
'fann_duplicate_train_data' => ['resource', 'data'=>'resource'],
'fann_get_activation_function' => ['int', 'ann'=>'resource', 'layer'=>'int', 'neuron'=>'int'],
'fann_get_activation_steepness' => ['float', 'ann'=>'resource', 'layer'=>'int', 'neuron'=>'int'],
'fann_get_bias_array' => ['array', 'ann'=>'resource'],
'fann_get_bit_fail' => ['int', 'ann'=>'resource'],
'fann_get_bit_fail_limit' => ['float', 'ann'=>'resource'],
'fann_get_cascade_activation_functions' => ['array', 'ann'=>'resource'],
'fann_get_cascade_activation_functions_count' => ['int', 'ann'=>'resource'],
'fann_get_cascade_activation_steepnesses' => ['array', 'ann'=>'resource'],
'fann_get_cascade_activation_steepnesses_count' => ['int', 'ann'=>'resource'],
'fann_get_cascade_candidate_change_fraction' => ['float', 'ann'=>'resource'],
'fann_get_cascade_candidate_limit' => ['float', 'ann'=>'resource'],
'fann_get_cascade_candidate_stagnation_epochs' => ['float', 'ann'=>'resource'],
'fann_get_cascade_max_cand_epochs' => ['int', 'ann'=>'resource'],
'fann_get_cascade_max_out_epochs' => ['int', 'ann'=>'resource'],
'fann_get_cascade_min_cand_epochs' => ['int', 'ann'=>'resource'],
'fann_get_cascade_min_out_epochs' => ['int', 'ann'=>'resource'],
'fann_get_cascade_num_candidate_groups' => ['int', 'ann'=>'resource'],
'fann_get_cascade_num_candidates' => ['int', 'ann'=>'resource'],
'fann_get_cascade_output_change_fraction' => ['float', 'ann'=>'resource'],
'fann_get_cascade_output_stagnation_epochs' => ['int', 'ann'=>'resource'],
'fann_get_cascade_weight_multiplier' => ['float', 'ann'=>'resource'],
'fann_get_connection_array' => ['array', 'ann'=>'resource'],
'fann_get_connection_rate' => ['float', 'ann'=>'resource'],
'fann_get_errno' => ['int', 'errdat'=>'resource'],
'fann_get_errstr' => ['string', 'errdat'=>'resource'],
'fann_get_layer_array' => ['array', 'ann'=>'resource'],
'fann_get_learning_momentum' => ['float', 'ann'=>'resource'],
'fann_get_learning_rate' => ['float', 'ann'=>'resource'],
'fann_get_MSE' => ['float', 'ann'=>'resource'],
'fann_get_network_type' => ['int', 'ann'=>'resource'],
'fann_get_num_input' => ['int', 'ann'=>'resource'],
'fann_get_num_layers' => ['int', 'ann'=>'resource'],
'fann_get_num_output' => ['int', 'ann'=>'resource'],
'fann_get_quickprop_decay' => ['float', 'ann'=>'resource'],
'fann_get_quickprop_mu' => ['float', 'ann'=>'resource'],
'fann_get_rprop_decrease_factor' => ['float', 'ann'=>'resource'],
'fann_get_rprop_delta_max' => ['float', 'ann'=>'resource'],
'fann_get_rprop_delta_min' => ['float', 'ann'=>'resource'],
'fann_get_rprop_delta_zero' => ['float|false', 'ann'=>'resource'],
'fann_get_rprop_increase_factor' => ['float', 'ann'=>'resource'],
'fann_get_sarprop_step_error_shift' => ['float', 'ann'=>'resource'],
'fann_get_sarprop_step_error_threshold_factor' => ['float', 'ann'=>'resource'],
'fann_get_sarprop_temperature' => ['float', 'ann'=>'resource'],
'fann_get_sarprop_weight_decay_shift' => ['float', 'ann'=>'resource'],
'fann_get_total_connections' => ['int', 'ann'=>'resource'],
'fann_get_total_neurons' => ['int', 'ann'=>'resource'],
'fann_get_train_error_function' => ['int', 'ann'=>'resource'],
'fann_get_train_stop_function' => ['int', 'ann'=>'resource'],
'fann_get_training_algorithm' => ['int', 'ann'=>'resource'],
'fann_init_weights' => ['bool', 'ann'=>'resource', 'train_data'=>'resource'],
'fann_length_train_data' => ['int', 'data'=>'resource'],
'fann_merge_train_data' => ['resource', 'data1'=>'resource', 'data2'=>'resource'],
'fann_num_input_train_data' => ['int', 'data'=>'resource'],
'fann_num_output_train_data' => ['int', 'data'=>'resource'],
'fann_print_error' => ['void', 'errdat'=>'string'],
'fann_randomize_weights' => ['bool', 'ann'=>'resource', 'min_weight'=>'float', 'max_weight'=>'float'],
'fann_read_train_from_file' => ['resource', 'filename'=>'string'],
'fann_reset_errno' => ['void', 'errdat'=>'resource'],
'fann_reset_errstr' => ['void', 'errdat'=>'resource'],
'fann_reset_MSE' => ['bool', 'ann'=>'string'],
'fann_run' => ['array', 'ann'=>'resource', 'input'=>'array'],
'fann_save' => ['bool', 'ann'=>'resource', 'configuration_file'=>'string'],
'fann_save_train' => ['bool', 'data'=>'resource', 'file_name'=>'string'],
'fann_scale_input' => ['bool', 'ann'=>'resource', 'input_vector'=>'array'],
'fann_scale_input_train_data' => ['bool', 'train_data'=>'resource', 'new_min'=>'float', 'new_max'=>'float'],
'fann_scale_output' => ['bool', 'ann'=>'resource', 'output_vector'=>'array'],
'fann_scale_output_train_data' => ['bool', 'train_data'=>'resource', 'new_min'=>'float', 'new_max'=>'float'],
'fann_scale_train' => ['bool', 'ann'=>'resource', 'train_data'=>'resource'],
'fann_scale_train_data' => ['bool', 'train_data'=>'resource', 'new_min'=>'float', 'new_max'=>'float'],
'fann_set_activation_function' => ['bool', 'ann'=>'resource', 'activation_function'=>'int', 'layer'=>'int', 'neuron'=>'int'],
'fann_set_activation_function_hidden' => ['bool', 'ann'=>'resource', 'activation_function'=>'int'],
'fann_set_activation_function_layer' => ['bool', 'ann'=>'resource', 'activation_function'=>'int', 'layer'=>'int'],
'fann_set_activation_function_output' => ['bool', 'ann'=>'resource', 'activation_function'=>'int'],
'fann_set_activation_steepness' => ['bool', 'ann'=>'resource', 'activation_steepness'=>'float', 'layer'=>'int', 'neuron'=>'int'],
'fann_set_activation_steepness_hidden' => ['bool', 'ann'=>'resource', 'activation_steepness'=>'float'],
'fann_set_activation_steepness_layer' => ['bool', 'ann'=>'resource', 'activation_steepness'=>'float', 'layer'=>'int'],
'fann_set_activation_steepness_output' => ['bool', 'ann'=>'resource', 'activation_steepness'=>'float'],
'fann_set_bit_fail_limit' => ['bool', 'ann'=>'resource', 'bit_fail_limit'=>'float'],
'fann_set_callback' => ['bool', 'ann'=>'resource', 'callback'=>'collable'],
'fann_set_cascade_activation_functions' => ['bool', 'ann'=>'resource', 'cascade_activation_functions'=>'array'],
'fann_set_cascade_activation_steepnesses' => ['bool', 'ann'=>'resource', 'cascade_activation_steepnesses_count'=>'array'],
'fann_set_cascade_candidate_change_fraction' => ['bool', 'ann'=>'resource', 'cascade_candidate_change_fraction'=>'float'],
'fann_set_cascade_candidate_limit' => ['bool', 'ann'=>'resource', 'cascade_candidate_limit'=>'float'],
'fann_set_cascade_candidate_stagnation_epochs' => ['bool', 'ann'=>'resource', 'cascade_candidate_stagnation_epochs'=>'int'],
'fann_set_cascade_max_cand_epochs' => ['bool', 'ann'=>'resource', 'cascade_max_cand_epochs'=>'int'],
'fann_set_cascade_max_out_epochs' => ['bool', 'ann'=>'resource', 'cascade_max_out_epochs'=>'int'],
'fann_set_cascade_min_cand_epochs' => ['bool', 'ann'=>'resource', 'cascade_min_cand_epochs'=>'int'],
'fann_set_cascade_min_out_epochs' => ['bool', 'ann'=>'resource', 'cascade_min_out_epochs'=>'int'],
'fann_set_cascade_num_candidate_groups' => ['bool', 'ann'=>'resource', 'cascade_num_candidate_groups'=>'int'],
'fann_set_cascade_output_change_fraction' => ['bool', 'ann'=>'resource', 'cascade_output_change_fraction'=>'float'],
'fann_set_cascade_output_stagnation_epochs' => ['bool', 'ann'=>'resource', 'cascade_output_stagnation_epochs'=>'int'],
'fann_set_cascade_weight_multiplier' => ['bool', 'ann'=>'resource', 'cascade_weight_multiplier'=>'float'],
'fann_set_error_log' => ['void', 'errdat'=>'resource', 'log_file'=>'string'],
'fann_set_input_scaling_params' => ['bool', 'ann'=>'resource', 'train_data'=>'resource', 'new_input_min'=>'float', 'new_input_max'=>'float'],
'fann_set_learning_momentum' => ['bool', 'ann'=>'resource', 'learning_momentum'=>'float'],
'fann_set_learning_rate' => ['bool', 'ann'=>'resource', 'learning_rate'=>'float'],
'fann_set_output_scaling_params' => ['bool', 'ann'=>'resource', 'train_data'=>'resource', 'new_output_min'=>'float', 'new_output_max'=>'float'],
'fann_set_quickprop_decay' => ['bool', 'ann'=>'resource', 'quickprop_decay'=>'float'],
'fann_set_quickprop_mu' => ['bool', 'ann'=>'resource', 'quickprop_mu'=>'float'],
'fann_set_rprop_decrease_factor' => ['bool', 'ann'=>'resource', 'rprop_decrease_factor'=>'float'],
'fann_set_rprop_delta_max' => ['bool', 'ann'=>'resource', 'rprop_delta_max'=>'float'],
'fann_set_rprop_delta_min' => ['bool', 'ann'=>'resource', 'rprop_delta_min'=>'float'],
'fann_set_rprop_delta_zero' => ['bool', 'ann'=>'resource', 'rprop_delta_zero'=>'float'],
'fann_set_rprop_increase_factor' => ['bool', 'ann'=>'resource', 'rprop_increase_factor'=>'float'],
'fann_set_sarprop_step_error_shift' => ['bool', 'ann'=>'resource', 'sarprop_step_error_shift'=>'float'],
'fann_set_sarprop_step_error_threshold_factor' => ['bool', 'ann'=>'resource', 'sarprop_step_error_threshold_factor'=>'float'],
'fann_set_sarprop_temperature' => ['bool', 'ann'=>'resource', 'sarprop_temperature'=>'float'],
'fann_set_sarprop_weight_decay_shift' => ['bool', 'ann'=>'resource', 'sarprop_weight_decay_shift'=>'float'],
'fann_set_scaling_params' => ['bool', 'ann'=>'resource', 'train_data'=>'resource', 'new_input_min'=>'float', 'new_input_max'=>'float', 'new_output_min'=>'float', 'new_output_max'=>'float'],
'fann_set_train_error_function' => ['bool', 'ann'=>'resource', 'error_function'=>'int'],
'fann_set_train_stop_function' => ['bool', 'ann'=>'resource', 'stop_function'=>'int'],
'fann_set_training_algorithm' => ['bool', 'ann'=>'resource', 'training_algorithm'=>'int'],
'fann_set_weight' => ['bool', 'ann'=>'resource', 'from_neuron'=>'int', 'to_neuron'=>'int', 'weight'=>'float'],
'fann_set_weight_array' => ['bool', 'ann'=>'resource', 'connections'=>'array'],
'fann_shuffle_train_data' => ['bool', 'train_data'=>'resource'],
'fann_subset_train_data' => ['resource', 'data'=>'resource', 'pos'=>'int', 'length'=>'int'],
'fann_test' => ['bool', 'ann'=>'resource', 'input'=>'array', 'desired_output'=>'array'],
'fann_test_data' => ['float', 'ann'=>'resource', 'data'=>'resource'],
'fann_train' => ['bool', 'ann'=>'resource', 'input'=>'array', 'desired_output'=>'array'],
'fann_train_epoch' => ['float', 'ann'=>'resource', 'data'=>'resource'],
'fann_train_on_data' => ['bool', 'ann'=>'resource', 'data'=>'resource', 'max_epochs'=>'int', 'epochs_between_reports'=>'int', 'desired_error'=>'float'],
'fann_train_on_file' => ['bool', 'ann'=>'resource', 'filename'=>'string', 'max_epochs'=>'int', 'epochs_between_reports'=>'int', 'desired_error'=>'float'],
'FANNConnection::__construct' => ['void', 'from_neuron'=>'int', 'to_neuron'=>'int', 'weight'=>'float'],
'FANNConnection::getFromNeuron' => ['int'],
'FANNConnection::getToNeuron' => ['int'],
'FANNConnection::getWeight' => ['void'],
'FANNConnection::setWeight' => ['bool', 'weight'=>'float'],
'fastcgi_finish_request' => ['bool'],
'fbsql_affected_rows' => ['int', 'link_identifier='=>'?resource'],
'fbsql_autocommit' => ['bool', 'link_identifier'=>'resource', 'onoff='=>'bool'],
'fbsql_blob_size' => ['int', 'blob_handle'=>'string', 'link_identifier='=>'?resource'],
'fbsql_change_user' => ['bool', 'user'=>'string', 'password'=>'string', 'database='=>'string', 'link_identifier='=>'?resource'],
'fbsql_clob_size' => ['int', 'clob_handle'=>'string', 'link_identifier='=>'?resource'],
'fbsql_close' => ['bool', 'link_identifier='=>'?resource'],
'fbsql_commit' => ['bool', 'link_identifier='=>'?resource'],
'fbsql_connect' => ['resource', 'hostname='=>'string', 'username='=>'string', 'password='=>'string'],
'fbsql_create_blob' => ['string', 'blob_data'=>'string', 'link_identifier='=>'?resource'],
'fbsql_create_clob' => ['string', 'clob_data'=>'string', 'link_identifier='=>'?resource'],
'fbsql_create_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource', 'database_options='=>'string'],
'fbsql_data_seek' => ['bool', 'result'=>'resource', 'row_number'=>'int'],
'fbsql_database' => ['string', 'link_identifier'=>'resource', 'database='=>'string'],
'fbsql_database_password' => ['string', 'link_identifier'=>'resource', 'database_password='=>'string'],
'fbsql_db_query' => ['resource', 'database'=>'string', 'query'=>'string', 'link_identifier='=>'?resource'],
'fbsql_db_status' => ['int', 'database_name'=>'string', 'link_identifier='=>'?resource'],
'fbsql_drop_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource'],
'fbsql_errno' => ['int', 'link_identifier='=>'?resource'],
'fbsql_error' => ['string', 'link_identifier='=>'?resource'],
'fbsql_fetch_array' => ['array', 'result'=>'resource', 'result_type='=>'int'],
'fbsql_fetch_assoc' => ['array', 'result'=>'resource'],
'fbsql_fetch_field' => ['object', 'result'=>'resource', 'field_offset='=>'int'],
'fbsql_fetch_lengths' => ['array', 'result'=>'resource'],
'fbsql_fetch_object' => ['object', 'result'=>'resource'],
'fbsql_fetch_row' => ['array', 'result'=>'resource'],
'fbsql_field_flags' => ['string', 'result'=>'resource', 'field_offset='=>'int'],
'fbsql_field_len' => ['int', 'result'=>'resource', 'field_offset='=>'int'],
'fbsql_field_name' => ['string', 'result'=>'resource', 'field_index='=>'int'],
'fbsql_field_seek' => ['bool', 'result'=>'resource', 'field_offset='=>'int'],
'fbsql_field_table' => ['string', 'result'=>'resource', 'field_offset='=>'int'],
'fbsql_field_type' => ['string', 'result'=>'resource', 'field_offset='=>'int'],
'fbsql_free_result' => ['bool', 'result'=>'resource'],
'fbsql_get_autostart_info' => ['array', 'link_identifier='=>'?resource'],
'fbsql_hostname' => ['string', 'link_identifier'=>'resource', 'host_name='=>'string'],
'fbsql_insert_id' => ['int', 'link_identifier='=>'?resource'],
'fbsql_list_dbs' => ['resource', 'link_identifier='=>'?resource'],
'fbsql_list_fields' => ['resource', 'database_name'=>'string', 'table_name'=>'string', 'link_identifier='=>'?resource'],
'fbsql_list_tables' => ['resource', 'database'=>'string', 'link_identifier='=>'?resource'],
'fbsql_next_result' => ['bool', 'result'=>'resource'],
'fbsql_num_fields' => ['int', 'result'=>'resource'],
'fbsql_num_rows' => ['int', 'result'=>'resource'],
'fbsql_password' => ['string', 'link_identifier'=>'resource', 'password='=>'string'],
'fbsql_pconnect' => ['resource', 'hostname='=>'string', 'username='=>'string', 'password='=>'string'],
'fbsql_query' => ['resource', 'query'=>'string', 'link_identifier='=>'?resource', 'batch_size='=>'int'],
'fbsql_read_blob' => ['string', 'blob_handle'=>'string', 'link_identifier='=>'?resource'],
'fbsql_read_clob' => ['string', 'clob_handle'=>'string', 'link_identifier='=>'?resource'],
'fbsql_result' => ['mixed', 'result'=>'resource', 'row='=>'int', 'field='=>'mixed'],
'fbsql_rollback' => ['bool', 'link_identifier='=>'?resource'],
'fbsql_rows_fetched' => ['int', 'result'=>'resource'],
'fbsql_select_db' => ['bool', 'database_name='=>'string', 'link_identifier='=>'?resource'],
'fbsql_set_characterset' => ['void', 'link_identifier'=>'resource', 'characterset'=>'int', 'in_out_both='=>'int'],
'fbsql_set_lob_mode' => ['bool', 'result'=>'resource', 'lob_mode'=>'int'],
'fbsql_set_password' => ['bool', 'link_identifier'=>'resource', 'user'=>'string', 'password'=>'string', 'old_password'=>'string'],
'fbsql_set_transaction' => ['void', 'link_identifier'=>'resource', 'locking'=>'int', 'isolation'=>'int'],
'fbsql_start_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource', 'database_options='=>'string'],
'fbsql_stop_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource'],
'fbsql_table_name' => ['string', 'result'=>'resource', 'index'=>'int'],
'fbsql_username' => ['string', 'link_identifier'=>'resource', 'username='=>'string'],
'fbsql_warnings' => ['bool', 'onoff='=>'bool'],
'fclose' => ['bool', 'fp'=>'resource'],
'fdf_add_doc_javascript' => ['bool', 'fdf_document'=>'resource', 'script_name'=>'string', 'script_code'=>'string'],
'fdf_add_template' => ['bool', 'fdf_document'=>'resource', 'newpage'=>'int', 'filename'=>'string', 'template'=>'string', 'rename'=>'int'],
'fdf_close' => ['void', 'fdf_document'=>'resource'],
'fdf_create' => ['resource'],
'fdf_enum_values' => ['bool', 'fdf_document'=>'resource', 'function'=>'callable', 'userdata='=>'mixed'],
'fdf_errno' => ['int'],
'fdf_error' => ['string', 'error_code='=>'int'],
'fdf_get_ap' => ['bool', 'fdf_document'=>'resource', 'field'=>'string', 'face'=>'int', 'filename'=>'string'],
'fdf_get_attachment' => ['array', 'fdf_document'=>'resource', 'fieldname'=>'string', 'savepath'=>'string'],
'fdf_get_encoding' => ['string', 'fdf_document'=>'resource'],
'fdf_get_file' => ['string', 'fdf_document'=>'resource'],
'fdf_get_flags' => ['int', 'fdf_document'=>'resource', 'fieldname'=>'string', 'whichflags'=>'int'],
'fdf_get_opt' => ['mixed', 'fdf_document'=>'resource', 'fieldname'=>'string', 'element='=>'int'],
'fdf_get_status' => ['string', 'fdf_document'=>'resource'],
'fdf_get_value' => ['mixed', 'fdf_document'=>'resource', 'fieldname'=>'string', 'which='=>'int'],
'fdf_get_version' => ['string', 'fdf_document='=>'resource'],
'fdf_header' => ['void'],
'fdf_next_field_name' => ['string', 'fdf_document'=>'resource', 'fieldname='=>'string'],
'fdf_open' => ['resource', 'filename'=>'string'],
'fdf_open_string' => ['resource', 'fdf_data'=>'string'],
'fdf_remove_item' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'item'=>'int'],
'fdf_save' => ['bool', 'fdf_document'=>'resource', 'filename='=>'string'],
'fdf_save_string' => ['string', 'fdf_document'=>'resource'],
'fdf_set_ap' => ['bool', 'fdf_document'=>'resource', 'field_name'=>'string', 'face'=>'int', 'filename'=>'string', 'page_number'=>'int'],
'fdf_set_encoding' => ['bool', 'fdf_document'=>'resource', 'encoding'=>'string'],
'fdf_set_file' => ['bool', 'fdf_document'=>'resource', 'url'=>'string', 'target_frame='=>'string'],
'fdf_set_flags' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'whichflags'=>'int', 'newflags'=>'int'],
'fdf_set_javascript_action' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'trigger'=>'int', 'script'=>'string'],
'fdf_set_on_import_javascript' => ['bool', 'fdf_document'=>'resource', 'script'=>'string', 'before_data_import'=>'bool'],
'fdf_set_opt' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'element'=>'int', 'str1'=>'string', 'str2'=>'string'],
'fdf_set_status' => ['bool', 'fdf_document'=>'resource', 'status'=>'string'],
'fdf_set_submit_form_action' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'trigger'=>'int', 'script'=>'string', 'flags'=>'int'],
'fdf_set_target_frame' => ['bool', 'fdf_document'=>'resource', 'frame_name'=>'string'],
'fdf_set_value' => ['bool', 'fdf_document'=>'resource', 'fieldname'=>'string', 'value'=>'mixed', 'isname='=>'int'],
'fdf_set_version' => ['bool', 'fdf_document'=>'resource', 'version'=>'string'],
'feof' => ['bool', 'fp'=>'resource'],
'fflush' => ['bool', 'fp'=>'resource'],
'ffmpeg_animated_gif::__construct' => ['void', 'output_file_path'=>'string', 'width'=>'int', 'height'=>'int', 'frame_rate'=>'int', 'loop_count='=>'int'],
'ffmpeg_animated_gif::addFrame' => ['', 'frame_to_add'=>'ffmpeg_frame'],
'ffmpeg_frame::__construct' => ['void', 'gd_image'=>'resource'],
'ffmpeg_frame::crop' => ['', 'crop_top'=>'int', 'crop_bottom='=>'int', 'crop_left='=>'int', 'crop_right='=>'int'],
'ffmpeg_frame::getHeight' => ['int'],
'ffmpeg_frame::getPresentationTimestamp' => ['int'],
'ffmpeg_frame::getPTS' => ['int'],
'ffmpeg_frame::getWidth' => ['int'],
'ffmpeg_frame::resize' => ['', 'width'=>'int', 'height'=>'int', 'crop_top='=>'int', 'crop_bottom='=>'int', 'crop_left='=>'int', 'crop_right='=>'int'],
'ffmpeg_frame::toGDImage' => ['resource'],
'ffmpeg_movie::__construct' => ['void', 'path_to_media'=>'string', 'persistent'=>'bool'],
'ffmpeg_movie::getArtist' => ['string'],
'ffmpeg_movie::getAudioBitRate' => ['int'],
'ffmpeg_movie::getAudioChannels' => ['int'],
'ffmpeg_movie::getAudioCodec' => ['string'],
'ffmpeg_movie::getAudioSampleRate' => ['int'],
'ffmpeg_movie::getAuthor' => ['string'],
'ffmpeg_movie::getBitRate' => ['int'],
'ffmpeg_movie::getComment' => ['string'],
'ffmpeg_movie::getCopyright' => ['string'],
'ffmpeg_movie::getDuration' => ['int'],
'ffmpeg_movie::getFilename' => ['string'],
'ffmpeg_movie::getFrame' => ['ffmpeg_frame', 'framenumber'=>'int'],
'ffmpeg_movie::getFrameCount' => ['int'],
'ffmpeg_movie::getFrameHeight' => ['int'],
'ffmpeg_movie::getFrameNumber' => ['int'],
'ffmpeg_movie::getFrameRate' => ['int'],
'ffmpeg_movie::getFrameWidth' => ['int'],
'ffmpeg_movie::getGenre' => ['string'],
'ffmpeg_movie::getNextKeyFrame' => ['ffmpeg_frame'],
'ffmpeg_movie::getPixelFormat' => [''],
'ffmpeg_movie::getTitle' => ['string'],
'ffmpeg_movie::getTrackNumber' => ['int|string'],
'ffmpeg_movie::getVideoBitRate' => ['int'],
'ffmpeg_movie::getVideoCodec' => ['string'],
'ffmpeg_movie::getYear' => ['int|string'],
'ffmpeg_movie::hasAudio' => ['bool'],
'ffmpeg_movie::hasVideo' => ['bool'],
'fgetc' => ['string|false', 'fp'=>'resource'],
'fgetcsv' => ['?array|?false', 'fp'=>'resource', 'length='=>'int', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'],
'fgets' => ['string|false', 'fp'=>'resource', 'length='=>'int'],
'fgetss' => ['string|false', 'fp'=>'resource', 'length='=>'int', 'allowable_tags='=>'string'],
'file' => ['array<int,string>|false', 'filename'=>'string', 'flags='=>'int', 'context='=>'resource'],
'file_exists' => ['bool', 'filename'=>'string'],
'file_get_contents' => ['string|false', 'filename'=>'string', 'use_include_path='=>'bool', 'context='=>'resource', 'offset='=>'int', 'maxlen='=>'int'],
'file_put_contents' => ['int|false', 'file'=>'string', 'data'=>'mixed', 'flags='=>'int', 'context='=>'resource'],
'fileatime' => ['int|false', 'filename'=>'string'],
'filectime' => ['int|false', 'filename'=>'string'],
'filegroup' => ['int|false', 'filename'=>'string'],
'fileinode' => ['int|false', 'filename'=>'string'],
'filemtime' => ['int|false', 'filename'=>'string'],
'fileowner' => ['int|false', 'filename'=>'string'],
'fileperms' => ['int|false', 'filename'=>'string'],
'filepro' => ['bool', 'directory'=>'string'],
'filepro_fieldcount' => ['int'],
'filepro_fieldname' => ['string', 'field_number'=>'int'],
'filepro_fieldtype' => ['string', 'field_number'=>'int'],
'filepro_fieldwidth' => ['int', 'field_number'=>'int'],
'filepro_retrieve' => ['string', 'row_number'=>'int', 'field_number'=>'int'],
'filepro_rowcount' => ['int'],
'filesize' => ['int|false', 'filename'=>'string'],
'FilesystemIterator::__construct' => ['void', 'path'=>'string', 'flags='=>'int'],
'FilesystemIterator::current' => ['string'],
'FilesystemIterator::getFlags' => ['int'],
'FilesystemIterator::key' => ['string'],
'FilesystemIterator::next' => ['void'],
'FilesystemIterator::rewind' => ['void'],
'FilesystemIterator::setFlags' => ['void', 'flags='=>'int'],
'filetype' => ['string|false', 'filename'=>'string'],
'filter_has_var' => ['bool', 'type'=>'int', 'variable_name'=>'string'],
'filter_id' => ['int|false', 'filtername'=>'string'],
'filter_input' => ['mixed', 'type'=>'int', 'variable_name'=>'string', 'filter='=>'int', 'options='=>'array|int'],
'filter_input_array' => ['mixed', 'type'=>'int', 'definition='=>'int|array', 'add_empty='=>'bool'],
'filter_list' => ['array'],
'filter_var' => ['mixed', 'variable'=>'mixed', 'filter='=>'int', 'options='=>'mixed'],
'filter_var_array' => ['mixed', 'data'=>'array', 'definition='=>'mixed', 'add_empty='=>'bool'],
'FilterIterator::__construct' => ['void', 'it'=>'iterator'],
'FilterIterator::accept' => ['bool'],
'FilterIterator::current' => ['mixed'],
'FilterIterator::getInnerIterator' => ['Iterator'],
'FilterIterator::key' => ['mixed'],
'FilterIterator::next' => ['void'],
'FilterIterator::rewind' => ['void'],
'FilterIterator::valid' => ['bool'],
'finfo::__construct' => ['void', 'options='=>'int', 'magic_file='=>'string'],
'finfo::buffer' => ['string', 'string'=>'string', 'options='=>'int', 'context='=>'resource'],
'finfo::file' => ['string', 'file_name'=>'string', 'options='=>'int', 'context='=>'resource'],
'finfo::set_flags' => ['bool', 'options'=>'int'],
'finfo_buffer' => ['string', 'finfo'=>'resource', 'string'=>'string', 'options='=>'int', 'context='=>'resource'],
'finfo_close' => ['bool', 'finfo'=>'resource'],
'finfo_file' => ['string', 'finfo'=>'resource', 'file_name'=>'string', 'options='=>'int', 'context='=>'resource'],
'finfo_open' => ['resource', 'options='=>'int', 'arg='=>'string'],
'finfo_set_flags' => ['bool', 'finfo'=>'resource', 'options'=>'int'],
'floatval' => ['float', 'var'=>'mixed'],
'flock' => ['bool', 'fp'=>'resource', 'operation'=>'int', '&w_wouldblock='=>'int'],
'floor' => ['float', 'number'=>'float'],
'flush' => ['void'],
'fmod' => ['float', 'x'=>'float', 'y'=>'float'],
'fnmatch' => ['bool', 'pattern'=>'string', 'filename'=>'string', 'flags='=>'int'],
'fopen' => ['resource|false', 'filename'=>'string', 'mode'=>'string', 'use_include_path='=>'bool', 'context='=>'resource'],
'forward_static_call' => ['mixed', 'function'=>'callable', '...parameters='=>'mixed'],
'forward_static_call_array' => ['mixed', 'function'=>'callable', 'parameters'=>'array<int,mixed>'],
'fpassthru' => ['int', 'fp'=>'resource'],
'fprintf' => ['int', 'stream'=>'resource', 'format'=>'string', '...args='=>'string|int|float'],
'fputcsv' => ['int', 'fp'=>'resource', 'fields'=>'array', 'delimiter='=>'string', 'enclosure='=>'string', 'escape_char='=>'string'],
'fputs' => ['int|false', 'fp'=>'resource', 'str'=>'string', 'length='=>'int'],
'fread' => ['string|false', 'fp'=>'resource', 'length'=>'int'],
'frenchtojd' => ['int', 'month'=>'int', 'day'=>'int', 'year'=>'int'],
'fribidi_log2vis' => ['string', 'str'=>'string', 'direction'=>'string', 'charset'=>'int'],
'fscanf' => ['array|int', 'stream'=>'resource', 'format'=>'string', '&...w_vars='=>'string|int|float'],
'fseek' => ['int', 'fp'=>'resource', 'offset'=>'int', 'whence='=>'int'],
'fsockopen' => ['resource|false', 'hostname'=>'string', 'port='=>'int', '&w_errno='=>'int', '&w_errstr='=>'string', 'timeout='=>'float'],
'fstat' => ['array', 'fp'=>'resource'],
'ftell' => ['int', 'fp'=>'resource'],
'ftok' => ['int', 'pathname'=>'string', 'proj'=>'string'],
'ftp_alloc' => ['bool', 'stream'=>'resource', 'size'=>'int', 'response='=>'string'],
'ftp_append' => ['bool', 'ftp'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'int'],
'ftp_cdup' => ['bool', 'stream'=>'resource'],
'ftp_chdir' => ['bool', 'stream'=>'resource', 'directory'=>'string'],
'ftp_chmod' => ['int', 'stream'=>'resource', 'mode'=>'int', 'filename'=>'string'],
'ftp_close' => ['bool', 'stream'=>'resource'],
'ftp_connect' => ['resource', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'],
'ftp_delete' => ['bool', 'stream'=>'resource', 'file'=>'string'],
'ftp_exec' => ['bool', 'stream'=>'resource', 'command'=>'string'],
'ftp_fget' => ['bool', 'stream'=>'resource', 'fp'=>'resource', 'remote_file'=>'string', 'mode'=>'int', 'resumepos='=>'int'],
'ftp_fput' => ['bool', 'stream'=>'resource', 'remote_file'=>'string', 'fp'=>'resource', 'mode'=>'int', 'startpos='=>'int'],
'ftp_get' => ['bool', 'stream'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'mode'=>'int', 'resume_pos='=>'int'],
'ftp_get_option' => ['mixed', 'stream'=>'resource', 'option'=>'int'],
'ftp_login' => ['bool', 'stream'=>'resource', 'username'=>'string', 'password'=>'string'],
'ftp_mdtm' => ['int', 'stream'=>'resource', 'filename'=>'string'],
'ftp_mkdir' => ['string', 'stream'=>'resource', 'directory'=>'string'],
'ftp_mlsd' => ['array', 'ftp_stream'=>'resource', 'directory'=>'string'],
'ftp_nb_continue' => ['int', 'stream'=>'resource'],
'ftp_nb_fget' => ['int', 'stream'=>'resource', 'fp'=>'resource', 'remote_file'=>'string', 'mode'=>'int', 'resumepos='=>'int'],
'ftp_nb_fput' => ['int', 'stream'=>'resource', 'remote_file'=>'string', 'fp'=>'resource', 'mode'=>'int', 'startpos='=>'int'],
'ftp_nb_get' => ['int', 'stream'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'mode'=>'int', 'resume_pos='=>'int'],
'ftp_nb_put' => ['int', 'stream'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode'=>'int', 'startpos='=>'int'],
'ftp_nlist' => ['array', 'stream'=>'resource', 'directory'=>'string'],
'ftp_pasv' => ['bool', 'stream'=>'resource', 'pasv'=>'bool'],
'ftp_put' => ['bool', 'stream'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode'=>'int', 'startpos='=>'int'],
'ftp_pwd' => ['string', 'stream'=>'resource'],
'ftp_raw' => ['array', 'stream'=>'resource', 'command'=>'string'],
'ftp_rawlist' => ['array', 'stream'=>'resource', 'directory'=>'string', 'recursive='=>'bool'],
'ftp_rename' => ['bool', 'stream'=>'resource', 'src'=>'string', 'dest'=>'string'],
'ftp_rmdir' => ['bool', 'stream'=>'resource', 'directory'=>'string'],
'ftp_set_option' => ['bool', 'stream'=>'resource', 'option'=>'int', 'value'=>'mixed'],
'ftp_site' => ['bool', 'stream'=>'resource', 'cmd'=>'string'],
'ftp_size' => ['int', 'stream'=>'resource', 'filename'=>'string'],
'ftp_ssl_connect' => ['resource', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'],
'ftp_systype' => ['string', 'stream'=>'resource'],
'ftruncate' => ['bool', 'fp'=>'resource', 'size'=>'int'],
'func_get_arg' => ['mixed', 'arg_num'=>'int'],
'func_get_args' => ['array'],
'func_num_args' => ['int'],
'function_exists' => ['bool', 'function_name'=>'string'],
'fwrite' => ['int|false', 'fp'=>'resource', 'str'=>'string', 'length='=>'int'],
'gc_collect_cycles' => ['int'],
'gc_disable' => ['void'],
'gc_enable' => ['void'],
'gc_enabled' => ['bool'],
'gc_mem_caches' => ['int'],
'gd_info' => ['array'],
'gearman_bugreport' => [''],
'gearman_client_add_options' => ['', 'client_object'=>'', 'option'=>''],
'gearman_client_add_server' => ['', 'client_object'=>'', 'host'=>'', 'port'=>''],
'gearman_client_add_servers' => ['', 'client_object'=>'', 'servers'=>''],
'gearman_client_add_task' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''],
'gearman_client_add_task_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''],
'gearman_client_add_task_high' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''],
'gearman_client_add_task_high_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''],
'gearman_client_add_task_low' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''],
'gearman_client_add_task_low_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'context'=>'', 'unique'=>''],
'gearman_client_add_task_status' => ['', 'client_object'=>'', 'job_handle'=>'', 'context'=>''],
'gearman_client_clear_fn' => ['', 'client_object'=>''],
'gearman_client_clone' => ['', 'client_object'=>''],
'gearman_client_context' => ['', 'client_object'=>''],
'gearman_client_create' => ['', 'client_object'=>''],
'gearman_client_do' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''],
'gearman_client_do_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''],
'gearman_client_do_high' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''],
'gearman_client_do_high_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''],
'gearman_client_do_job_handle' => ['', 'client_object'=>''],
'gearman_client_do_low' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''],
'gearman_client_do_low_background' => ['', 'client_object'=>'', 'function_name'=>'', 'workload'=>'', 'unique'=>''],
'gearman_client_do_normal' => ['', 'client_object'=>'', 'function_name'=>'string', 'workload'=>'string', 'unique'=>'string'],
'gearman_client_do_status' => ['', 'client_object'=>''],
'gearman_client_echo' => ['', 'client_object'=>'', 'workload'=>''],
'gearman_client_errno' => ['', 'client_object'=>''],
'gearman_client_error' => ['', 'client_object'=>''],
'gearman_client_job_status' => ['', 'client_object'=>'', 'job_handle'=>''],
'gearman_client_options' => ['', 'client_object'=>''],
'gearman_client_remove_options' => ['', 'client_object'=>'', 'option'=>''],
'gearman_client_return_code' => ['', 'client_object'=>''],
'gearman_client_run_tasks' => ['', 'data'=>''],
'gearman_client_set_complete_fn' => ['', 'client_object'=>'', 'callback'=>''],
'gearman_client_set_context' => ['', 'client_object'=>'', 'context'=>''],
'gearman_client_set_created_fn' => ['', 'client_object'=>'', 'callback'=>''],
'gearman_client_set_data_fn' => ['', 'client_object'=>'', 'callback'=>''],
'gearman_client_set_exception_fn' => ['', 'client_object'=>'', 'callback'=>''],
'gearman_client_set_fail_fn' => ['', 'client_object'=>'', 'callback'=>''],
'gearman_client_set_options' => ['', 'client_object'=>'', 'option'=>''],
'gearman_client_set_status_fn' => ['', 'client_object'=>'', 'callback'=>''],
'gearman_client_set_timeout' => ['', 'client_object'=>'', 'timeout'=>''],
'gearman_client_set_warning_fn' => ['', 'client_object'=>'', 'callback'=>''],
'gearman_client_set_workload_fn' => ['', 'client_object'=>'', 'callback'=>''],
'gearman_client_timeout' => ['', 'client_object'=>''],
'gearman_client_wait' => ['', 'client_object'=>''],
'gearman_job_function_name' => ['', 'job_object'=>''],
'gearman_job_handle' => ['string'],
'gearman_job_return_code' => ['', 'job_object'=>''],
'gearman_job_send_complete' => ['', 'job_object'=>'', 'result'=>''],
'gearman_job_send_data' => ['', 'job_object'=>'', 'data'=>''],
'gearman_job_send_exception' => ['', 'job_object'=>'', 'exception'=>''],
'gearman_job_send_fail' => ['', 'job_object'=>''],
'gearman_job_send_status' => ['', 'job_object'=>'', 'numerator'=>'', 'denominator'=>''],
'gearman_job_send_warning' => ['', 'job_object'=>'', 'warning'=>''],
'gearman_job_status' => ['array', 'job_handle'=>'string'],
'gearman_job_unique' => ['', 'job_object'=>''],
'gearman_job_workload' => ['', 'job_object'=>''],
'gearman_job_workload_size' => ['', 'job_object'=>''],
'gearman_task_data' => ['', 'task_object'=>''],
'gearman_task_data_size' => ['', 'task_object'=>''],
'gearman_task_denominator' => ['', 'task_object'=>''],
'gearman_task_function_name' => ['', 'task_object'=>''],
'gearman_task_is_known' => ['', 'task_object'=>''],
'gearman_task_is_running' => ['', 'task_object'=>''],
'gearman_task_job_handle' => ['', 'task_object'=>''],
'gearman_task_numerator' => ['', 'task_object'=>''],
'gearman_task_recv_data' => ['', 'task_object'=>'', 'data_len'=>''],
'gearman_task_return_code' => ['', 'task_object'=>''],
'gearman_task_send_workload' => ['', 'task_object'=>'', 'data'=>''],
'gearman_task_unique' => ['', 'task_object'=>''],
'gearman_verbose_name' => ['', 'verbose'=>''],
'gearman_version' => [''],
'gearman_worker_add_function' => ['', 'worker_object'=>'', 'function_name'=>'', 'function'=>'', 'data'=>'', 'timeout'=>''],
'gearman_worker_add_options' => ['', 'worker_object'=>'', 'option'=>''],
'gearman_worker_add_server' => ['', 'worker_object'=>'', 'host'=>'', 'port'=>''],
'gearman_worker_add_servers' => ['', 'worker_object'=>'', 'servers'=>''],
'gearman_worker_clone' => ['', 'worker_object'=>''],
'gearman_worker_create' => [''],
'gearman_worker_echo' => ['', 'worker_object'=>'', 'workload'=>''],
'gearman_worker_errno' => ['', 'worker_object'=>''],
'gearman_worker_error' => ['', 'worker_object'=>''],
'gearman_worker_grab_job' => ['', 'worker_object'=>''],
'gearman_worker_options' => ['', 'worker_object'=>''],
'gearman_worker_register' => ['', 'worker_object'=>'', 'function_name'=>'', 'timeout'=>''],
'gearman_worker_remove_options' => ['', 'worker_object'=>'', 'option'=>''],
'gearman_worker_return_code' => ['', 'worker_object'=>''],
'gearman_worker_set_options' => ['', 'worker_object'=>'', 'option'=>''],
'gearman_worker_set_timeout' => ['', 'worker_object'=>'', 'timeout'=>''],
'gearman_worker_timeout' => ['', 'worker_object'=>''],
'gearman_worker_unregister' => ['', 'worker_object'=>'', 'function_name'=>''],
'gearman_worker_unregister_all' => ['', 'worker_object'=>''],
'gearman_worker_wait' => ['', 'worker_object'=>''],
'gearman_worker_work' => ['', 'worker_object'=>''],
'GearmanClient::__construct' => ['void'],
'GearmanClient::addOptions' => ['bool', 'options'=>'int'],
'GearmanClient::addServer' => ['bool', 'host='=>'string', 'port='=>'int'],
'GearmanClient::addServers' => ['bool', 'servers='=>'string'],
'GearmanClient::addTask' => ['GearmanTask', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'],
'GearmanClient::addTaskBackground' => ['GearmanTask', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'],
'GearmanClient::addTaskHigh' => ['GearmanTask', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'],
'GearmanClient::addTaskHighBackground' => ['GearmanTask', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'],
'GearmanClient::addTaskLow' => ['GearmanTask', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'],
'GearmanClient::addTaskLowBackground' => ['GearmanTask', 'function_name'=>'string', 'workload'=>'string', 'context='=>'mixed', 'unique='=>'string'],
'GearmanClient::addTaskStatus' => ['GearmanTask', 'job_handle'=>'string', 'context='=>'string'],
'GearmanClient::clearCallbacks' => ['bool'],
'GearmanClient::clone' => ['GearmanClient'],
'GearmanClient::context' => ['string'],
'GearmanClient::data' => ['string'],
'GearmanClient::do' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'],
'GearmanClient::doBackground' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'],
'GearmanClient::doHigh' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'],
'GearmanClient::doHighBackground' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'],
'GearmanClient::doJobHandle' => ['string'],
'GearmanClient::doLow' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'],
'GearmanClient::doLowBackground' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'],
'GearmanClient::doNormal' => ['string', 'function_name'=>'string', 'workload'=>'string', 'unique='=>'string'],
'GearmanClient::doStatus' => ['array'],
'GearmanClient::echo' => ['bool', 'workload'=>'string'],
'GearmanClient::error' => ['string'],
'GearmanClient::getErrno' => ['int'],
'GearmanClient::jobStatus' => ['array', 'job_handle'=>'string'],
'GearmanClient::options' => [''],
'GearmanClient::ping' => ['bool', 'workload'=>'string'],
'GearmanClient::removeOptions' => ['bool', 'options'=>'int'],
'GearmanClient::returnCode' => ['int'],
'GearmanClient::runTasks' => ['bool'],
'GearmanClient::setClientCallback' => ['void', 'callback'=>'callable'],
'GearmanClient::setCompleteCallback' => ['bool', 'callback'=>'callable'],
'GearmanClient::setContext' => ['bool', 'context'=>'string'],
'GearmanClient::setCreatedCallback' => ['bool', 'callback'=>'string'],
'GearmanClient::setData' => ['bool', 'data'=>'string'],
'GearmanClient::setDataCallback' => ['bool', 'callback'=>'callable'],
'GearmanClient::setExceptionCallback' => ['bool', 'callback'=>'callable'],
'GearmanClient::setFailCallback' => ['bool', 'callback'=>'callable'],
'GearmanClient::setOptions' => ['bool', 'options'=>'int'],
'GearmanClient::setStatusCallback' => ['bool', 'callback'=>'callable'],
'GearmanClient::setTimeout' => ['bool', 'timeout'=>'int'],
'GearmanClient::setWarningCallback' => ['bool', 'callback'=>'callable'],
'GearmanClient::setWorkloadCallback' => ['bool', 'callback'=>'callable'],
'GearmanClient::timeout' => ['int'],
'GearmanClient::wait' => [''],
'GearmanJob::__construct' => ['void'],
'GearmanJob::complete' => ['bool', 'result'=>'string'],
'GearmanJob::data' => ['bool', 'data'=>'string'],
'GearmanJob::exception' => ['bool', 'exception'=>'string'],
'GearmanJob::fail' => ['bool'],
'GearmanJob::functionName' => ['string'],
'GearmanJob::handle' => ['string'],
'GearmanJob::returnCode' => ['int'],
'GearmanJob::sendComplete' => ['bool', 'result'=>'string'],
'GearmanJob::sendData' => ['bool', 'data'=>'string'],
'GearmanJob::sendException' => ['bool', 'exception'=>'string'],
'GearmanJob::sendFail' => ['bool'],
'GearmanJob::sendStatus' => ['bool', 'numerator'=>'int', 'denominator'=>'int'],
'GearmanJob::sendWarning' => ['bool', 'warning'=>'string'],
'GearmanJob::setReturn' => ['bool', 'gearman_return_t'=>'string'],
'GearmanJob::status' => ['bool', 'numerator'=>'int', 'denominator'=>'int'],
'GearmanJob::unique' => ['string'],
'GearmanJob::warning' => ['bool', 'warning'=>'string'],
'GearmanJob::workload' => ['string'],
'GearmanJob::workloadSize' => ['int'],
'GearmanTask::__construct' => ['void'],
'GearmanTask::create' => ['GearmanTask'],
'GearmanTask::data' => ['string'],
'GearmanTask::dataSize' => ['int'],
'GearmanTask::function' => ['string'],
'GearmanTask::functionName' => ['string'],
'GearmanTask::isKnown' => ['bool'],
'GearmanTask::isRunning' => ['bool'],
'GearmanTask::jobHandle' => ['string'],
'GearmanTask::recvData' => ['array', 'data_len'=>'int'],
'GearmanTask::returnCode' => ['int'],
'GearmanTask::sendData' => ['int', 'data'=>'string'],
'GearmanTask::sendWorkload' => ['int', 'data'=>'string'],
'GearmanTask::taskDenominator' => ['int'],
'GearmanTask::taskNumerator' => ['int'],
'GearmanTask::unique' => ['string'],
'GearmanTask::uuid' => ['string'],
'GearmanWorker::__construct' => ['void'],
'GearmanWorker::addFunction' => ['bool', 'function_name'=>'string', 'function'=>'callable', 'context='=>'mixed', 'timeout='=>'int'],
'GearmanWorker::addOptions' => ['bool', 'option'=>'int'],
'GearmanWorker::addServer' => ['bool', 'host='=>'string', 'port='=>'int'],
'GearmanWorker::addServers' => ['bool', 'servers'=>'string'],
'GearmanWorker::clone' => ['void'],
'GearmanWorker::echo' => ['bool', 'workload'=>'string'],
'GearmanWorker::error' => ['string'],
'GearmanWorker::getErrno' => ['int'],
'GearmanWorker::grabJob' => [''],
'GearmanWorker::options' => ['int'],
'GearmanWorker::register' => ['bool', 'function_name'=>'string', 'timeout='=>'int'],
'GearmanWorker::removeOptions' => ['bool', 'option'=>'int'],
'GearmanWorker::returnCode' => ['int'],
'GearmanWorker::setId' => ['bool', 'id'=>'string'],
'GearmanWorker::setOptions' => ['bool', 'option'=>'int'],
'GearmanWorker::setTimeout' => ['bool', 'timeout'=>'int'],
'GearmanWorker::timeout' => ['int'],
'GearmanWorker::unregister' => ['bool', 'function_name'=>'string'],
'GearmanWorker::unregisterAll' => ['bool'],
'GearmanWorker::wait' => ['bool'],
'GearmanWorker::work' => ['bool'],
'Gender\Gender::__construct' => ['void', 'dsn='=>'string'],
'Gender\Gender::connect' => ['bool', 'dsn'=>'string'],
'Gender\Gender::country' => ['array', 'country'=>'int'],
'Gender\Gender::get' => ['int', 'name'=>'string', 'country='=>'int'],
'Gender\Gender::isNick' => ['array', 'name0'=>'string', 'name1'=>'string', 'country='=>'int'],
'Gender\Gender::similarNames' => ['array', 'name'=>'string', 'country='=>'int'],
'Generator::__wakeup' => ['void'],
'Generator::current' => [''],
'Generator::getReturn' => [''],
'Generator::key' => [''],
'Generator::next' => [''],
'Generator::rewind' => [''],
'Generator::send' => ['', 'value'=>''],
'Generator::throw' => ['', 'exception'=>'exception'],
'Generator::valid' => ['bool'],
'geoip_asnum_by_name' => ['string', 'hostname'=>'string'],
'geoip_continent_code_by_name' => ['string', 'hostname'=>'string'],
'geoip_country_code3_by_name' => ['string', 'hostname'=>'string'],
'geoip_country_code_by_name' => ['string', 'hostname'=>'string'],
'geoip_country_name_by_name' => ['string', 'hostname'=>'string'],
'geoip_database_info' => ['string', 'database='=>'int'],
'geoip_db_avail' => ['bool', 'database'=>'int'],
'geoip_db_filename' => ['string', 'database'=>'int'],
'geoip_db_get_all_info' => ['array'],
'geoip_domain_by_name' => ['string', 'hostname'=>'string'],
'geoip_id_by_name' => ['int', 'hostname'=>'string'],
'geoip_isp_by_name' => ['string', 'hostname'=>'string'],
'geoip_netspeedcell_by_name' => ['string', 'hostname'=>'string'],
'geoip_org_by_name' => ['string', 'hostname'=>'string'],
'geoip_record_by_name' => ['array', 'hostname'=>'string'],
'geoip_region_by_name' => ['array', 'hostname'=>'string'],
'geoip_region_name_by_code' => ['string', 'country_code'=>'string', 'region_code'=>'string'],
'geoip_setup_custom_directory' => ['void', 'path'=>'string'],
'geoip_time_zone_by_country_and_region' => ['string|false', 'country_code'=>'string', 'region_code='=>'string'],
'get_browser' => ['mixed', 'browser_name='=>'string', 'return_array='=>'bool'],
'get_call_stack' => [''],
'get_called_class' => ['string'],
'get_cfg_var' => ['mixed', 'option_name'=>'string'],
'get_class' => ['string', 'object='=>'object'],
'get_class_methods' => ['array', 'class'=>'mixed'],
'get_class_vars' => ['array', 'class_name'=>'string'],
'get_current_user' => ['string'],
'get_declared_classes' => ['array'],
'get_declared_interfaces' => ['array'],
'get_declared_traits' => ['array'],
'get_defined_constants' => ['array', 'categorize='=>'bool'],
'get_defined_functions' => ['array<string,array<string,string>>', 'exclude_disabled='=>'bool'],
'get_defined_vars' => ['array'],
'get_extension_funcs' => ['array', 'extension_name'=>'string'],
'get_headers' => ['array', 'url'=>'string', 'format='=>'int'],
'get_html_translation_table' => ['array', 'table='=>'int', 'flags='=>'int', 'encoding='=>'string'],
'get_include_path' => ['string'],
'get_included_files' => ['array'],
'get_loaded_extensions' => ['array', 'zend_extensions='=>'bool'],
'get_magic_quotes_gpc' => ['bool'],
'get_magic_quotes_runtime' => ['bool'],
'get_meta_tags' => ['array', 'filename'=>'string', 'use_include_path='=>'bool'],
'get_object_vars' => ['array', 'obj'=>'object'],
'get_parent_class' => ['string', 'object='=>'mixed'],
'get_required_files' => ['string[]'],
'get_resource_type' => ['string', 'res'=>'resource'],
'get_resources' => ['resource[]', 'resource_type'=>'string'],
'getallheaders' => ['array'],
'getcwd' => ['string|false'],
'getdate' => ['array', 'timestamp='=>'int'],
'getenv' => ['string|array<string,string>|false', 'varname='=>'string', 'local_only='=>'bool'],
'gethostbyaddr' => ['string', 'ip_address'=>'string'],
'gethostbyname' => ['string', 'hostname'=>'string'],
'gethostbynamel' => ['array|false', 'hostname'=>'string'],
'gethostname' => ['string'],
'getimagesize' => ['array|false', 'imagefile'=>'string', '&w_info='=>'array'],
'getimagesizefromstring' => ['array|false', 'data'=>'string', '&w_info='=>'array'],
'getlastmod' => ['int'],
'getmxrr' => ['bool', 'hostname'=>'string', '&w_mxhosts'=>'array', '&w_weight='=>'array'],
'getmygid' => ['int'],
'getmyinode' => ['int'],
'getmypid' => ['int'],
'getmyuid' => ['int'],
'getopt' => ['array<string,string>|array<string,false>|array<string,array<int,string|false>>', 'options'=>'string', 'longopts='=>'array', '&w_optind='=>'int'],
'getprotobyname' => ['int|false', 'name'=>'string'],
'getprotobynumber' => ['string', 'proto'=>'int'],
'getrandmax' => ['int'],
'getrusage' => ['array', 'who='=>'int'],
'getservbyname' => ['int|false', 'service'=>'string', 'protocol'=>'string'],
'getservbyport' => ['string|false', 'port'=>'int', 'protocol'=>'string'],
'gettext' => ['string', 'msgid'=>'string'],
'gettimeofday' => ['array|float', 'get_as_float='=>'bool'],
'gettype' => ['string', 'var'=>'mixed'],
'glob' => ['array', 'pattern'=>'string', 'flags='=>'int'],
'GlobIterator::__construct' => ['void', 'path'=>'string', 'flags='=>'int'],
'GlobIterator::cont' => ['int'],
'GlobIterator::count' => ['int'],
'Gmagick::__construct' => ['void', 'filename='=>'string'],
'Gmagick::addimage' => ['Gmagick', 'gmagick'=>'gmagick'],
'Gmagick::addnoiseimage' => ['Gmagick', 'noise'=>'int'],
'Gmagick::annotateimage' => ['Gmagick', 'gmagickdraw'=>'gmagickdraw', 'x'=>'float', 'y'=>'float', 'angle'=>'float', 'text'=>'string'],
'Gmagick::blurimage' => ['Gmagick', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'],
'Gmagick::borderimage' => ['Gmagick', 'color'=>'gmagickpixel', 'width'=>'int', 'height'=>'int'],
'Gmagick::charcoalimage' => ['Gmagick', 'radius'=>'float', 'sigma'=>'float'],
'Gmagick::chopimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'],
'Gmagick::clear' => ['Gmagick'],
'Gmagick::commentimage' => ['Gmagick', 'comment'=>'string'],
'Gmagick::compositeimage' => ['Gmagick', 'source'=>'gmagick', 'compose'=>'int', 'x'=>'int', 'y'=>'int'],
'Gmagick::cropimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'],
'Gmagick::cropthumbnailimage' => ['Gmagick', 'width'=>'int', 'height'=>'int'],
'Gmagick::current' => ['Gmagick'],
'Gmagick::cyclecolormapimage' => ['Gmagick', 'displace'=>'int'],
'Gmagick::deconstructimages' => ['Gmagick'],
'Gmagick::despeckleimage' => ['Gmagick'],
'Gmagick::destroy' => ['Gmagick'],
'Gmagick::drawimage' => ['Gmagick', 'gmagickdraw'=>'gmagickdraw'],
'Gmagick::edgeimage' => ['Gmagick', 'radius'=>'float'],
'Gmagick::embossimage' => ['Gmagick', 'radius'=>'float', 'sigma'=>'float'],
'Gmagick::enhanceimage' => ['Gmagick'],
'Gmagick::equalizeimage' => ['Gmagick'],
'Gmagick::flipimage' => ['Gmagick'],
'Gmagick::flopimage' => ['Gmagick'],
'Gmagick::frameimage' => ['Gmagick', 'color'=>'gmagickpixel', 'width'=>'int', 'height'=>'int', 'inner_bevel'=>'int', 'outer_bevel'=>'int'],
'Gmagick::gammaimage' => ['Gmagick', 'gamma'=>'float'],
'Gmagick::getcopyright' => ['string'],
'Gmagick::getfilename' => ['string'],
'Gmagick::getimagebackgroundcolor' => ['GmagickPixel'],
'Gmagick::getimageblueprimary' => ['array'],
'Gmagick::getimagebordercolor' => ['GmagickPixel'],
'Gmagick::getimagechanneldepth' => ['int', 'channel_type'=>'int'],
'Gmagick::getimagecolors' => ['int'],
'Gmagick::getimagecolorspace' => ['int'],
'Gmagick::getimagecompose' => ['int'],
'Gmagick::getimagedelay' => ['int'],
'Gmagick::getimagedepth' => ['int'],
'Gmagick::getimagedispose' => ['int'],
'Gmagick::getimageextrema' => ['array'],
'Gmagick::getimagefilename' => ['string'],
'Gmagick::getimageformat' => ['string'],
'Gmagick::getimagegamma' => ['float'],
'Gmagick::getimagegreenprimary' => ['array'],
'Gmagick::getimageheight' => ['int'],
'Gmagick::getimagehistogram' => ['array'],
'Gmagick::getimageindex' => ['int'],
'Gmagick::getimageinterlacescheme' => ['int'],
'Gmagick::getimageiterations' => ['int'],
'Gmagick::getimagematte' => ['int'],
'Gmagick::getimagemattecolor' => ['GmagickPixel'],
'Gmagick::getimageprofile' => ['string', 'name'=>'string'],
'Gmagick::getimageredprimary' => ['array'],
'Gmagick::getimagerenderingintent' => ['int'],
'Gmagick::getimageresolution' => ['array'],
'Gmagick::getimagescene' => ['int'],
'Gmagick::getimagesignature' => ['string'],
'Gmagick::getimagetype' => ['int'],
'Gmagick::getimageunits' => ['int'],
'Gmagick::getimagewhitepoint' => ['array'],
'Gmagick::getimagewidth' => ['int'],
'Gmagick::getpackagename' => ['string'],
'Gmagick::getquantumdepth' => ['array'],
'Gmagick::getreleasedate' => ['string'],
'Gmagick::getsamplingfactors' => ['array'],
'Gmagick::getsize' => ['array'],
'Gmagick::getversion' => ['array'],
'Gmagick::hasnextimage' => ['mixed'],
'Gmagick::haspreviousimage' => ['mixed'],
'Gmagick::implodeimage' => ['mixed', 'radius'=>'float'],
'Gmagick::labelimage' => ['mixed', 'label'=>'string'],
'Gmagick::levelimage' => ['mixed', 'blackpoint'=>'float', 'gamma'=>'float', 'whitepoint'=>'float', 'channel='=>'int'],
'Gmagick::magnifyimage' => ['mixed'],
'Gmagick::mapimage' => ['Gmagick', 'gmagick'=>'gmagick', 'dither'=>'bool'],
'Gmagick::medianfilterimage' => ['void', 'radius'=>'float'],
'Gmagick::minifyimage' => ['Gmagick'],
'Gmagick::modulateimage' => ['Gmagick', 'brightness'=>'float', 'saturation'=>'float', 'hue'=>'float'],
'Gmagick::motionblurimage' => ['Gmagick', 'radius'=>'float', 'sigma'=>'float', 'angle'=>'float'],
'Gmagick::newimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'background'=>'string', 'format='=>'string'],
'Gmagick::nextimage' => ['bool'],
'Gmagick::normalizeimage' => ['Gmagick', 'channel='=>'int'],
'Gmagick::oilpaintimage' => ['Gmagick', 'radius'=>'float'],
'Gmagick::previousimage' => ['bool'],
'Gmagick::profileimage' => ['Gmagick', 'name'=>'string', 'profile'=>'string'],
'Gmagick::quantizeimage' => ['Gmagick', 'numcolors'=>'int', 'colorspace'=>'int', 'treedepth'=>'int', 'dither'=>'bool', 'measureerror'=>'bool'],
'Gmagick::quantizeimages' => ['Gmagick', 'numcolors'=>'int', 'colorspace'=>'int', 'treedepth'=>'int', 'dither'=>'bool', 'measureerror'=>'bool'],
'Gmagick::queryfontmetrics' => ['array', 'draw'=>'gmagickdraw', 'text'=>'string'],
'Gmagick::queryfonts' => ['array', 'pattern='=>'string'],
'Gmagick::queryformats' => ['array', 'pattern='=>'string'],
'Gmagick::radialblurimage' => ['Gmagick', 'angle'=>'float', 'channel='=>'int'],
'Gmagick::raiseimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int', 'raise'=>'bool'],
'Gmagick::read' => ['Gmagick', 'filename'=>'string'],
'Gmagick::readimage' => ['Gmagick', 'filename'=>'string'],
'Gmagick::readimageblob' => ['Gmagick', 'imagecontents'=>'string', 'filename='=>'string'],
'Gmagick::readimagefile' => ['Gmagick', 'fp'=>'resource', 'filename='=>'string'],
'Gmagick::reducenoiseimage' => ['Gmagick', 'radius'=>'float'],
'Gmagick::removeimage' => ['Gmagick'],
'Gmagick::removeimageprofile' => ['string', 'name'=>'string'],
'Gmagick::resampleimage' => ['Gmagick', 'xresolution'=>'float', 'yresolution'=>'float', 'filter'=>'int', 'blur'=>'float'],
'Gmagick::resizeimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'filter'=>'int', 'blur'=>'float', 'fit='=>'bool'],
'Gmagick::rollimage' => ['Gmagick', 'x'=>'int', 'y'=>'int'],
'Gmagick::rotateimage' => ['Gmagick', 'color'=>'mixed', 'degrees'=>'float'],
'Gmagick::scaleimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'fit='=>'bool'],
'Gmagick::separateimagechannel' => ['Gmagick', 'channel'=>'int'],
'Gmagick::setCompressionQuality' => ['Gmagick', 'quality'=>'int'],
'Gmagick::setfilename' => ['Gmagick', 'filename'=>'string'],
'Gmagick::setimagebackgroundcolor' => ['Gmagick', 'color'=>'gmagickpixel'],
'Gmagick::setimageblueprimary' => ['Gmagick', 'x'=>'float', 'y'=>'float'],
'Gmagick::setimagebordercolor' => ['Gmagick', 'color'=>'gmagickpixel'],
'Gmagick::setimagechanneldepth' => ['Gmagick', 'channel'=>'int', 'depth'=>'int'],
'Gmagick::setimagecolorspace' => ['Gmagick', 'colorspace'=>'int'],
'Gmagick::setimagecompose' => ['Gmagick', 'composite'=>'int'],
'Gmagick::setimagedelay' => ['Gmagick', 'delay'=>'int'],
'Gmagick::setimagedepth' => ['Gmagick', 'depth'=>'int'],
'Gmagick::setimagedispose' => ['Gmagick', 'disposetype'=>'int'],
'Gmagick::setimagefilename' => ['Gmagick', 'filename'=>'string'],
'Gmagick::setimageformat' => ['Gmagick', 'imageformat'=>'string'],
'Gmagick::setimagegamma' => ['Gmagick', 'gamma'=>'float'],
'Gmagick::setimagegreenprimary' => ['Gmagick', 'x'=>'float', 'y'=>'float'],
'Gmagick::setimageindex' => ['Gmagick', 'index'=>'int'],
'Gmagick::setimageinterlacescheme' => ['Gmagick', 'interlace'=>'int'],
'Gmagick::setimageiterations' => ['Gmagick', 'iterations'=>'int'],
'Gmagick::setimageprofile' => ['Gmagick', 'name'=>'string', 'profile'=>'string'],
'Gmagick::setimageredprimary' => ['Gmagick', 'x'=>'float', 'y'=>'float'],
'Gmagick::setimagerenderingintent' => ['Gmagick', 'rendering_intent'=>'int'],
'Gmagick::setimageresolution' => ['Gmagick', 'xresolution'=>'float', 'yresolution'=>'float'],
'Gmagick::setimagescene' => ['Gmagick', 'scene'=>'int'],
'Gmagick::setimagetype' => ['Gmagick', 'imgtype'=>'int'],
'Gmagick::setimageunits' => ['Gmagick', 'resolution'=>'int'],
'Gmagick::setimagewhitepoint' => ['Gmagick', 'x'=>'float', 'y'=>'float'],
'Gmagick::setsamplingfactors' => ['Gmagick', 'factors'=>'array'],
'Gmagick::setsize' => ['Gmagick', 'columns'=>'int', 'rows'=>'int'],
'Gmagick::shearimage' => ['Gmagick', 'color'=>'mixed', 'xshear'=>'float', 'yshear'=>'float'],
'Gmagick::solarizeimage' => ['Gmagick', 'threshold'=>'int'],
'Gmagick::spreadimage' => ['Gmagick', 'radius'=>'float'],
'Gmagick::stripimage' => ['Gmagick'],
'Gmagick::swirlimage' => ['Gmagick', 'degrees'=>'float'],
'Gmagick::thumbnailimage' => ['Gmagick', 'width'=>'int', 'height'=>'int', 'fit='=>'bool'],
'Gmagick::trimimage' => ['Gmagick', 'fuzz'=>'float'],
'Gmagick::write' => ['', 'filename'=>'string'],
'Gmagick::writeimage' => ['Gmagick', 'filename'=>'string', 'all_frames='=>'bool'],
'GmagickDraw::annotate' => ['GmagickDraw', 'x'=>'float', 'y'=>'float', 'text'=>'string'],
'GmagickDraw::arc' => ['GmagickDraw', 'sx'=>'float', 'sy'=>'float', 'ex'=>'float', 'ey'=>'float', 'sd'=>'float', 'ed'=>'float'],
'GmagickDraw::bezier' => ['GmagickDraw', 'coordinate_array'=>'array'],
'GmagickDraw::ellipse' => ['GmagickDraw', 'ox'=>'float', 'oy'=>'float', 'rx'=>'float', 'ry'=>'float', 'start'=>'float', 'end'=>'float'],
'GmagickDraw::getfillcolor' => ['GmagickPixel'],
'GmagickDraw::getfillopacity' => ['float'],
'GmagickDraw::getfont' => ['string'],
'GmagickDraw::getfontsize' => ['float'],
'GmagickDraw::getfontstyle' => ['int'],
'GmagickDraw::getfontweight' => ['int'],
'GmagickDraw::getstrokecolor' => ['GmagickPixel'],
'GmagickDraw::getstrokeopacity' => ['float'],
'GmagickDraw::getstrokewidth' => ['float'],
'GmagickDraw::gettextdecoration' => ['int'],
'GmagickDraw::gettextencoding' => ['string'],
'GmagickDraw::line' => ['GmagickDraw', 'sx'=>'float', 'sy'=>'float', 'ex'=>'float', 'ey'=>'float'],
'GmagickDraw::point' => ['GmagickDraw', 'x'=>'float', 'y'=>'float'],
'GmagickDraw::polygon' => ['GmagickDraw', 'coordinates'=>'array'],
'GmagickDraw::polyline' => ['GmagickDraw', 'coordinate_array'=>'array'],
'GmagickDraw::rectangle' => ['GmagickDraw', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float'],
'GmagickDraw::rotate' => ['GmagickDraw', 'degrees'=>'float'],
'GmagickDraw::roundrectangle' => ['GmagickDraw', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'rx'=>'float', 'ry'=>'float'],
'GmagickDraw::scale' => ['GmagickDraw', 'x'=>'float', 'y'=>'float'],
'GmagickDraw::setfillcolor' => ['GmagickDraw', 'color'=>'string'],
'GmagickDraw::setfillopacity' => ['GmagickDraw', 'fill_opacity'=>'float'],
'GmagickDraw::setfont' => ['GmagickDraw', 'font'=>'string'],
'GmagickDraw::setfontsize' => ['GmagickDraw', 'pointsize'=>'float'],
'GmagickDraw::setfontstyle' => ['GmagickDraw', 'style'=>'int'],
'GmagickDraw::setfontweight' => ['GmagickDraw', 'weight'=>'int'],
'GmagickDraw::setstrokecolor' => ['GmagickDraw', 'color'=>'gmagickpixel'],
'GmagickDraw::setstrokeopacity' => ['GmagickDraw', 'stroke_opacity'=>'float'],
'GmagickDraw::setstrokewidth' => ['GmagickDraw', 'width'=>'float'],
'GmagickDraw::settextdecoration' => ['GmagickDraw', 'decoration'=>'int'],
'GmagickDraw::settextencoding' => ['GmagickDraw', 'encoding'=>'string'],
'GmagickPixel::__construct' => ['void', 'color='=>'string'],
'GmagickPixel::getcolor' => ['mixed', 'as_array='=>'bool', 'normalize_array='=>'bool'],
'GmagickPixel::getcolorcount' => ['int'],
'GmagickPixel::getcolorvalue' => ['float', 'color'=>'int'],
'GmagickPixel::setcolor' => ['GmagickPixel', 'color'=>'string'],
'GmagickPixel::setcolorvalue' => ['GmagickPixel', 'color'=>'int', 'value'=>'float'],
'gmdate' => ['string', 'format'=>'string', 'timestamp='=>'int'],
'gmmktime' => ['int', 'hour='=>'int', 'min='=>'int', 'sec='=>'int', 'mon='=>'int', 'day='=>'int', 'year='=>'int'],
'GMP::__construct' => ['void'],
'GMP::serialize' => ['string'],
'GMP::unserialize' => ['void', 'serialized'=>'string'],
'gmp_abs' => ['GMP', 'a'=>'GMP|string|int'],
'gmp_add' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_and' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_clrbit' => ['void', 'a'=>'GMP|string|int', 'index'=>'int'],
'gmp_cmp' => ['int', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_com' => ['GMP', 'a'=>'GMP|string|int'],
'gmp_div' => ['resource', 'a'=>'GMP|resource|string', 'b'=>'GMP|resource|string', 'round='=>'int'],
'gmp_div_q' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int', 'round='=>'int'],
'gmp_div_qr' => ['array', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int', 'round='=>'int'],
'gmp_div_r' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int', 'round='=>'int'],
'gmp_divexact' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_export' => ['string', 'gmpnumber'=>'GMP|string|int', 'word_size='=>'int', 'options='=>'int'],
'gmp_fact' => ['GMP', 'a'=>'int'],
'gmp_gcd' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_gcdext' => ['array', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_hamdist' => ['int', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_import' => ['GMP', 'data'=>'string', 'word_size='=>'int', 'options='=>'int'],
'gmp_init' => ['GMP', 'number'=>'int|string', 'base='=>'int'],
'gmp_intval' => ['int', 'gmpnumber'=>'GMP|string|int'],
'gmp_invert' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_jacobi' => ['int', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_legendre' => ['int', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_mod' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_mul' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_neg' => ['GMP', 'a'=>'GMP|string|int'],
'gmp_nextprime' => ['GMP', 'a'=>'GMP|string|int'],
'gmp_or' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_perfect_square' => ['bool', 'a'=>'GMP|string|int'],
'gmp_popcount' => ['int', 'a'=>'GMP|string|int'],
'gmp_pow' => ['GMP', 'base'=>'GMP|string|int', 'exp'=>'int'],
'gmp_powm' => ['GMP', 'base'=>'GMP|string|int', 'exp'=>'GMP|string|int', 'mod'=>'GMP|string|int'],
'gmp_prob_prime' => ['int', 'a'=>'GMP|string|int', 'reps='=>'int'],
'gmp_random' => ['GMP', 'limiter='=>'int'],
'gmp_random_bits' => ['GMP', 'bits'=>'int'],
'gmp_random_range' => ['GMP', 'min'=>'GMP|string|int', 'max'=>'GMP|string|int'],
'gmp_random_seed' => ['GMP', 'seed'=>'GMP|string|int'],
'gmp_root' => ['GMP', 'a'=>'GMP|string|int', 'nth'=>'int'],
'gmp_rootrem' => ['array<int,GMP>', 'a'=>'GMP|string|int', 'nth'=>'int'],
'gmp_scan0' => ['int', 'a'=>'GMP|string|int', 'start'=>'int'],
'gmp_scan1' => ['int', 'a'=>'GMP|string|int', 'start'=>'int'],
'gmp_setbit' => ['void', 'a'=>'GMP|string|int', 'index'=>'int', 'set_clear='=>'bool'],
'gmp_sign' => ['int', 'a'=>'GMP|string|int'],
'gmp_sqrt' => ['GMP', 'a'=>'GMP|string|int'],
'gmp_sqrtrem' => ['array', 'a'=>'GMP|string|int'],
'gmp_strval' => ['string', 'gmpnumber'=>'GMP|string|int', 'base='=>'int'],
'gmp_sub' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmp_testbit' => ['bool', 'a'=>'GMP|string|int', 'index'=>'int'],
'gmp_xor' => ['GMP', 'a'=>'GMP|string|int', 'b'=>'GMP|string|int'],
'gmstrftime' => ['string', 'format'=>'string', 'timestamp='=>'int'],
'gnupg::adddecryptkey' => ['bool', 'fingerprint'=>'string', 'passphrase'=>'string'],
'gnupg::addencryptkey' => ['bool', 'fingerprint'=>'string'],
'gnupg::addsignkey' => ['bool', 'fingerprint'=>'string', 'passphrase='=>'string'],
'gnupg::cleardecryptkeys' => ['bool'],
'gnupg::clearencryptkeys' => ['bool'],
'gnupg::clearsignkeys' => ['bool'],
'gnupg::decrypt' => ['string', 'text'=>'string'],
'gnupg::decryptverify' => ['array', 'text'=>'string', '&plaintext'=>'string'],
'gnupg::encrypt' => ['string', 'plaintext'=>'string'],
'gnupg::encryptsign' => ['string', 'plaintext'=>'string'],
'gnupg::export' => ['string', 'fingerprint'=>'string'],
'gnupg::geterror' => ['string'],
'gnupg::getprotocol' => ['int'],
'gnupg::import' => ['array', 'keydata'=>'string'],
'gnupg::init' => ['resource'],
'gnupg::keyinfo' => ['array', 'pattern'=>'string'],
'gnupg::setarmor' => ['bool', 'armor'=>'int'],
'gnupg::seterrormode' => ['void', 'errormode'=>'int'],
'gnupg::setsignmode' => ['bool', 'signmode'=>'int'],
'gnupg::sign' => ['string', 'plaintext'=>'string'],
'gnupg::verify' => ['array', 'signed_text'=>'string', 'signature'=>'string', '&plaintext='=>'string'],
'gnupg_adddecryptkey' => ['bool', 'identifier'=>'resource', 'fingerprint'=>'string', 'passphrase'=>'string'],
'gnupg_addencryptkey' => ['bool', 'identifier'=>'resource', 'fingerprint'=>'string'],
'gnupg_addsignkey' => ['bool', 'identifier'=>'resource', 'fingerprint'=>'string', 'passphrase='=>'string'],
'gnupg_cleardecryptkeys' => ['bool', 'identifier'=>'resource'],
'gnupg_clearencryptkeys' => ['bool', 'identifier'=>'resource'],
'gnupg_clearsignkeys' => ['bool', 'identifier'=>'resource'],
'gnupg_decrypt' => ['string', 'identifier'=>'resource', 'text'=>'string'],
'gnupg_decryptverify' => ['array', 'identifier'=>'resource', 'text'=>'string', 'plaintext'=>'string'],
'gnupg_encrypt' => ['string', 'identifier'=>'resource', 'plaintext'=>'string'],
'gnupg_encryptsign' => ['string', 'identifier'=>'resource', 'plaintext'=>'string'],
'gnupg_export' => ['string', 'identifier'=>'resource', 'fingerprint'=>'string'],
'gnupg_geterror' => ['string', 'identifier'=>'resource'],
'gnupg_getprotocol' => ['int', 'identifier'=>'resource'],
'gnupg_import' => ['array', 'identifier'=>'resource', 'keydata'=>'string'],
'gnupg_init' => ['resource'],
'gnupg_keyinfo' => ['array', 'identifier'=>'resource', 'pattern'=>'string'],
'gnupg_setarmor' => ['bool', 'identifier'=>'resource', 'armor'=>'int'],
'gnupg_seterrormode' => ['void', 'identifier'=>'resource', 'errormode'=>'int'],
'gnupg_setsignmode' => ['bool', 'identifier'=>'resource', 'signmode'=>'int'],
'gnupg_sign' => ['string', 'identifier'=>'resource', 'plaintext'=>'string'],
'gnupg_verify' => ['array', 'identifier'=>'resource', 'signed_text'=>'string', 'signature'=>'string', 'plaintext='=>'string'],
'gopher_parsedir' => ['array', 'dirent'=>'string'],
'grapheme_extract' => ['string|false', 'str'=>'string', 'size'=>'int', 'extract_type='=>'int', 'start='=>'int', '&w_next='=>'int'],
'grapheme_stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'grapheme_stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool'],
'grapheme_strlen' => ['int|false', 'str'=>'string'],
'grapheme_strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'grapheme_strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'grapheme_strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'grapheme_strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool'],
'grapheme_substr' => ['string|false', 'str'=>'string', 'start'=>'int', 'length='=>'int'],
'gregoriantojd' => ['int', 'month'=>'int', 'day'=>'int', 'year'=>'int'],
'gridObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'Grpc\Call::__construct' => ['void', 'channel'=>'Grpc\Channel', 'method'=>'string', 'absolute_deadline'=>'Grpc\Timeval', 'host_override='=>'mixed'],
'Grpc\Call::cancel' => [''],
'Grpc\Call::getPeer' => ['string'],
'Grpc\Call::setCredentials' => ['int', 'creds_obj'=>'Grpc\CallCredentials'],
'Grpc\Call::startBatch' => ['object', 'batch'=>'array'],
'Grpc\CallCredentials::createComposite' => ['Grpc\CallCredentials', 'cred1'=>'Grpc\CallCredentials', 'cred2'=>'Grpc\CallCredentials'],
'Grpc\CallCredentials::createFromPlugin' => ['Grpc\CallCredentials', 'callback'=>'Closure'],
'Grpc\Channel::__construct' => ['void', 'target'=>'string', 'args='=>'array'],
'Grpc\Channel::close' => [''],
'Grpc\Channel::getConnectivityState' => ['int', 'try_to_connect='=>'bool|false'],
'Grpc\Channel::getTarget' => ['string'],
'Grpc\Channel::watchConnectivityState' => ['bool', 'last_state'=>'int', 'deadline_obj'=>'Grpc\Timeval'],
'Grpc\ChannelCredentials::createComposite' => ['Grpc\ChannelCredentials', 'cred1'=>'Grpc\ChannelCredentials', 'cred2'=>'Grpc\CallCredentials'],
'Grpc\ChannelCredentials::createDefault' => ['Grpc\ChannelCredentials'],
'Grpc\ChannelCredentials::createInsecure' => ['null'],
'Grpc\ChannelCredentials::createSsl' => ['Grpc\ChannelCredentials', 'pem_root_certs'=>'string', 'pem_private_key='=>'string', 'pem_cert_chain='=>'string'],
'Grpc\ChannelCredentials::setDefaultRootsPem' => ['', 'pem_roots'=>'string'],
'Grpc\Server::__construct' => ['void', 'args'=>'array'],
'Grpc\Server::addHttp2Port' => ['bool', 'addr'=>'string'],
'Grpc\Server::addSecureHttp2Port' => ['bool', 'addr'=>'string', 'creds_obj'=>'Grpc\ServerCredentials'],
'Grpc\Server::requestCall' => ['', 'tag_new'=>'int', 'tag_cancel'=>'int'],
'Grpc\Server::start' => [''],
'Grpc\ServerCredentials::createSsl' => ['object', 'pem_root_certs'=>'string', 'pem_private_key'=>'string', 'pem_cert_chain'=>'string'],
'Grpc\Timeval::__construct' => ['void', 'usec'=>'int'],
'Grpc\Timeval::add' => ['Grpc\Timeval', 'other'=>'Grpc\Timeval'],
'Grpc\Timeval::compare' => ['int', 'a'=>'Grpc\Timeval', 'b'=>'Grpc\Timeval'],
'Grpc\Timeval::infFuture' => ['Grpc\Timeval'],
'Grpc\Timeval::infPast' => ['Grpc\Timeval'],
'Grpc\Timeval::now' => ['Grpc\Timeval'],
'Grpc\Timeval::similar' => ['bool', 'a'=>'Grpc\Timeval', 'b'=>'Grpc\Timeval', 'threshold'=>'Grpc\Timeval'],
'Grpc\Timeval::sleepUntil' => [''],
'Grpc\Timeval::subtract' => ['Grpc\Timeval', 'other'=>'Grpc\Timeval'],
'Grpc\Timeval::zero' => ['Grpc\Timeval'],
'gupnp_context_get_host_ip' => ['string', 'context'=>'resource'],
'gupnp_context_get_port' => ['int', 'context'=>'resource'],
'gupnp_context_get_subscription_timeout' => ['int', 'context'=>'resource'],
'gupnp_context_host_path' => ['bool', 'context'=>'resource', 'local_path'=>'string', 'server_path'=>'string'],
'gupnp_context_new' => ['resource', 'host_ip='=>'string', 'port='=>'int'],
'gupnp_context_set_subscription_timeout' => ['void', 'context'=>'resource', 'timeout'=>'int'],
'gupnp_context_timeout_add' => ['bool', 'context'=>'resource', 'timeout'=>'int', 'callback'=>'mixed', 'arg='=>'mixed'],
'gupnp_context_unhost_path' => ['bool', 'context'=>'resource', 'server_path'=>'string'],
'gupnp_control_point_browse_start' => ['bool', 'cpoint'=>'resource'],
'gupnp_control_point_browse_stop' => ['bool', 'cpoint'=>'resource'],
'gupnp_control_point_callback_set' => ['bool', 'cpoint'=>'resource', 'signal'=>'int', 'callback'=>'mixed', 'arg='=>'mixed'],
'gupnp_control_point_new' => ['resource', 'context'=>'resource', 'target'=>'string'],
'gupnp_device_action_callback_set' => ['bool', 'root_device'=>'resource', 'signal'=>'int', 'action_name'=>'string', 'callback'=>'mixed', 'arg='=>'mixed'],
'gupnp_device_info_get' => ['array', 'root_device'=>'resource'],
'gupnp_device_info_get_service' => ['resource', 'root_device'=>'resource', 'type'=>'string'],
'gupnp_root_device_get_available' => ['bool', 'root_device'=>'resource'],
'gupnp_root_device_get_relative_location' => ['string', 'root_device'=>'resource'],
'gupnp_root_device_new' => ['resource', 'context'=>'resource', 'location'=>'string', 'description_dir'=>'string'],
'gupnp_root_device_set_available' => ['bool', 'root_device'=>'resource', 'available'=>'bool'],
'gupnp_root_device_start' => ['bool', 'root_device'=>'resource'],
'gupnp_root_device_stop' => ['bool', 'root_device'=>'resource'],
'gupnp_service_action_get' => ['mixed', 'action'=>'resource', 'name'=>'string', 'type'=>'int'],
'gupnp_service_action_return' => ['bool', 'action'=>'resource'],
'gupnp_service_action_return_error' => ['bool', 'action'=>'resource', 'error_code'=>'int', 'error_description='=>'string'],
'gupnp_service_action_set' => ['bool', 'action'=>'resource', 'name'=>'string', 'type'=>'int', 'value'=>'mixed'],
'gupnp_service_freeze_notify' => ['bool', 'service'=>'resource'],
'gupnp_service_info_get' => ['array', 'proxy'=>'resource'],
'gupnp_service_info_get_introspection' => ['mixed', 'proxy'=>'resource', 'callback='=>'mixed', 'arg='=>'mixed'],
'gupnp_service_introspection_get_state_variable' => ['array', 'introspection'=>'resource', 'variable_name'=>'string'],
'gupnp_service_notify' => ['bool', 'service'=>'resource', 'name'=>'string', 'type'=>'int', 'value'=>'mixed'],
'gupnp_service_proxy_action_get' => ['mixed', 'proxy'=>'resource', 'action'=>'string', 'name'=>'string', 'type'=>'int'],
'gupnp_service_proxy_action_set' => ['bool', 'proxy'=>'resource', 'action'=>'string', 'name'=>'string', 'value'=>'mixed', 'type'=>'int'],
'gupnp_service_proxy_add_notify' => ['bool', 'proxy'=>'resource', 'value'=>'string', 'type'=>'int', 'callback'=>'mixed', 'arg='=>'mixed'],
'gupnp_service_proxy_callback_set' => ['bool', 'proxy'=>'resource', 'signal'=>'int', 'callback'=>'mixed', 'arg='=>'mixed'],
'gupnp_service_proxy_get_subscribed' => ['bool', 'proxy'=>'resource'],
'gupnp_service_proxy_remove_notify' => ['bool', 'proxy'=>'resource', 'value'=>'string'],
'gupnp_service_proxy_send_action' => ['array', 'proxy'=>'resource', 'action'=>'string', 'in_params'=>'array', 'out_params'=>'array'],
'gupnp_service_proxy_set_subscribed' => ['bool', 'proxy'=>'resource', 'subscribed'=>'bool'],
'gupnp_service_thaw_notify' => ['bool', 'service'=>'resource'],
'gzclose' => ['bool', 'zp'=>'resource'],
'gzcompress' => ['string', 'data'=>'string', 'level='=>'int', 'encoding='=>'int'],
'gzdecode' => ['string', 'data'=>'string', 'max_decoded_len='=>'int'],
'gzdeflate' => ['string', 'data'=>'string', 'level='=>'int', 'encoding='=>'int'],
'gzencode' => ['string', 'data'=>'string', 'level='=>'int', 'encoding='=>'int'],
'gzeof' => ['int', 'zp'=>'resource'],
'gzfile' => ['array', 'filename'=>'string', 'use_include_path='=>'int'],
'gzgetc' => ['string', 'zp'=>'resource'],
'gzgets' => ['string', 'zp'=>'resource', 'length='=>'int'],
'gzgetss' => ['string', 'zp'=>'resource', 'length'=>'int', 'allowable_tags='=>'string'],
'gzinflate' => ['string', 'data'=>'string', 'max_decoded_len='=>'int'],
'gzopen' => ['resource', 'filename'=>'string', 'mode'=>'string', 'use_include_path='=>'int'],
'gzpassthru' => ['int', 'zp'=>'resource'],
'gzputs' => ['int', 'zp'=>'resource', 'string'=>'string', 'length='=>'int'],
'gzread' => ['string', 'zp'=>'resource', 'length'=>'int'],
'gzrewind' => ['bool', 'zp'=>'resource'],
'gzseek' => ['int', 'zp'=>'resource', 'offset'=>'int', 'whence='=>'int'],
'gztell' => ['int', 'zp'=>'resource'],
'gzuncompress' => ['string', 'data'=>'string', 'max_decoded_len='=>'int'],
'gzwrite' => ['int', 'zp'=>'resource', 'string'=>'string', 'length='=>'int'],
'HaruAnnotation::setBorderStyle' => ['bool', 'width'=>'float', 'dash_on'=>'int', 'dash_off'=>'int'],
'HaruAnnotation::setHighlightMode' => ['bool', 'mode'=>'int'],
'HaruAnnotation::setIcon' => ['bool', 'icon'=>'int'],
'HaruAnnotation::setOpened' => ['bool', 'opened'=>'bool'],
'HaruDestination::setFit' => ['bool'],
'HaruDestination::setFitB' => ['bool'],
'HaruDestination::setFitBH' => ['bool', 'top'=>'float'],
'HaruDestination::setFitBV' => ['bool', 'left'=>'float'],
'HaruDestination::setFitH' => ['bool', 'top'=>'float'],
'HaruDestination::setFitR' => ['bool', 'left'=>'float', 'bottom'=>'float', 'right'=>'float', 'top'=>'float'],
'HaruDestination::setFitV' => ['bool', 'left'=>'float'],
'HaruDestination::setXYZ' => ['bool', 'left'=>'float', 'top'=>'float', 'zoom'=>'float'],
'HaruDoc::__construct' => ['void'],
'HaruDoc::addPage' => ['object'],
'HaruDoc::addPageLabel' => ['bool', 'first_page'=>'int', 'style'=>'int', 'first_num'=>'int', 'prefix='=>'string'],
'HaruDoc::createOutline' => ['object', 'title'=>'string', 'parent_outline='=>'object', 'encoder='=>'object'],
'HaruDoc::getCurrentEncoder' => ['object'],
'HaruDoc::getCurrentPage' => ['object'],
'HaruDoc::getEncoder' => ['object', 'encoding'=>'string'],
'HaruDoc::getFont' => ['object', 'fontname'=>'string', 'encoding='=>'string'],
'HaruDoc::getInfoAttr' => ['string', 'type'=>'int'],
'HaruDoc::getPageLayout' => ['int'],
'HaruDoc::getPageMode' => ['int'],
'HaruDoc::getStreamSize' => ['int'],
'HaruDoc::insertPage' => ['object', 'page'=>'object'],
'HaruDoc::loadJPEG' => ['object', 'filename'=>'string'],
'HaruDoc::loadPNG' => ['object', 'filename'=>'string', 'deferred='=>'bool'],
'HaruDoc::loadRaw' => ['object', 'filename'=>'string', 'width'=>'int', 'height'=>'int', 'color_space'=>'int'],
'HaruDoc::loadTTC' => ['string', 'fontfile'=>'string', 'index'=>'int', 'embed='=>'bool'],
'HaruDoc::loadTTF' => ['string', 'fontfile'=>'string', 'embed='=>'bool'],
'HaruDoc::loadType1' => ['string', 'afmfile'=>'string', 'pfmfile='=>'string'],
'HaruDoc::output' => ['bool'],
'HaruDoc::readFromStream' => ['string', 'bytes'=>'int'],
'HaruDoc::resetError' => ['bool'],
'HaruDoc::resetStream' => ['bool'],
'HaruDoc::save' => ['bool', 'file'=>'string'],
'HaruDoc::saveToStream' => ['bool'],
'HaruDoc::setCompressionMode' => ['bool', 'mode'=>'int'],
'HaruDoc::setCurrentEncoder' => ['bool', 'encoding'=>'string'],
'HaruDoc::setEncryptionMode' => ['bool', 'mode'=>'int', 'key_len='=>'int'],
'HaruDoc::setInfoAttr' => ['bool', 'type'=>'int', 'info'=>'string'],
'HaruDoc::setInfoDateAttr' => ['bool', 'type'=>'int', 'year'=>'int', 'month'=>'int', 'day'=>'int', 'hour'=>'int', 'min'=>'int', 'sec'=>'int', 'ind'=>'string', 'off_hour'=>'int', 'off_min'=>'int'],
'HaruDoc::setOpenAction' => ['bool', 'destination'=>'object'],
'HaruDoc::setPageLayout' => ['bool', 'layout'=>'int'],
'HaruDoc::setPageMode' => ['bool', 'mode'=>'int'],
'HaruDoc::setPagesConfiguration' => ['bool', 'page_per_pages'=>'int'],
'HaruDoc::setPassword' => ['bool', 'owner_password'=>'string', 'user_password'=>'string'],
'HaruDoc::setPermission' => ['bool', 'permission'=>'int'],
'HaruDoc::useCNSEncodings' => ['bool'],
'HaruDoc::useCNSFonts' => ['bool'],
'HaruDoc::useCNTEncodings' => ['bool'],
'HaruDoc::useCNTFonts' => ['bool'],
'HaruDoc::useJPEncodings' => ['bool'],
'HaruDoc::useJPFonts' => ['bool'],
'HaruDoc::useKREncodings' => ['bool'],
'HaruDoc::useKRFonts' => ['bool'],
'HaruEncoder::getByteType' => ['int', 'text'=>'string', 'index'=>'int'],
'HaruEncoder::getType' => ['int'],
'HaruEncoder::getUnicode' => ['int', 'character'=>'int'],
'HaruEncoder::getWritingMode' => ['int'],
'HaruFont::getAscent' => ['int'],
'HaruFont::getCapHeight' => ['int'],
'HaruFont::getDescent' => ['int'],
'HaruFont::getEncodingName' => ['string'],
'HaruFont::getFontName' => ['string'],
'HaruFont::getTextWidth' => ['array', 'text'=>'string'],
'HaruFont::getUnicodeWidth' => ['int', 'character'=>'int'],
'HaruFont::getXHeight' => ['int'],
'HaruFont::measureText' => ['int', 'text'=>'string', 'width'=>'float', 'font_size'=>'float', 'char_space'=>'float', 'word_space'=>'float', 'word_wrap='=>'bool'],
'HaruImage::getBitsPerComponent' => ['int'],
'HaruImage::getColorSpace' => ['string'],
'HaruImage::getHeight' => ['int'],
'HaruImage::getSize' => ['array'],
'HaruImage::getWidth' => ['int'],
'HaruImage::setColorMask' => ['bool', 'rmin'=>'int', 'rmax'=>'int', 'gmin'=>'int', 'gmax'=>'int', 'bmin'=>'int', 'bmax'=>'int'],
'HaruImage::setMaskImage' => ['bool', 'mask_image'=>'object'],
'HaruOutline::setDestination' => ['bool', 'destination'=>'object'],
'HaruOutline::setOpened' => ['bool', 'opened'=>'bool'],
'HaruPage::arc' => ['bool', 'x'=>'float', 'y'=>'float', 'ray'=>'float', 'ang1'=>'float', 'ang2'=>'float'],
'HaruPage::beginText' => ['bool'],
'HaruPage::circle' => ['bool', 'x'=>'float', 'y'=>'float', 'ray'=>'float'],
'HaruPage::closePath' => ['bool'],
'HaruPage::concat' => ['bool', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'x'=>'float', 'y'=>'float'],
'HaruPage::createDestination' => ['object'],
'HaruPage::createLinkAnnotation' => ['object', 'rectangle'=>'array', 'destination'=>'object'],
'HaruPage::createTextAnnotation' => ['object', 'rectangle'=>'array', 'text'=>'string', 'encoder='=>'object'],
'HaruPage::createURLAnnotation' => ['object', 'rectangle'=>'array', 'url'=>'string'],
'HaruPage::curveTo' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'],
'HaruPage::curveTo2' => ['bool', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'],
'HaruPage::curveTo3' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x3'=>'float', 'y3'=>'float'],
'HaruPage::drawImage' => ['bool', 'image'=>'object', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'],
'HaruPage::ellipse' => ['bool', 'x'=>'float', 'y'=>'float', 'xray'=>'float', 'yray'=>'float'],
'HaruPage::endPath' => ['bool'],
'HaruPage::endText' => ['bool'],
'HaruPage::eofill' => ['bool'],
'HaruPage::eoFillStroke' => ['bool', 'close_path='=>'bool'],
'HaruPage::fill' => ['bool'],
'HaruPage::fillStroke' => ['bool', 'close_path='=>'bool'],
'HaruPage::getCharSpace' => ['float'],
'HaruPage::getCMYKFill' => ['array'],
'HaruPage::getCMYKStroke' => ['array'],
'HaruPage::getCurrentFont' => ['object'],
'HaruPage::getCurrentFontSize' => ['float'],
'HaruPage::getCurrentPos' => ['array'],
'HaruPage::getCurrentTextPos' => ['array'],
'HaruPage::getDash' => ['array'],
'HaruPage::getFillingColorSpace' => ['int'],
'HaruPage::getFlatness' => ['float'],
'HaruPage::getGMode' => ['int'],
'HaruPage::getGrayFill' => ['float'],
'HaruPage::getGrayStroke' => ['float'],
'HaruPage::getHeight' => ['float'],
'HaruPage::getHorizontalScaling' => ['float'],
'HaruPage::getLineCap' => ['int'],
'HaruPage::getLineJoin' => ['int'],
'HaruPage::getLineWidth' => ['float'],
'HaruPage::getMiterLimit' => ['float'],
'HaruPage::getRGBFill' => ['array'],
'HaruPage::getRGBStroke' => ['array'],
'HaruPage::getStrokingColorSpace' => ['int'],
'HaruPage::getTextLeading' => ['float'],
'HaruPage::getTextMatrix' => ['array'],
'HaruPage::getTextRenderingMode' => ['int'],
'HaruPage::getTextRise' => ['float'],
'HaruPage::getTextWidth' => ['float', 'text'=>'string'],
'HaruPage::getTransMatrix' => ['array'],
'HaruPage::getWidth' => ['float'],
'HaruPage::getWordSpace' => ['float'],
'HaruPage::lineTo' => ['bool', 'x'=>'float', 'y'=>'float'],
'HaruPage::measureText' => ['int', 'text'=>'string', 'width'=>'float', 'wordwrap='=>'bool'],
'HaruPage::moveTextPos' => ['bool', 'x'=>'float', 'y'=>'float', 'set_leading='=>'bool'],
'HaruPage::moveTo' => ['bool', 'x'=>'float', 'y'=>'float'],
'HaruPage::moveToNextLine' => ['bool'],
'HaruPage::rectangle' => ['bool', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'],
'HaruPage::setCharSpace' => ['bool', 'char_space'=>'float'],
'HaruPage::setCMYKFill' => ['bool', 'c'=>'float', 'm'=>'float', 'y'=>'float', 'k'=>'float'],
'HaruPage::setCMYKStroke' => ['bool', 'c'=>'float', 'm'=>'float', 'y'=>'float', 'k'=>'float'],
'HaruPage::setDash' => ['bool', 'pattern'=>'array', 'phase'=>'int'],
'HaruPage::setFlatness' => ['bool', 'flatness'=>'float'],
'HaruPage::setFontAndSize' => ['bool', 'font'=>'object', 'size'=>'float'],
'HaruPage::setGrayFill' => ['bool', 'value'=>'float'],
'HaruPage::setGrayStroke' => ['bool', 'value'=>'float'],
'HaruPage::setHeight' => ['bool', 'height'=>'float'],
'HaruPage::setHorizontalScaling' => ['bool', 'scaling'=>'float'],
'HaruPage::setLineCap' => ['bool', 'cap'=>'int'],
'HaruPage::setLineJoin' => ['bool', 'join'=>'int'],
'HaruPage::setLineWidth' => ['bool', 'width'=>'float'],
'HaruPage::setMiterLimit' => ['bool', 'limit'=>'float'],
'HaruPage::setRGBFill' => ['bool', 'r'=>'float', 'g'=>'float', 'b'=>'float'],
'HaruPage::setRGBStroke' => ['bool', 'r'=>'float', 'g'=>'float', 'b'=>'float'],
'HaruPage::setRotate' => ['bool', 'angle'=>'int'],
'HaruPage::setSize' => ['bool', 'size'=>'int', 'direction'=>'int'],
'HaruPage::setSlideShow' => ['bool', 'type'=>'int', 'disp_time'=>'float', 'trans_time'=>'float'],
'HaruPage::setTextLeading' => ['bool', 'text_leading'=>'float'],
'HaruPage::setTextMatrix' => ['bool', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'x'=>'float', 'y'=>'float'],
'HaruPage::setTextRenderingMode' => ['bool', 'mode'=>'int'],
'HaruPage::setTextRise' => ['bool', 'rise'=>'float'],
'HaruPage::setWidth' => ['bool', 'width'=>'float'],
'HaruPage::setWordSpace' => ['bool', 'word_space'=>'float'],
'HaruPage::showText' => ['bool', 'text'=>'string'],
'HaruPage::showTextNextLine' => ['bool', 'text'=>'string', 'word_space='=>'float', 'char_space='=>'float'],
'HaruPage::stroke' => ['bool', 'close_path='=>'bool'],
'HaruPage::textOut' => ['bool', 'x'=>'float', 'y'=>'float', 'text'=>'string'],
'HaruPage::textRect' => ['bool', 'left'=>'float', 'top'=>'float', 'right'=>'float', 'bottom'=>'float', 'text'=>'string', 'align='=>'int'],
'hash' => ['string', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
'hash_algos' => ['array'],
'hash_copy' => ['HashContext', 'context'=>'HashContext'],
'hash_equals' => ['bool', 'known_string'=>'string', 'user_string'=>'string'],
'hash_file' => ['string', 'algo'=>'string', 'filename'=>'string', 'raw_output='=>'bool'],
'hash_final' => ['string', 'context'=>'HashContext', 'raw_output='=>'bool'],
'hash_hkdf' => ['string', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
'hash_hmac' => ['string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_hmac_algos' => ['array<int,string>'],
'hash_hmac_file' => ['string', 'algo'=>'string', 'filename'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_init' => ['HashContext', 'algo'=>'string', 'options='=>'int', 'key='=>'string'],
'hash_pbkdf2' => ['string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
'hash_update' => ['bool', 'context'=>'HashContext', 'data'=>'string'],
'hash_update_file' => ['bool', 'context='=>'HashContext', 'filename'=>'string', 'scontext='=>'?HashContext'],
'hash_update_stream' => ['int', 'context'=>'HashContext', 'handle'=>'resource', 'length='=>'int'],
'hashTableObj::clear' => ['void'],
'hashTableObj::get' => ['string', 'key'=>'string'],
'hashTableObj::nextkey' => ['string', 'previousKey'=>'string'],
'hashTableObj::remove' => ['int', 'key'=>'string'],
'hashTableObj::set' => ['int', 'key'=>'string', 'value'=>'string'],
'header' => ['void', 'header'=>'string', 'replace='=>'bool', 'http_response_code='=>'int'],
'header_register_callback' => ['bool', 'callback'=>'callable():void'],
'header_remove' => ['void', 'name='=>'string'],
'headers_list' => ['array'],
'headers_sent' => ['bool', '&w_file='=>'string', '&w_line='=>'int'],
'hebrev' => ['string', 'str'=>'string', 'max_chars_per_line='=>'int'],
'hebrevc' => ['string', 'str'=>'string', 'max_chars_per_line='=>'int'],
'hex2bin' => ['string', 'data'=>'string'],
'hexdec' => ['int', 'hexadecimal_number'=>'string'],
'highlight_file' => ['string|bool', 'file_name'=>'string', 'return='=>'bool'],
'highlight_string' => ['string|bool', 'string'=>'string', 'return='=>'bool'],
'HRTime\PerformanceCounter::getElapsedTicks' => ['int'],
'HRTime\PerformanceCounter::getFrequency' => ['int'],
'HRTime\PerformanceCounter::getLastElapsedTicks' => ['int'],
'HRTime\PerformanceCounter::getTicks' => ['int'],
'HRTime\PerformanceCounter::getTicksSince' => ['int', 'start'=>'int'],
'HRTime\PerformanceCounter::isRunning' => ['bool'],
'HRTime\PerformanceCounter::start' => ['void'],
'HRTime\PerformanceCounter::stop' => ['void'],
'HRTime\StopWatch::getElapsedTicks' => ['int'],
'HRTime\StopWatch::getElapsedTime' => ['float', 'unit='=>'int'],
'HRTime\StopWatch::getLastElapsedTicks' => ['int'],
'HRTime\StopWatch::getLastElapsedTime' => ['float', 'unit='=>'int'],
'HRTime\StopWatch::isRunning' => ['bool'],
'HRTime\StopWatch::start' => ['void'],
'HRTime\StopWatch::stop' => ['void'],
'html_entity_decode' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string'],
'htmlentities' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string', 'double_encode='=>'bool'],
'htmlspecialchars' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string', 'double_encode='=>'bool'],
'htmlspecialchars_decode' => ['string', 'string'=>'string', 'quote_style='=>'int'],
'http\Env\Request::__construct' => ['void'],
'http\Env\Request::getCookie' => ['mixed', 'name='=>'string', 'type='=>'mixed', 'defval='=>'mixed', 'delete='=>'bool|false'],
'http\Env\Request::getFiles' => ['array'],
'http\Env\Request::getForm' => ['mixed', 'name='=>'string', 'type='=>'mixed', 'defval='=>'mixed', 'delete='=>'bool|false'],
'http\Env\Request::getQuery' => ['mixed', 'name='=>'string', 'type='=>'mixed', 'defval='=>'mixed', 'delete='=>'bool|false'],
'http\Env\Response::__construct' => ['void'],
'http\Env\Response::__invoke' => ['bool', 'data'=>'string', 'ob_flags='=>'int'],
'http\Env\Response::isCachedByETag' => ['int', 'header_name='=>'string'],
'http\Env\Response::isCachedByLastModified' => ['int', 'header_name='=>'string'],
'http\Env\Response::send' => ['bool', 'stream='=>'resource'],
'http\Env\Response::setCacheControl' => ['http\Env\Response', 'cache_control'=>'string'],
'http\Env\Response::setContentDisposition' => ['http\Env\Response', 'disposition_params'=>'array'],
'http\Env\Response::setContentEncoding' => ['http\Env\Response', 'content_encoding'=>'int'],
'http\Env\Response::setContentType' => ['http\Env\Response', 'content_type'=>'string'],
'http\Env\Response::setCookie' => ['http\Env\Response', 'cookie'=>'mixed'],
'http\Env\Response::setEnvRequest' => ['http\Env\Response', 'env_request'=>'http\Message'],
'http\Env\Response::setEtag' => ['http\Env\Response', 'etag'=>'string'],
'http\Env\Response::setLastModified' => ['http\Env\Response', 'last_modified'=>'int'],
'http\Env\Response::setThrottleRate' => ['http\Env\Response', 'chunk_size'=>'int', 'delay='=>'float|int'],
'http\QueryString::__construct' => ['void', 'querystring'=>'string'],
'http\QueryString::__toString' => ['string'],
'http\QueryString::get' => ['', 'name='=>'string', 'type='=>'mixed', 'defval='=>'mixed', 'delete='=>'bool|false'],
'http\QueryString::getArray' => ['array', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'],
'http\QueryString::getBool' => ['bool', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'],
'http\QueryString::getFloat' => ['float', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'],
'http\QueryString::getGlobalInstance' => ['http\QueryString'],
'http\QueryString::getInt' => ['int', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'],
'http\QueryString::getIterator' => ['IteratorAggregate'],
'http\QueryString::getObject' => ['', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'],
'http\QueryString::getString' => ['string', 'name'=>'string', 'defval='=>'mixed', 'delete='=>'bool|false'],
'http\QueryString::mod' => ['http\QueryString', 'params='=>'mixed'],
'http\QueryString::offsetExists' => ['bool', 'offset'=>'mixed'],
'http\QueryString::offsetGet' => ['mixed', 'offset'=>'mixed'],
'http\QueryString::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'],
'http\QueryString::offsetUnset' => ['void', 'offset'=>'mixed'],
'http\QueryString::serialize' => ['string'],
'http\QueryString::set' => ['http\QueryString', 'params'=>'mixed'],
'http\QueryString::toArray' => ['mixed[]'],
'http\QueryString::toString' => ['string'],
'http\QueryString::unserialize' => ['void', 'serialized'=>''],
'http\QueryString::xlate' => ['http\QueryString'],
'http\Url::__construct' => ['void', 'old_url='=>'mixed', 'new_url='=>'mixed', 'flags='=>'int'],
'http\Url::__toString' => [''],
'http\Url::mod' => ['http\Url', 'parts'=>'mixed', 'flags='=>'float|int|mixed'],
'http\Url::toArray' => ['string[]'],
'http\Url::toString' => ['string'],
'http_build_cookie' => ['string', 'cookie'=>'array'],
'http_build_query' => ['string', 'querydata'=>'array|object', 'prefix='=>'?string', 'arg_separator='=>'string', 'enc_type='=>'int'],
'http_build_str' => ['string', 'query'=>'array', 'prefix='=>'?string', 'arg_separator='=>'string'],
'http_build_url' => ['string', 'url='=>'string|array', 'parts='=>'string|array', 'flags='=>'int', 'new_url='=>'array'],
'http_cache_etag' => ['bool', 'etag='=>'string'],
'http_cache_last_modified' => ['bool', 'timestamp_or_expires='=>'int'],
'http_chunked_decode' => ['string', 'encoded'=>'string'],
'http_date' => ['string', 'timestamp='=>'int'],
'http_deflate' => ['string', 'data'=>'string', 'flags='=>'int'],
'http_get' => ['string', 'url'=>'string', 'options='=>'array', 'info='=>'array'],
'http_get_request_body' => ['string'],
'http_get_request_body_stream' => ['resource'],
'http_get_request_headers' => ['array'],
'http_head' => ['string', 'url'=>'string', 'options='=>'array', 'info='=>'array'],
'http_inflate' => ['string', 'data'=>'string'],
'http_match_etag' => ['bool', 'etag'=>'string', 'for_range='=>'bool'],
'http_match_modified' => ['bool', 'timestamp='=>'int', 'for_range='=>'bool'],
'http_match_request_header' => ['bool', 'header'=>'string', 'value'=>'string', 'match_case='=>'bool'],
'http_negotiate_charset' => ['string', 'supported'=>'array', 'result='=>'array'],
'http_negotiate_content_type' => ['string', 'supported'=>'array', 'result='=>'array'],
'http_negotiate_language' => ['string', 'supported'=>'array', 'result='=>'array'],
'http_parse_cookie' => ['object', 'cookie'=>'string', 'flags='=>'int', 'allowed_extras='=>'array'],
'http_parse_headers' => ['array', 'header'=>'string'],
'http_parse_message' => ['object', 'message'=>'string'],
'http_parse_params' => ['object', 'param'=>'string', 'flags='=>'int'],
'http_persistent_handles_clean' => ['string', 'ident='=>'string'],
'http_persistent_handles_count' => ['object'],
'http_persistent_handles_ident' => ['string', 'ident='=>'string'],
'http_post_data' => ['string', 'url'=>'string', 'data'=>'string', 'options='=>'array', 'info='=>'array'],
'http_post_fields' => ['string', 'url'=>'string', 'data'=>'array', 'files='=>'array', 'options='=>'array', 'info='=>'array'],
'http_put_data' => ['string', 'url'=>'string', 'data'=>'string', 'options='=>'array', 'info='=>'array'],
'http_put_file' => ['string', 'url'=>'string', 'file'=>'string', 'options='=>'array', 'info='=>'array'],
'http_put_stream' => ['string', 'url'=>'string', 'stream'=>'', 'options='=>'array', 'info='=>'array'],
'http_redirect' => ['bool', 'url='=>'string', 'params='=>'array', 'session='=>'bool', 'status='=>'int'],
'http_request' => ['string', 'method'=>'int', 'url'=>'string', 'body='=>'string', 'options='=>'array', 'info='=>'array'],
'http_request_body_encode' => ['string', 'fields'=>'array', 'files'=>'array'],
'http_request_method_exists' => ['int', 'method'=>''],
'http_request_method_name' => ['string', 'method'=>'int'],
'http_request_method_register' => ['int', 'method'=>'string'],
'http_request_method_unregister' => ['bool', 'method'=>''],
'http_response_code' => ['int|bool', 'response_code='=>'int'],
'http_send_content_disposition' => ['bool', 'filename'=>'string', 'inline='=>'bool'],
'http_send_content_type' => ['bool', 'content_type='=>'string'],
'http_send_data' => ['bool', 'data'=>'string'],
'http_send_file' => ['bool', 'file'=>'string'],
'http_send_last_modified' => ['bool', 'timestamp='=>'int'],
'http_send_status' => ['bool', 'status'=>'int'],
'http_send_stream' => ['bool', 'stream'=>''],
'http_support' => ['int', 'feature='=>'int'],
'http_throttle' => ['', 'sec'=>'float', 'bytes='=>'int'],
'HttpDeflateStream::__construct' => ['void', 'flags='=>'int'],
'HttpDeflateStream::factory' => ['HttpDeflateStream', 'flags='=>'int', 'class_name='=>'string'],
'HttpDeflateStream::finish' => ['string', 'data='=>'string'],
'HttpDeflateStream::flush' => ['string', 'data='=>'string'],
'HttpDeflateStream::update' => ['string', 'data'=>'string'],
'HttpInflateStream::__construct' => ['void', 'flags='=>'int'],
'HttpInflateStream::factory' => ['HttpInflateStream', 'flags='=>'int', 'class_name='=>'string'],
'HttpInflateStream::finish' => ['string', 'data='=>'string'],
'HttpInflateStream::flush' => ['string', 'data='=>'string'],
'HttpInflateStream::update' => ['string', 'data'=>'string'],
'HttpMessage::__construct' => ['void', 'message='=>'string'],
'HttpMessage::__toString' => ['string'],
'HttpMessage::addHeaders' => ['', 'headers'=>'array', 'append='=>'bool'],
'HttpMessage::count' => ['int'],
'HttpMessage::current' => ['mixed'],
'HttpMessage::detach' => ['HttpMessage'],
'HttpMessage::factory' => ['HttpMessage', 'raw_message='=>'string', 'class_name='=>'string'],
'HttpMessage::fromEnv' => ['HttpMessage', 'message_type'=>'int', 'class_name='=>'string'],
'HttpMessage::fromString' => ['HttpMessage', 'raw_message='=>'string', 'class_name='=>'string'],
'HttpMessage::getBody' => ['string'],
'HttpMessage::getHeader' => ['string', 'header'=>'string'],
'HttpMessage::getHeaders' => ['array'],
'HttpMessage::getHttpVersion' => ['string'],
'HttpMessage::getInfo' => [''],
'HttpMessage::getParentMessage' => ['HttpMessage'],
'HttpMessage::getRequestMethod' => ['string'],
'HttpMessage::getRequestUrl' => ['string'],
'HttpMessage::getResponseCode' => ['int'],
'HttpMessage::getResponseStatus' => ['string'],
'HttpMessage::getType' => ['int'],
'HttpMessage::guessContentType' => ['string', 'magic_file'=>'string', 'magic_mode='=>'int'],
'HttpMessage::key' => ['int|string'],
'HttpMessage::next' => ['void'],
'HttpMessage::prepend' => ['', 'message'=>'httpmessage', 'top='=>'bool'],
'HttpMessage::reverse' => ['HttpMessage'],
'HttpMessage::rewind' => ['void'],
'HttpMessage::send' => ['bool'],
'HttpMessage::serialize' => ['string'],
'HttpMessage::setBody' => ['', 'body'=>'string'],
'HttpMessage::setHeaders' => ['', 'headers'=>'array'],
'HttpMessage::setHttpVersion' => ['bool', 'version'=>'string'],
'HttpMessage::setInfo' => ['', 'http_info'=>''],
'HttpMessage::setRequestMethod' => ['bool', 'method'=>'string'],
'HttpMessage::setRequestUrl' => ['bool', 'url'=>'string'],
'HttpMessage::setResponseCode' => ['bool', 'code'=>'int'],
'HttpMessage::setResponseStatus' => ['bool', 'status'=>'string'],
'HttpMessage::setType' => ['', 'type'=>'int'],
'HttpMessage::toMessageTypeObject' => ['HttpRequest|HttpResponse'],
'HttpMessage::toString' => ['string', 'include_parent='=>'bool'],
'HttpMessage::unserialize' => ['void', 'serialized'=>''],
'HttpMessage::valid' => ['bool'],
'HttpQueryString::__construct' => ['void', 'global='=>'bool', 'add='=>''],
'HttpQueryString::__toString' => ['string'],
'HttpQueryString::factory' => ['', 'global'=>'', 'params'=>'', 'class_name'=>''],
'HttpQueryString::get' => ['', 'key='=>'string', 'type='=>'', 'defval='=>'', 'delete='=>'bool'],
'HttpQueryString::getArray' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''],
'HttpQueryString::getBool' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''],
'HttpQueryString::getFloat' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''],
'HttpQueryString::getInt' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''],
'HttpQueryString::getObject' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''],
'HttpQueryString::getString' => ['', 'name'=>'', 'defval'=>'', 'delete'=>''],
'HttpQueryString::mod' => ['HttpQueryString', 'params'=>''],
'HttpQueryString::offsetExists' => ['bool', 'offset'=>'mixed'],
'HttpQueryString::offsetGet' => ['mixed', 'offset'=>'mixed'],
'HttpQueryString::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'],
'HttpQueryString::offsetUnset' => ['void', 'offset'=>'mixed'],
'HttpQueryString::serialize' => ['string'],
'HttpQueryString::set' => ['string', 'params'=>''],
'HttpQueryString::singleton' => ['HttpQueryString', 'global='=>'bool'],
'HttpQueryString::toArray' => ['array'],
'HttpQueryString::toString' => ['string'],
'HttpQueryString::unserialize' => ['void', 'serialized'=>'string'],
'HttpQueryString::xlate' => ['bool', 'ie'=>'string', 'oe'=>'string'],
'HttpRequest::__construct' => ['void', 'url='=>'string', 'request_method='=>'int', 'options='=>'array'],
'HttpRequest::addBody' => ['', 'request_body_data'=>''],
'HttpRequest::addCookies' => ['bool', 'cookies'=>'array'],
'HttpRequest::addHeaders' => ['bool', 'headers'=>'array'],
'HttpRequest::addPostFields' => ['bool', 'post_data'=>'array'],
'HttpRequest::addPostFile' => ['bool', 'name'=>'string', 'file'=>'string', 'content_type='=>'string'],
'HttpRequest::addPutData' => ['bool', 'put_data'=>'string'],
'HttpRequest::addQueryData' => ['bool', 'query_params'=>'array'],
'HttpRequest::addRawPostData' => ['bool', 'raw_post_data'=>'string'],
'HttpRequest::addSslOptions' => ['bool', 'options'=>'array'],
'HttpRequest::clearHistory' => [''],
'HttpRequest::enableCookies' => ['bool'],
'HttpRequest::encodeBody' => ['', 'fields'=>'', 'files'=>''],
'HttpRequest::factory' => ['', 'url'=>'', 'method'=>'', 'options'=>'', 'class_name'=>''],
'HttpRequest::flushCookies' => [''],
'HttpRequest::get' => ['', 'url'=>'', 'options'=>'', '&info'=>''],
'HttpRequest::getBody' => [''],
'HttpRequest::getContentType' => ['string'],
'HttpRequest::getCookies' => ['array'],
'HttpRequest::getHeaders' => ['array'],
'HttpRequest::getHistory' => ['HttpMessage'],
'HttpRequest::getMethod' => ['int'],
'HttpRequest::getOptions' => ['array'],
'HttpRequest::getPostFields' => ['array'],
'HttpRequest::getPostFiles' => ['array'],
'HttpRequest::getPutData' => ['string'],
'HttpRequest::getPutFile' => ['string'],
'HttpRequest::getQueryData' => ['string'],
'HttpRequest::getRawPostData' => ['string'],
'HttpRequest::getRawRequestMessage' => ['string'],
'HttpRequest::getRawResponseMessage' => ['string'],
'HttpRequest::getRequestMessage' => ['HttpMessage'],
'HttpRequest::getResponseBody' => ['string'],
'HttpRequest::getResponseCode' => ['int'],
'HttpRequest::getResponseCookies' => ['array', 'flags='=>'int', 'allowed_extras='=>'array'],
'HttpRequest::getResponseData' => ['array'],
'HttpRequest::getResponseHeader' => ['', 'name='=>'string'],
'HttpRequest::getResponseInfo' => ['', 'name='=>'string'],
'HttpRequest::getResponseMessage' => ['HttpMessage'],
'HttpRequest::getResponseStatus' => ['string'],
'HttpRequest::getSslOptions' => ['array'],
'HttpRequest::getUrl' => ['string'],
'HttpRequest::head' => ['', 'url'=>'', 'options'=>'', '&info'=>''],
'HttpRequest::methodExists' => ['', 'method'=>''],
'HttpRequest::methodName' => ['', 'method_id'=>''],
'HttpRequest::methodRegister' => ['', 'method_name'=>''],
'HttpRequest::methodUnregister' => ['', 'method'=>''],
'HttpRequest::postData' => ['', 'url'=>'', 'data'=>'', 'options'=>'', '&info'=>''],
'HttpRequest::postFields' => ['', 'url'=>'', 'data'=>'', 'options'=>'', '&info'=>''],
'HttpRequest::putData' => ['', 'url'=>'', 'data'=>'', 'options'=>'', '&info'=>''],
'HttpRequest::putFile' => ['', 'url'=>'', 'file'=>'', 'options'=>'', '&info'=>''],
'HttpRequest::putStream' => ['', 'url'=>'', 'stream'=>'', 'options'=>'', '&info'=>''],
'HttpRequest::resetCookies' => ['bool', 'session_only='=>'bool'],
'HttpRequest::send' => ['HttpMessage'],
'HttpRequest::setBody' => ['bool', 'request_body_data='=>'string'],
'HttpRequest::setContentType' => ['bool', 'content_type'=>'string'],
'HttpRequest::setCookies' => ['bool', 'cookies='=>'array'],
'HttpRequest::setHeaders' => ['bool', 'headers='=>'array'],
'HttpRequest::setMethod' => ['bool', 'request_method'=>'int'],
'HttpRequest::setOptions' => ['bool', 'options='=>'array'],
'HttpRequest::setPostFields' => ['bool', 'post_data'=>'array'],
'HttpRequest::setPostFiles' => ['bool', 'post_files'=>'array'],
'HttpRequest::setPutData' => ['bool', 'put_data='=>'string'],
'HttpRequest::setPutFile' => ['bool', 'file='=>'string'],
'HttpRequest::setQueryData' => ['bool', 'query_data'=>''],
'HttpRequest::setRawPostData' => ['bool', 'raw_post_data='=>'string'],
'HttpRequest::setSslOptions' => ['bool', 'options='=>'array'],
'HttpRequest::setUrl' => ['bool', 'url'=>'string'],
'HttpRequestDataShare::__construct' => ['void'],
'HttpRequestDataShare::__destruct' => [''],
'HttpRequestDataShare::attach' => ['', 'request'=>'HttpRequest'],
'HttpRequestDataShare::count' => ['int'],
'HttpRequestDataShare::detach' => ['', 'request'=>'HttpRequest'],
'HttpRequestDataShare::factory' => ['', 'global'=>'', 'class_name'=>''],
'HttpRequestDataShare::reset' => [''],
'HttpRequestDataShare::singleton' => ['', 'global'=>''],
'HttpRequestPool::__construct' => ['void', 'request='=>'httprequest'],
'HttpRequestPool::__destruct' => [''],
'HttpRequestPool::attach' => ['bool', 'request'=>'httprequest'],
'HttpRequestPool::count' => ['int'],
'HttpRequestPool::current' => ['mixed'],
'HttpRequestPool::detach' => ['bool', 'request'=>'httprequest'],
'HttpRequestPool::enableEvents' => ['', 'enable'=>''],
'HttpRequestPool::enablePipelining' => ['', 'enable'=>''],
'HttpRequestPool::getAttachedRequests' => ['array'],
'HttpRequestPool::getFinishedRequests' => ['array'],
'HttpRequestPool::key' => ['int|string'],
'HttpRequestPool::next' => ['void'],
'HttpRequestPool::reset' => [''],
'HttpRequestPool::rewind' => ['void'],
'HttpRequestPool::send' => ['bool'],
'HttpRequestPool::socketPerform' => ['bool'],
'HttpRequestPool::socketSelect' => ['bool', 'timeout='=>'float'],
'HttpRequestPool::valid' => ['bool'],
'HttpResponse::capture' => [''],
'HttpResponse::getBufferSize' => ['int'],
'HttpResponse::getCache' => ['bool'],
'HttpResponse::getCacheControl' => ['string'],
'HttpResponse::getContentDisposition' => ['string'],
'HttpResponse::getContentType' => ['string'],
'HttpResponse::getData' => ['string'],
'HttpResponse::getETag' => ['string'],
'HttpResponse::getFile' => ['string'],
'HttpResponse::getGzip' => ['bool'],
'HttpResponse::getHeader' => ['', 'name='=>'string'],
'HttpResponse::getLastModified' => ['int'],
'HttpResponse::getRequestBody' => ['string'],
'HttpResponse::getRequestBodyStream' => ['resource'],
'HttpResponse::getRequestHeaders' => ['array'],
'HttpResponse::getStream' => ['resource'],
'HttpResponse::getThrottleDelay' => ['float'],
'HttpResponse::guessContentType' => ['string', 'magic_file'=>'string', 'magic_mode='=>'int'],
'HttpResponse::redirect' => ['', 'url='=>'string', 'params='=>'array', 'session='=>'bool', 'status='=>'int'],
'HttpResponse::send' => ['bool', 'clean_ob='=>'bool'],
'HttpResponse::setBufferSize' => ['bool', 'bytes'=>'int'],
'HttpResponse::setCache' => ['bool', 'cache'=>'bool'],
'HttpResponse::setCacheControl' => ['bool', 'control'=>'string', 'max_age='=>'int', 'must_revalidate='=>'bool'],
'HttpResponse::setContentDisposition' => ['bool', 'filename'=>'string', 'inline='=>'bool'],
'HttpResponse::setContentType' => ['bool', 'content_type'=>'string'],
'HttpResponse::setData' => ['bool', 'data'=>''],
'HttpResponse::setETag' => ['bool', 'etag'=>'string'],
'HttpResponse::setFile' => ['bool', 'file'=>'string'],
'HttpResponse::setGzip' => ['bool', 'gzip'=>'bool'],
'HttpResponse::setHeader' => ['bool', 'name'=>'string', 'value='=>'', 'replace='=>'bool'],
'HttpResponse::setLastModified' => ['bool', 'timestamp'=>'int'],
'HttpResponse::setStream' => ['bool', 'stream'=>''],
'HttpResponse::setThrottleDelay' => ['bool', 'seconds'=>'float'],
'HttpResponse::status' => ['bool', 'status'=>'int'],
'HttpUtil::buildCookie' => ['', 'cookie_array'=>''],
'HttpUtil::buildStr' => ['', 'query'=>'', 'prefix'=>'', 'arg_sep'=>''],
'HttpUtil::buildUrl' => ['', 'url'=>'', 'parts'=>'', 'flags'=>'', '&composed'=>''],
'HttpUtil::chunkedDecode' => ['', 'encoded_string'=>''],
'HttpUtil::date' => ['', 'timestamp'=>''],
'HttpUtil::deflate' => ['', 'plain'=>'', 'flags'=>''],
'HttpUtil::inflate' => ['', 'encoded'=>''],
'HttpUtil::matchEtag' => ['', 'plain_etag'=>'', 'for_range'=>''],
'HttpUtil::matchModified' => ['', 'last_modified'=>'', 'for_range'=>''],
'HttpUtil::matchRequestHeader' => ['', 'header_name'=>'', 'header_value'=>'', 'case_sensitive'=>''],
'HttpUtil::negotiateCharset' => ['', 'supported'=>'', '&result'=>''],
'HttpUtil::negotiateContentType' => ['', 'supported'=>'', '&result'=>''],
'HttpUtil::negotiateLanguage' => ['', 'supported'=>'', '&result'=>''],
'HttpUtil::parseCookie' => ['', 'cookie_string'=>''],
'HttpUtil::parseHeaders' => ['', 'headers_string'=>''],
'HttpUtil::parseMessage' => ['', 'message_string'=>''],
'HttpUtil::parseParams' => ['', 'param_string'=>'', 'flags'=>''],
'HttpUtil::support' => ['', 'feature'=>''],
'hw_api::checkin' => ['bool', 'parameter'=>'array'],
'hw_api::checkout' => ['bool', 'parameter'=>'array'],
'hw_api::children' => ['array', 'parameter'=>'array'],
'hw_api::content' => ['HW_API_Content', 'parameter'=>'array'],
'hw_api::copy' => ['hw_api_content', 'parameter'=>'array'],
'hw_api::dbstat' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::dcstat' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::dstanchors' => ['array', 'parameter'=>'array'],
'hw_api::dstofsrcanchor' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::find' => ['array', 'parameter'=>'array'],
'hw_api::ftstat' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::hwstat' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::identify' => ['bool', 'parameter'=>'array'],
'hw_api::info' => ['array', 'parameter'=>'array'],
'hw_api::insert' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::insertanchor' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::insertcollection' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::insertdocument' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::link' => ['bool', 'parameter'=>'array'],
'hw_api::lock' => ['bool', 'parameter'=>'array'],
'hw_api::move' => ['bool', 'parameter'=>'array'],
'hw_api::object' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::objectbyanchor' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::parents' => ['array', 'parameter'=>'array'],
'hw_api::remove' => ['bool', 'parameter'=>'array'],
'hw_api::replace' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::setcommittedversion' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::srcanchors' => ['array', 'parameter'=>'array'],
'hw_api::srcsofdst' => ['array', 'parameter'=>'array'],
'hw_api::unlock' => ['bool', 'parameter'=>'array'],
'hw_api::user' => ['hw_api_object', 'parameter'=>'array'],
'hw_api::userlist' => ['array', 'parameter'=>'array'],
'hw_api_attribute' => ['HW_API_Attribute', 'name='=>'string', 'value='=>'string'],
'hw_api_attribute::key' => ['string'],
'hw_api_attribute::langdepvalue' => ['string', 'language'=>'string'],
'hw_api_attribute::value' => ['string'],
'hw_api_attribute::values' => ['array'],
'hw_api_content' => ['HW_API_Content', 'content'=>'string', 'mimetype'=>'string'],
'hw_api_content::mimetype' => ['string'],
'hw_api_content::read' => ['string', 'buffer'=>'string', 'len'=>'int'],
'hw_api_error::count' => ['int'],
'hw_api_error::reason' => ['HW_API_Reason'],
'hw_api_object' => ['hw_api_object', 'parameter'=>'array'],
'hw_api_object::assign' => ['bool', 'parameter'=>'array'],
'hw_api_object::attreditable' => ['bool', 'parameter'=>'array'],
'hw_api_object::count' => ['int', 'parameter'=>'array'],
'hw_api_object::insert' => ['bool', 'attribute'=>'hw_api_attribute'],
'hw_api_object::remove' => ['bool', 'name'=>'string'],
'hw_api_object::title' => ['string', 'parameter'=>'array'],
'hw_api_object::value' => ['string', 'name'=>'string'],
'hw_api_reason::description' => ['string'],
'hw_api_reason::type' => ['HW_API_Reason'],
'hw_Array2Objrec' => ['string', 'object_array'=>'array'],
'hw_changeobject' => ['bool', 'link'=>'int', 'objid'=>'int', 'attributes'=>'array'],
'hw_Children' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_ChildrenObj' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_Close' => ['bool', 'connection'=>'int'],
'hw_Connect' => ['int', 'host'=>'string', 'port'=>'int', 'username='=>'string', 'password='=>'string'],
'hw_connection_info' => ['', 'link'=>'int'],
'hw_cp' => ['int', 'connection'=>'int', 'object_id_array'=>'array', 'destination_id'=>'int'],
'hw_Deleteobject' => ['bool', 'connection'=>'int', 'object_to_delete'=>'int'],
'hw_DocByAnchor' => ['int', 'connection'=>'int', 'anchorid'=>'int'],
'hw_DocByAnchorObj' => ['string', 'connection'=>'int', 'anchorid'=>'int'],
'hw_Document_Attributes' => ['string', 'hw_document'=>'int'],
'hw_Document_BodyTag' => ['string', 'hw_document'=>'int', 'prefix='=>'string'],
'hw_Document_Content' => ['string', 'hw_document'=>'int'],
'hw_Document_SetContent' => ['bool', 'hw_document'=>'int', 'content'=>'string'],
'hw_Document_Size' => ['int', 'hw_document'=>'int'],
'hw_dummy' => ['string', 'link'=>'int', 'id'=>'int', 'msgid'=>'int'],
'hw_EditText' => ['bool', 'connection'=>'int', 'hw_document'=>'int'],
'hw_Error' => ['int', 'connection'=>'int'],
'hw_ErrorMsg' => ['string', 'connection'=>'int'],
'hw_Free_Document' => ['bool', 'hw_document'=>'int'],
'hw_GetAnchors' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_GetAnchorsObj' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_GetAndLock' => ['string', 'connection'=>'int', 'objectid'=>'int'],
'hw_GetChildColl' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_GetChildCollObj' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_GetChildDocColl' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_GetChildDocCollObj' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_GetObject' => ['', 'connection'=>'int', 'objectid'=>'', 'query='=>'string'],
'hw_GetObjectByQuery' => ['array', 'connection'=>'int', 'query'=>'string', 'max_hits'=>'int'],
'hw_GetObjectByQueryColl' => ['array', 'connection'=>'int', 'objectid'=>'int', 'query'=>'string', 'max_hits'=>'int'],
'hw_GetObjectByQueryCollObj' => ['array', 'connection'=>'int', 'objectid'=>'int', 'query'=>'string', 'max_hits'=>'int'],
'hw_GetObjectByQueryObj' => ['array', 'connection'=>'int', 'query'=>'string', 'max_hits'=>'int'],
'hw_GetParents' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_GetParentsObj' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_getrellink' => ['string', 'link'=>'int', 'rootid'=>'int', 'sourceid'=>'int', 'destid'=>'int'],
'hw_GetRemote' => ['int', 'connection'=>'int', 'objectid'=>'int'],
'hw_getremotechildren' => ['', 'connection'=>'int', 'object_record'=>'string'],
'hw_GetSrcByDestObj' => ['array', 'connection'=>'int', 'objectid'=>'int'],
'hw_GetText' => ['int', 'connection'=>'int', 'objectid'=>'int', 'rootid/prefix='=>''],
'hw_getusername' => ['string', 'connection'=>'int'],
'hw_Identify' => ['string', 'link'=>'int', 'username'=>'string', 'password'=>'string'],
'hw_InCollections' => ['array', 'connection'=>'int', 'object_id_array'=>'array', 'collection_id_array'=>'array', 'return_collections'=>'int'],
'hw_Info' => ['string', 'connection'=>'int'],
'hw_InsColl' => ['int', 'connection'=>'int', 'objectid'=>'int', 'object_array'=>'array'],
'hw_InsDoc' => ['int', 'connection'=>'', 'parentid'=>'int', 'object_record'=>'string', 'text='=>'string'],
'hw_insertanchors' => ['bool', 'hwdoc'=>'int', 'anchorecs'=>'array', 'dest'=>'array', 'urlprefixes='=>'array'],
'hw_InsertDocument' => ['int', 'connection'=>'int', 'parent_id'=>'int', 'hw_document'=>'int'],
'hw_InsertObject' => ['int', 'connection'=>'int', 'object_rec'=>'string', 'parameter'=>'string'],
'hw_mapid' => ['int', 'connection'=>'int', 'server_id'=>'int', 'object_id'=>'int'],
'hw_Modifyobject' => ['bool', 'connection'=>'int', 'object_to_change'=>'int', 'remove'=>'array', 'add'=>'array', 'mode='=>'int'],
'hw_mv' => ['int', 'connection'=>'int', 'object_id_array'=>'array', 'source_id'=>'int', 'destination_id'=>'int'],
'hw_New_Document' => ['int', 'object_record'=>'string', 'document_data'=>'string', 'document_size'=>'int'],
'hw_objrec2array' => ['array', 'object_record'=>'string', 'format='=>'array'],
'hw_Output_Document' => ['bool', 'hw_document'=>'int'],
'hw_pConnect' => ['int', 'host'=>'string', 'port'=>'int', 'username='=>'string', 'password='=>'string'],
'hw_PipeDocument' => ['int', 'connection'=>'int', 'objectid'=>'int', 'url_prefixes='=>'array'],
'hw_Root' => ['int'],
'hw_setlinkroot' => ['int', 'link'=>'int', 'rootid'=>'int'],
'hw_stat' => ['string', 'link'=>'int'],
'hw_Unlock' => ['bool', 'connection'=>'int', 'objectid'=>'int'],
'hw_Who' => ['array', 'connection'=>'int'],
'hwapi_attribute_new' => ['HW_API_Attribute', 'name='=>'string', 'value='=>'string'],
'hwapi_content_new' => ['HW_API_Content', 'content'=>'string', 'mimetype'=>'string'],
'hwapi_hgcsp' => ['HW_API', 'hostname'=>'string', 'port='=>'int'],
'hwapi_object_new' => ['hw_api_object', 'parameter'=>'array'],
'hypot' => ['float', 'num1'=>'float', 'num2'=>'float'],
'ibase_add_user' => ['bool', 'service_handle'=>'resource', 'user_name'=>'string', 'password'=>'string', 'first_name='=>'string', 'middle_name='=>'string', 'last_name='=>'string'],
'ibase_affected_rows' => ['int', 'link_identifier='=>'resource'],
'ibase_backup' => ['mixed', 'service_handle'=>'resource', 'source_db'=>'string', 'dest_file'=>'string', 'options='=>'int', 'verbose='=>'bool'],
'ibase_blob_add' => ['bool', 'blob_handle'=>'resource', 'data'=>'string'],
'ibase_blob_cancel' => ['bool', 'blob_handle'=>'resource'],
'ibase_blob_close' => ['string', 'blob_handle'=>'resource'],
'ibase_blob_create' => ['resource', 'link_identifier='=>'resource'],
'ibase_blob_echo' => ['bool', 'link_identifier'=>'', 'blob_id'=>'string'],
'ibase_blob_echo\'1' => ['bool', 'blob_id'=>'string'],
'ibase_blob_get' => ['string', 'blob_handle'=>'resource', 'len'=>'int'],
'ibase_blob_import' => ['string', 'link_identifier'=>'', 'file_handle'=>''],
'ibase_blob_info' => ['array', 'link_identifier'=>'', 'blob_id'=>'string'],
'ibase_blob_info\'1' => ['array', 'blob_id'=>'string'],
'ibase_blob_open' => ['resource', 'link_identifier'=>'', 'blob_id'=>'string'],
'ibase_blob_open\'1' => ['resource', 'blob_id'=>'string'],
'ibase_close' => ['bool', 'link_identifier='=>'resource'],
'ibase_commit' => ['bool', 'link_identifier='=>'resource'],
'ibase_commit_ret' => ['bool', 'link_identifier='=>'resource'],
'ibase_connect' => ['resource', 'database='=>'string', 'username='=>'string', 'password='=>'string', 'charset='=>'string', 'buffers='=>'int', 'dialect='=>'int', 'role='=>'string'],
'ibase_db_info' => ['string', 'service_handle'=>'resource', 'db'=>'string', 'action'=>'int', 'argument='=>'int'],
'ibase_delete_user' => ['bool', 'service_handle'=>'resource', 'user_name'=>'string', 'password='=>'string', 'first_name='=>'string', 'middle_name='=>'string', 'last_name='=>'string'],
'ibase_drop_db' => ['bool', 'link_identifier='=>'resource'],
'ibase_errcode' => ['int'],
'ibase_errmsg' => ['string'],
'ibase_execute' => ['resource', 'query'=>'resource', 'bind_arg='=>'mixed', '...args='=>'mixed'],
'ibase_fetch_assoc' => ['array', 'result'=>'resource', 'fetch_flags='=>'int'],
'ibase_fetch_object' => ['object', 'result'=>'resource', 'fetch_flags='=>'int'],
'ibase_fetch_row' => ['array', 'result'=>'resource', 'fetch_flags='=>'int'],
'ibase_field_info' => ['array', 'query_result'=>'resource', 'field_number'=>'int'],
'ibase_free_event_handler' => ['bool', 'event'=>'resource'],
'ibase_free_query' => ['bool', 'query'=>'resource'],
'ibase_free_result' => ['bool', 'result'=>'resource'],
'ibase_gen_id' => ['int', 'generator'=>'string', 'increment='=>'int', 'link_identifier='=>'resource'],
'ibase_maintain_db' => ['bool', 'service_handle'=>'resource', 'db'=>'string', 'action'=>'int', 'argument='=>'int'],
'ibase_modify_user' => ['bool', 'service_handle'=>'resource', 'user_name'=>'string', 'password'=>'string', 'first_name='=>'string', 'middle_name='=>'string', 'last_name='=>'string'],
'ibase_name_result' => ['bool', 'result'=>'resource', 'name'=>'string'],
'ibase_num_fields' => ['int', 'query_result'=>'resource'],
'ibase_num_params' => ['int', 'query'=>'resource'],
'ibase_num_rows' => ['int', 'result_identifier'=>''],
'ibase_param_info' => ['array', 'query'=>'resource', 'field_number'=>'int'],
'ibase_pconnect' => ['resource', 'database='=>'string', 'username='=>'string', 'password='=>'string', 'charset='=>'string', 'buffers='=>'int', 'dialect='=>'int', 'role='=>'string'],
'ibase_prepare' => ['resource', 'link_identifier'=>'', 'query'=>'string', 'trans_identifier'=>''],
'ibase_query' => ['resource', 'link_identifier='=>'resource', 'string='=>'string', 'bind_arg='=>'int', '...args='=>''],
'ibase_restore' => ['mixed', 'service_handle'=>'resource', 'source_file'=>'string', 'dest_db'=>'string', 'options='=>'int', 'verbose='=>'bool'],
'ibase_rollback' => ['bool', 'link_identifier='=>'resource'],
'ibase_rollback_ret' => ['bool', 'link_identifier='=>'resource'],
'ibase_server_info' => ['string', 'service_handle'=>'resource', 'action'=>'int'],
'ibase_service_attach' => ['resource', 'host'=>'string', 'dba_username'=>'string', 'dba_password'=>'string'],
'ibase_service_detach' => ['bool', 'service_handle'=>'resource'],
'ibase_set_event_handler' => ['resource', 'link_identifier'=>'', 'callback'=>'callable', 'event='=>'string', '...args='=>''],
'ibase_set_event_handler\'1' => ['resource', 'callback'=>'callable', 'event'=>'string', '...args'=>''],
'ibase_timefmt' => ['bool', 'format'=>'string', 'columntype='=>'int'],
'ibase_trans' => ['resource', 'trans_args='=>'int', 'link_identifier='=>'', '...args='=>''],
'ibase_wait_event' => ['string', 'link_identifier'=>'', 'event='=>'string', '...args='=>''],
'ibase_wait_event\'1' => ['string', 'event'=>'string', '...args'=>''],
'iconv' => ['string', 'in_charset'=>'string', 'out_charset'=>'string', 'str'=>'string'],
'iconv_get_encoding' => ['mixed', 'type='=>'string'],
'iconv_mime_decode' => ['string', 'encoded_string'=>'string', 'mode='=>'int', 'charset='=>'string'],
'iconv_mime_decode_headers' => ['array', 'headers'=>'string', 'mode='=>'int', 'charset='=>'string'],
'iconv_mime_encode' => ['string', 'field_name'=>'string', 'field_value'=>'string', 'preference='=>'array'],
'iconv_set_encoding' => ['bool', 'type'=>'string', 'charset'=>'string'],
'iconv_strlen' => ['int', 'str'=>'string', 'charset='=>'string'],
'iconv_strpos' => ['int', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'charset='=>'string'],
'iconv_strrpos' => ['int', 'haystack'=>'string', 'needle'=>'string', 'charset='=>'string'],
'iconv_substr' => ['string', 'str'=>'string', 'offset'=>'int', 'length='=>'int', 'charset='=>'string'],
'id3_get_frame_long_name' => ['string', 'frameid'=>'string'],
'id3_get_frame_short_name' => ['string', 'frameid'=>'string'],
'id3_get_genre_id' => ['int', 'genre'=>'string'],
'id3_get_genre_list' => ['array'],
'id3_get_genre_name' => ['string', 'genre_id'=>'int'],
'id3_get_tag' => ['array', 'filename'=>'string', 'version='=>'int'],
'id3_get_version' => ['int', 'filename'=>'string'],
'id3_remove_tag' => ['bool', 'filename'=>'string', 'version='=>'int'],
'id3_set_tag' => ['bool', 'filename'=>'string', 'tag'=>'array', 'version='=>'int'],
'idate' => ['int', 'format'=>'string', 'timestamp='=>'int'],
'idn_strerror' => ['string', 'errorcode'=>'int'],
'idn_to_ascii' => ['string|false', 'domain'=>'string', 'options='=>'int', 'variant='=>'int', '&w_idna_info='=>'array'],
'idn_to_utf8' => ['string|false', 'domain'=>'string', 'options='=>'int', 'variant='=>'int', '&w_idna_info='=>'array'],
'ifx_affected_rows' => ['int', 'result_id'=>'resource'],
'ifx_blobinfile_mode' => ['bool', 'mode'=>'int'],
'ifx_byteasvarchar' => ['bool', 'mode'=>'int'],
'ifx_close' => ['bool', 'link_identifier='=>'resource'],
'ifx_connect' => ['resource', 'database='=>'string', 'userid='=>'string', 'password='=>'string'],
'ifx_copy_blob' => ['int', 'bid'=>'int'],
'ifx_create_blob' => ['int', 'type'=>'int', 'mode'=>'int', 'param'=>'string'],
'ifx_create_char' => ['int', 'param'=>'string'],
'ifx_do' => ['bool', 'result_id'=>'resource'],
'ifx_error' => ['string', 'link_identifier='=>'resource'],
'ifx_errormsg' => ['string', 'errorcode='=>'int'],
'ifx_fetch_row' => ['array', 'result_id'=>'resource', 'position='=>'mixed'],
'ifx_fieldproperties' => ['array', 'result_id'=>'resource'],
'ifx_fieldtypes' => ['array', 'result_id'=>'resource'],
'ifx_free_blob' => ['bool', 'bid'=>'int'],
'ifx_free_char' => ['bool', 'bid'=>'int'],
'ifx_free_result' => ['bool', 'result_id'=>'resource'],
'ifx_get_blob' => ['string', 'bid'=>'int'],
'ifx_get_char' => ['string', 'bid'=>'int'],
'ifx_getsqlca' => ['array', 'result_id'=>'resource'],
'ifx_htmltbl_result' => ['int', 'result_id'=>'resource', 'html_table_options='=>'string'],
'ifx_nullformat' => ['bool', 'mode'=>'int'],
'ifx_num_fields' => ['int', 'result_id'=>'resource'],
'ifx_num_rows' => ['int', 'result_id'=>'resource'],
'ifx_pconnect' => ['resource', 'database='=>'string', 'userid='=>'string', 'password='=>'string'],
'ifx_prepare' => ['resource', 'query'=>'string', 'link_identifier'=>'resource', 'cursor_def='=>'int', 'blobidarray='=>'mixed'],
'ifx_query' => ['resource', 'query'=>'string', 'link_identifier'=>'resource', 'cursor_type='=>'int', 'blobidarray='=>'mixed'],
'ifx_textasvarchar' => ['bool', 'mode'=>'int'],
'ifx_update_blob' => ['bool', 'bid'=>'int', 'content'=>'string'],
'ifx_update_char' => ['bool', 'bid'=>'int', 'content'=>'string'],
'ifxus_close_slob' => ['bool', 'bid'=>'int'],
'ifxus_create_slob' => ['int', 'mode'=>'int'],
'ifxus_free_slob' => ['bool', 'bid'=>'int'],
'ifxus_open_slob' => ['int', 'bid'=>'int', 'mode'=>'int'],
'ifxus_read_slob' => ['string', 'bid'=>'int', 'nbytes'=>'int'],
'ifxus_seek_slob' => ['int', 'bid'=>'int', 'mode'=>'int', 'offset'=>'int'],
'ifxus_tell_slob' => ['int', 'bid'=>'int'],
'ifxus_write_slob' => ['int', 'bid'=>'int', 'content'=>'string'],
'igbinary_serialize' => ['string|false', 'value'=>''],
'igbinary_unserialize' => ['', 'str'=>'string'],
'ignore_user_abort' => ['int', 'value='=>'bool'],
'iis_add_server' => ['int', 'path'=>'string', 'comment'=>'string', 'server_ip'=>'string', 'port'=>'int', 'host_name'=>'string', 'rights'=>'int', 'start_server'=>'int'],
'iis_get_dir_security' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string'],
'iis_get_script_map' => ['string', 'server_instance'=>'int', 'virtual_path'=>'string', 'script_extension'=>'string'],
'iis_get_server_by_comment' => ['int', 'comment'=>'string'],
'iis_get_server_by_path' => ['int', 'path'=>'string'],
'iis_get_server_rights' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string'],
'iis_get_service_state' => ['int', 'service_id'=>'string'],
'iis_remove_server' => ['int', 'server_instance'=>'int'],
'iis_set_app_settings' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string', 'application_scope'=>'string'],
'iis_set_dir_security' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string', 'directory_flags'=>'int'],
'iis_set_script_map' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string', 'script_extension'=>'string', 'engine_path'=>'string', 'allow_scripting'=>'int'],
'iis_set_server_rights' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string', 'directory_flags'=>'int'],
'iis_start_server' => ['int', 'server_instance'=>'int'],
'iis_start_service' => ['int', 'service_id'=>'string'],
'iis_stop_server' => ['int', 'server_instance'=>'int'],
'iis_stop_service' => ['int', 'service_id'=>'string'],
'image2wbmp' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'threshold='=>'int'],
'image_type_to_extension' => ['string', 'imagetype'=>'int', 'include_dot='=>'bool'],
'image_type_to_mime_type' => ['string', 'imagetype'=>'int'],
'imageaffine' => ['resource', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'],
'imageaffineconcat' => ['array', 'm1'=>'array', 'm2'=>'array'],
'imageaffinematrixconcat' => ['array', 'm1'=>'array', 'm2'=>'array'],
'imageaffinematrixget' => ['array', 'type'=>'int', 'options'=>'array'],
'imagealphablending' => ['bool', 'im'=>'resource', 'on'=>'bool'],
'imageantialias' => ['bool', 'im'=>'resource', 'on'=>'bool'],
'imagearc' => ['bool', 'im'=>'resource', 'cx'=>'int', 'cy'=>'int', 'w'=>'int', 'h'=>'int', 's'=>'int', 'e'=>'int', 'col'=>'int'],
'imagebmp' => ['bool', 'image'=>'resource', 'to='=>'mixed', 'compressed='=>'bool'],
'imagechar' => ['bool', 'im'=>'resource', 'font'=>'int', 'x'=>'int', 'y'=>'int', 'c'=>'string', 'col'=>'int'],
'imagecharup' => ['bool', 'im'=>'resource', 'font'=>'int', 'x'=>'int', 'y'=>'int', 'c'=>'string', 'col'=>'int'],
'imagecolorallocate' => ['int', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'],
'imagecolorallocatealpha' => ['int', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'],
'imagecolorat' => ['int', 'im'=>'resource', 'x'=>'int', 'y'=>'int'],
'imagecolorclosest' => ['int', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'],
'imagecolorclosestalpha' => ['int', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'],
'imagecolorclosesthwb' => ['int', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'],
'imagecolordeallocate' => ['bool', 'im'=>'resource', 'index'=>'int'],
'imagecolorexact' => ['int', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'],
'imagecolorexactalpha' => ['int', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'],
'imagecolormatch' => ['bool', 'im1'=>'resource', 'im2'=>'resource'],
'imagecolorresolve' => ['int', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'],
'imagecolorresolvealpha' => ['int', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'],
'imagecolorset' => ['void', 'im'=>'resource', 'col'=>'int', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha='=>'int'],
'imagecolorsforindex' => ['array', 'im'=>'resource', 'col'=>'int'],
'imagecolorstotal' => ['int', 'im'=>'resource'],
'imagecolortransparent' => ['int', 'im'=>'resource', 'col='=>'int'],
'imageconvolution' => ['resource', 'src_im'=>'resource', 'matrix3x3'=>'array', 'div'=>'float', 'offset'=>'float'],
'imagecopy' => ['bool', 'dst_im'=>'resource', 'src_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'src_w'=>'int', 'src_h'=>'int'],
'imagecopymerge' => ['bool', 'src_im'=>'resource', 'dst_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'src_w'=>'int', 'src_h'=>'int', 'pct'=>'int'],
'imagecopymergegray' => ['bool', 'src_im'=>'resource', 'dst_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'src_w'=>'int', 'src_h'=>'int', 'pct'=>'int'],
'imagecopyresampled' => ['bool', 'dst_im'=>'resource', 'src_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'dst_w'=>'int', 'dst_h'=>'int', 'src_w'=>'int', 'src_h'=>'int'],
'imagecopyresized' => ['bool', 'dst_im'=>'resource', 'src_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'dst_w'=>'int', 'dst_h'=>'int', 'src_w'=>'int', 'src_h'=>'int'],
'imagecreate' => ['resource', 'x_size'=>'int', 'y_size'=>'int'],
'imagecreatefrombmp' => ['resource', 'filename'=>'string'],
'imagecreatefromgd' => ['resource', 'filename'=>'string'],
'imagecreatefromgd2' => ['resource', 'filename'=>'string'],
'imagecreatefromgd2part' => ['resource', 'filename'=>'string', 'srcx'=>'int', 'srcy'=>'int', 'width'=>'int', 'height'=>'int'],
'imagecreatefromgif' => ['resource', 'filename'=>'string'],
'imagecreatefromjpeg' => ['resource', 'filename'=>'string'],
'imagecreatefrompng' => ['resource', 'filename'=>'string'],
'imagecreatefromstring' => ['resource', 'image'=>'string'],
'imagecreatefromwbmp' => ['resource', 'filename'=>'string'],
'imagecreatefromwebp' => ['resource', 'filename'=>'string'],
'imagecreatefromxbm' => ['resource', 'filename'=>'string'],
'imagecreatefromxpm' => ['resource', 'filename'=>'string'],
'imagecreatetruecolor' => ['resource', 'x_size'=>'int', 'y_size'=>'int'],
'imagecrop' => ['resource', 'im'=>'resource', 'rect'=>'array'],
'imagecropauto' => ['resource', 'im'=>'resource', 'mode'=>'int', 'threshold'=>'float', 'color'=>'int'],
'imagedashedline' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int', 'col'=>'int'],
'imagedestroy' => ['bool', 'im'=>'resource'],
'imageellipse' => ['bool', 'im'=>'resource', 'cx'=>'int', 'cy'=>'int', 'w'=>'int', 'h'=>'int', 'color'=>'int'],
'imagefill' => ['bool', 'im'=>'resource', 'x'=>'int', 'y'=>'int', 'col'=>'int'],
'imagefilledarc' => ['bool', 'im'=>'resource', 'cx'=>'int', 'cy'=>'int', 'w'=>'int', 'h'=>'int', 's'=>'int', 'e'=>'int', 'col'=>'int', 'style'=>'int'],
'imagefilledellipse' => ['bool', 'im'=>'resource', 'cx'=>'int', 'cy'=>'int', 'w'=>'int', 'h'=>'int', 'color'=>'int'],
'imagefilledpolygon' => ['bool', 'im'=>'resource', 'point'=>'array', 'num_points'=>'int', 'col'=>'int'],
'imagefilledrectangle' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int', 'col'=>'int'],
'imagefilltoborder' => ['bool', 'im'=>'resource', 'x'=>'int', 'y'=>'int', 'border'=>'int', 'col'=>'int'],
'imagefilter' => ['bool', 'src_im'=>'resource', 'filtertype'=>'int', 'arg1='=>'int', 'arg2='=>'int', 'arg3='=>'int', 'arg4='=>'int'],
'imageflip' => ['bool', 'im'=>'resource', 'mode'=>'int'],
'imagefontheight' => ['int', 'font'=>'int'],
'imagefontwidth' => ['int', 'font'=>'int'],
'imageftbbox' => ['array', 'size'=>'float', 'angle'=>'float', 'font_file'=>'string', 'text'=>'string', 'extrainfo='=>'array'],
'imagefttext' => ['array', 'im'=>'resource', 'size'=>'float', 'angle'=>'float', 'x'=>'int', 'y'=>'int', 'col'=>'int', 'font_file'=>'string', 'text'=>'string', 'extrainfo='=>'array'],
'imagegammacorrect' => ['bool', 'im'=>'resource', 'inputgamma'=>'float', 'outputgamma'=>'float'],
'imagegd' => ['bool', 'im'=>'resource', 'filename='=>'?string'],
'imagegd2' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'chunk_size='=>'int', 'type='=>'int'],
'imagegetclip' => ['array', 'im'=>'resource'],
'imagegif' => ['bool', 'im'=>'resource', 'filename='=>'?string'],
'imagegrabscreen' => ['resource'],
'imagegrabwindow' => ['resource', 'window_handle'=>'int', 'client_area='=>'int'],
'imageinterlace' => ['int', 'im'=>'resource', 'interlace='=>'int'],
'imageistruecolor' => ['bool', 'im'=>'resource'],
'imagejpeg' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'quality='=>'int'],
'imagelayereffect' => ['bool', 'im'=>'resource', 'effect'=>'int'],
'imageline' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int', 'col'=>'int'],
'imageloadfont' => ['int', 'filename'=>'string'],
'imageObj::pasteImage' => ['void', 'srcImg'=>'imageObj', 'transparentColorHex'=>'int', 'dstX'=>'int', 'dstY'=>'int', 'angle'=>'int'],
'imageObj::saveImage' => ['int', 'filename'=>'string', 'oMap'=>'MapObj'],
'imageObj::saveWebImage' => ['string'],
'imageopenpolygon' => ['bool', 'image'=>'resource', 'points'=>'array', 'num_points'=>'int', 'color'=>'int'],
'imagepalettecopy' => ['void', 'dst'=>'resource', 'src'=>'resource'],
'imagepalettetotruecolor' => ['bool', 'src'=>'resource'],
'imagepng' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'quality='=>'int', 'filters='=>'int'],
'imagepolygon' => ['bool', 'im'=>'resource', 'point'=>'array', 'num_points'=>'int', 'col'=>'int'],
'imagepsbbox' => ['array', 'text'=>'string', 'font'=>'', 'size'=>'int', 'space'=>'int', 'tightness'=>'int', 'angle'=>'float'],
'imagepsencodefont' => ['bool', 'font_index'=>'resource', 'encodingfile'=>'string'],
'imagepsextendfont' => ['bool', 'font_index'=>'resource', 'extend'=>'float'],
'imagepsfreefont' => ['bool', 'font_index'=>'resource'],
'imagepsloadfont' => ['resource', 'filename'=>'string'],
'imagepsslantfont' => ['bool', 'font_index'=>'resource', 'slant'=>'float'],
'imagepstext' => ['array', 'image'=>'resource', 'text'=>'string', 'font_index'=>'resource', 'size'=>'int', 'foreground'=>'int', 'background'=>'int', 'x'=>'int', 'y'=>'int', 'space='=>'int', 'tightness='=>'int', 'angle='=>'float', 'antialias_steps='=>'int'],
'imagerectangle' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int', 'col'=>'int'],
'imageresolution' => ['mixed', 'image'=>'resource', 'res_x='=>'int', 'res_y='=>'int'],
'imagerotate' => ['resource', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'],
'imagesavealpha' => ['bool', 'im'=>'resource', 'on'=>'bool'],
'imagescale' => ['resource', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'],
'imagesetbrush' => ['bool', 'image'=>'resource', 'brush'=>'resource'],
'imagesetclip' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int'],
'imagesetinterpolation' => ['bool', 'im'=>'resource', 'method'=>'int'],
'imagesetpixel' => ['bool', 'im'=>'resource', 'x'=>'int', 'y'=>'int', 'col'=>'int'],
'imagesetstyle' => ['bool', 'im'=>'resource', 'styles'=>'array'],
'imagesetthickness' => ['bool', 'im'=>'resource', 'thickness'=>'int'],
'imagesettile' => ['bool', 'image'=>'resource', 'tile'=>'resource'],
'imagestring' => ['bool', 'im'=>'resource', 'font'=>'int', 'x'=>'int', 'y'=>'int', 'str'=>'string', 'col'=>'int'],
'imagestringup' => ['bool', 'im'=>'resource', 'font'=>'int', 'x'=>'int', 'y'=>'int', 'str'=>'string', 'col'=>'int'],
'imagesx' => ['int', 'im'=>'resource'],
'imagesy' => ['int', 'im'=>'resource'],
'imagetruecolortopalette' => ['bool', 'im'=>'resource', 'ditherflag'=>'bool', 'colorswanted'=>'int'],
'imagettfbbox' => ['array', 'size'=>'float', 'angle'=>'float', 'font_file'=>'string', 'text'=>'string'],
'imagettftext' => ['array', 'im'=>'resource', 'size'=>'float', 'angle'=>'float', 'x'=>'int', 'y'=>'int', 'col'=>'int', 'font_file'=>'string', 'text'=>'string'],
'imagetypes' => ['int'],
'imagewbmp' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'foreground='=>'int'],
'imagewebp' => ['bool', 'im'=>'resource', 'filename'=>'?string', 'quality'=>'int'],
'imagexbm' => ['bool', 'im'=>'resource', 'filename'=>'?string', 'foreground='=>'int'],
'Imagick::__construct' => ['void', 'files='=>''],
'Imagick::__toString' => ['string'],
'Imagick::adaptiveBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'],
'Imagick::adaptiveResizeImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'bestfit='=>'bool'],
'Imagick::adaptiveSharpenImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'],
'Imagick::adaptiveThresholdImage' => ['bool', 'width'=>'int', 'height'=>'int', 'offset'=>'int'],
'Imagick::addImage' => ['bool', 'source'=>'imagick'],
'Imagick::addNoiseImage' => ['bool', 'noise_type'=>'int', 'channel='=>'int'],
'Imagick::affineTransformImage' => ['bool', 'matrix'=>'imagickdraw'],
'Imagick::animateImages' => ['bool', 'x_server'=>'string'],
'Imagick::annotateImage' => ['bool', 'draw_settings'=>'imagickdraw', 'x'=>'float', 'y'=>'float', 'angle'=>'float', 'text'=>'string'],
'Imagick::appendImages' => ['Imagick', 'stack'=>'bool'],
'Imagick::autoGammaImage' => ['bool', 'channel='=>'int'],
'Imagick::autoLevelImage' => ['void', 'CHANNEL='=>'string'],
'Imagick::autoOrient' => ['bool'],
'Imagick::averageImages' => ['Imagick'],
'Imagick::blackThresholdImage' => ['bool', 'threshold'=>'mixed'],
'Imagick::blueShiftImage' => ['void', 'factor='=>'float'],
'Imagick::blurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'],
'Imagick::borderImage' => ['bool', 'bordercolor'=>'mixed', 'width'=>'int', 'height'=>'int'],
'Imagick::brightnessContrastImage' => ['void', 'brightness'=>'string', 'contrast'=>'string', 'CHANNEL='=>'string'],
'Imagick::charcoalImage' => ['bool', 'radius'=>'float', 'sigma'=>'float'],
'Imagick::chopImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'],
'Imagick::clampImage' => ['void', 'CHANNEL='=>'string'],
'Imagick::clear' => ['bool'],
'Imagick::clipImage' => ['bool'],
'Imagick::clipImagePath' => ['void', 'pathname'=>'string', 'inside'=>'string'],
'Imagick::clipPathImage' => ['bool', 'pathname'=>'string', 'inside'=>'bool'],
'Imagick::clone' => ['Imagick'],
'Imagick::clutImage' => ['bool', 'lookup_table'=>'imagick', 'channel='=>'float'],
'Imagick::coalesceImages' => ['Imagick'],
'Imagick::colorFloodfillImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int'],
'Imagick::colorizeImage' => ['bool', 'colorize'=>'mixed', 'opacity'=>'mixed'],
'Imagick::colorMatrixImage' => ['void', 'color_matrix'=>'string'],
'Imagick::combineImages' => ['Imagick', 'channeltype'=>'int'],
'Imagick::commentImage' => ['bool', 'comment'=>'string'],
'Imagick::compareImageChannels' => ['array', 'image'=>'imagick', 'channeltype'=>'int', 'metrictype'=>'int'],
'Imagick::compareImageLayers' => ['Imagick', 'method'=>'int'],
'Imagick::compareImages' => ['array', 'compare'=>'imagick', 'metric'=>'int'],
'Imagick::compositeImage' => ['bool', 'composite_object'=>'imagick', 'composite'=>'int', 'x'=>'int', 'y'=>'int', 'channel='=>'int'],
'Imagick::compositeImageGravity' => ['bool', 'imagick'=>'Imagick', 'COMPOSITE_CONSTANT'=>'int', 'GRAVITY_CONSTANT'=>'int'],
'Imagick::contrastImage' => ['bool', 'sharpen'=>'bool'],
'Imagick::contrastStretchImage' => ['bool', 'black_point'=>'float', 'white_point'=>'float', 'channel='=>'int'],
'Imagick::convolveImage' => ['bool', 'kernel'=>'array', 'channel='=>'int'],
'Imagick::count' => ['void', 'mode='=>'string'],
'Imagick::cropImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'],
'Imagick::cropThumbnailImage' => ['bool', 'width'=>'int', 'height'=>'int'],
'Imagick::current' => ['Imagick'],
'Imagick::cycleColormapImage' => ['bool', 'displace'=>'int'],
'Imagick::decipherImage' => ['bool', 'passphrase'=>'string'],
'Imagick::deconstructImages' => ['Imagick'],
'Imagick::deleteImageArtifact' => ['bool', 'artifact'=>'string'],
'Imagick::deleteImageProperty' => ['void', 'name'=>'string'],
'Imagick::deskewImage' => ['bool', 'threshold'=>'float'],
'Imagick::despeckleImage' => ['bool'],
'Imagick::destroy' => ['bool'],
'Imagick::displayImage' => ['bool', 'servername'=>'string'],
'Imagick::displayImages' => ['bool', 'servername'=>'string'],
'Imagick::distortImage' => ['bool', 'method'=>'int', 'arguments'=>'array', 'bestfit'=>'bool'],
'Imagick::drawImage' => ['bool', 'draw'=>'imagickdraw'],
'Imagick::edgeImage' => ['bool', 'radius'=>'float'],
'Imagick::embossImage' => ['bool', 'radius'=>'float', 'sigma'=>'float'],
'Imagick::encipherImage' => ['bool', 'passphrase'=>'string'],
'Imagick::enhanceImage' => ['bool'],
'Imagick::equalizeImage' => ['bool'],
'Imagick::evaluateImage' => ['bool', 'op'=>'int', 'constant'=>'float', 'channel='=>'int'],
'Imagick::evaluateImages' => ['bool', 'EVALUATE_CONSTANT'=>'int'],
'Imagick::exportImagePixels' => ['array', 'x'=>'int', 'y'=>'int', 'width'=>'int', 'height'=>'int', 'map'=>'string', 'storage'=>'int'],
'Imagick::extentImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'],
'Imagick::filter' => ['void', 'ImagickKernel'=>'ImagickKernel', 'CHANNEL='=>'int'],
'Imagick::flattenImages' => ['Imagick'],
'Imagick::flipImage' => ['bool'],
'Imagick::floodFillPaintImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'target'=>'mixed', 'x'=>'int', 'y'=>'int', 'invert'=>'bool', 'channel='=>'int'],
'Imagick::flopImage' => ['bool'],
'Imagick::forwardFourierTransformimage' => ['void', 'magnitude'=>'bool'],
'Imagick::frameImage' => ['bool', 'matte_color'=>'mixed', 'width'=>'int', 'height'=>'int', 'inner_bevel'=>'int', 'outer_bevel'=>'int'],
'Imagick::functionImage' => ['bool', 'function'=>'int', 'arguments'=>'array', 'channel='=>'int'],
'Imagick::fxImage' => ['Imagick', 'expression'=>'string', 'channel='=>'int'],
'Imagick::gammaImage' => ['bool', 'gamma'=>'float', 'channel='=>'int'],
'Imagick::gaussianBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'],
'Imagick::getColorspace' => ['int'],
'Imagick::getCompression' => ['int'],
'Imagick::getCompressionQuality' => ['int'],
'Imagick::getConfigureOptions' => ['string'],
'Imagick::getCopyright' => ['string'],
'Imagick::getFeatures' => ['string'],
'Imagick::getFilename' => ['string'],
'Imagick::getFont' => ['string'],
'Imagick::getFormat' => ['string'],
'Imagick::getGravity' => ['int'],
'Imagick::getHDRIEnabled' => ['int'],
'Imagick::getHomeURL' => ['string'],
'Imagick::getImage' => ['Imagick'],
'Imagick::getImageAlphaChannel' => ['int'],
'Imagick::getImageArtifact' => ['string', 'artifact'=>'string'],
'Imagick::getImageAttribute' => ['string', 'key'=>'string'],
'Imagick::getImageBackgroundColor' => ['ImagickPixel'],
'Imagick::getImageBlob' => ['string'],
'Imagick::getImageBluePrimary' => ['array'],
'Imagick::getImageBorderColor' => ['ImagickPixel'],
'Imagick::getImageChannelDepth' => ['int', 'channel'=>'int'],
'Imagick::getImageChannelDistortion' => ['float', 'reference'=>'imagick', 'channel'=>'int', 'metric'=>'int'],
'Imagick::getImageChannelDistortions' => ['float', 'reference'=>'imagick', 'metric'=>'int', 'channel='=>'int'],
'Imagick::getImageChannelExtrema' => ['array', 'channel'=>'int'],
'Imagick::getImageChannelKurtosis' => ['array', 'channel='=>'int'],
'Imagick::getImageChannelMean' => ['array', 'channel'=>'int'],
'Imagick::getImageChannelRange' => ['array', 'channel'=>'int'],
'Imagick::getImageChannelStatistics' => ['array'],
'Imagick::getImageClipMask' => ['Imagick'],
'Imagick::getImageColormapColor' => ['ImagickPixel', 'index'=>'int'],
'Imagick::getImageColors' => ['int'],
'Imagick::getImageColorspace' => ['int'],
'Imagick::getImageCompose' => ['int'],
'Imagick::getImageCompression' => ['int'],
'Imagick::getImageCompressionQuality' => ['int'],
'Imagick::getImageDelay' => ['int'],
'Imagick::getImageDepth' => ['int'],
'Imagick::getImageDispose' => ['int'],
'Imagick::getImageDistortion' => ['float', 'reference'=>'magickwand', 'metric'=>'int'],
'Imagick::getImageExtrema' => ['array'],
'Imagick::getImageFilename' => ['string'],
'Imagick::getImageFormat' => ['string'],
'Imagick::getImageGamma' => ['float'],
'Imagick::getImageGeometry' => ['array'],
'Imagick::getImageGravity' => ['int'],
'Imagick::getImageGreenPrimary' => ['array'],
'Imagick::getImageHeight' => ['int'],
'Imagick::getImageHistogram' => ['array'],
'Imagick::getImageIndex' => ['int'],
'Imagick::getImageInterlaceScheme' => ['int'],
'Imagick::getImageInterpolateMethod' => ['int'],
'Imagick::getImageIterations' => ['int'],
'Imagick::getImageLength' => ['int'],
'Imagick::getImageMagickLicense' => ['string'],
'Imagick::getImageMatte' => ['bool'],
'Imagick::getImageMatteColor' => ['ImagickPixel'],
'Imagick::getImageMimeType' => ['string'],
'Imagick::getImageOrientation' => ['int'],
'Imagick::getImagePage' => ['array'],
'Imagick::getImagePixelColor' => ['ImagickPixel', 'x'=>'int', 'y'=>'int'],
'Imagick::getImageProfile' => ['string', 'name'=>'string'],
'Imagick::getImageProfiles' => ['array', 'pattern='=>'string', 'only_names='=>'bool'],
'Imagick::getImageProperties' => ['array', 'pattern='=>'string', 'only_names='=>'bool'],
'Imagick::getImageProperty' => ['string', 'name'=>'string'],
'Imagick::getImageRedPrimary' => ['array'],
'Imagick::getImageRegion' => ['Imagick', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'],
'Imagick::getImageRenderingIntent' => ['int'],
'Imagick::getImageResolution' => ['array'],
'Imagick::getImagesBlob' => ['string'],
'Imagick::getImageScene' => ['int'],
'Imagick::getImageSignature' => ['string'],
'Imagick::getImageSize' => ['int'],
'Imagick::getImageTicksPerSecond' => ['int'],
'Imagick::getImageTotalInkDensity' => ['float'],
'Imagick::getImageType' => ['int'],
'Imagick::getImageUnits' => ['int'],
'Imagick::getImageVirtualPixelMethod' => ['int'],
'Imagick::getImageWhitePoint' => ['array'],
'Imagick::getImageWidth' => ['int'],
'Imagick::getInterlaceScheme' => ['int'],
'Imagick::getIteratorIndex' => ['int'],
'Imagick::getNumberImages' => ['int'],
'Imagick::getOption' => ['string', 'key'=>'string'],
'Imagick::getPackageName' => ['string'],
'Imagick::getPage' => ['array'],
'Imagick::getPixelIterator' => ['ImagickPixelIterator'],
'Imagick::getPixelRegionIterator' => ['ImagickPixelIterator', 'x'=>'int', 'y'=>'int', 'columns'=>'int', 'rows'=>'int'],
'Imagick::getPointSize' => ['float'],
'Imagick::getQuantum' => ['int'],
'Imagick::getQuantumDepth' => ['array'],
'Imagick::getQuantumRange' => ['array'],
'Imagick::getRegistry' => ['string', 'key'=>'string'],
'Imagick::getReleaseDate' => ['string'],
'Imagick::getResource' => ['int', 'type'=>'int'],
'Imagick::getResourceLimit' => ['int', 'type'=>'int'],
'Imagick::getSamplingFactors' => ['array'],
'Imagick::getSize' => ['array'],
'Imagick::getSizeOffset' => ['int'],
'Imagick::getVersion' => ['array'],
'Imagick::haldClutImage' => ['bool', 'clut'=>'imagick', 'channel='=>'int'],
'Imagick::hasNextImage' => ['bool'],
'Imagick::hasPreviousImage' => ['bool'],
'Imagick::identifyFormat' => ['string|false', 'embedText'=>'string'],
'Imagick::identifyImage' => ['array', 'appendrawoutput='=>'bool'],
'Imagick::identifyImageType' => ['int'],
'Imagick::implodeImage' => ['bool', 'radius'=>'float'],
'Imagick::importImagePixels' => ['bool', 'x'=>'int', 'y'=>'int', 'width'=>'int', 'height'=>'int', 'map'=>'string', 'storage'=>'int', 'pixels'=>'array'],
'Imagick::inverseFourierTransformImage' => ['void', 'complement'=>'string', 'magnitude'=>'string'],
'Imagick::key' => ['int|string'],
'Imagick::labelImage' => ['bool', 'label'=>'string'],
'Imagick::levelImage' => ['bool', 'blackpoint'=>'float', 'gamma'=>'float', 'whitepoint'=>'float', 'channel='=>'int'],
'Imagick::linearStretchImage' => ['bool', 'blackpoint'=>'float', 'whitepoint'=>'float'],
'Imagick::liquidRescaleImage' => ['bool', 'width'=>'int', 'height'=>'int', 'delta_x'=>'float', 'rigidity'=>'float'],
'Imagick::listRegistry' => ['array'],
'Imagick::localContrastImage' => ['bool', 'radius'=>'float', 'strength'=>'float'],
'Imagick::magnifyImage' => ['bool'],
'Imagick::mapImage' => ['bool', 'map'=>'imagick', 'dither'=>'bool'],
'Imagick::matteFloodfillImage' => ['bool', 'alpha'=>'float', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int'],
'Imagick::medianFilterImage' => ['bool', 'radius'=>'float'],
'Imagick::mergeImageLayers' => ['bool', 'layer_method'=>'int'],
'Imagick::minifyImage' => ['bool'],
'Imagick::modulateImage' => ['bool', 'brightness'=>'float', 'saturation'=>'float', 'hue'=>'float'],
'Imagick::montageImage' => ['Imagick', 'draw'=>'imagickdraw', 'tile_geometry'=>'string', 'thumbnail_geometry'=>'string', 'mode'=>'int', 'frame'=>'string'],
'Imagick::morphImages' => ['Imagick', 'number_frames'=>'int'],
'Imagick::morphology' => ['void', 'morphologyMethod'=>'int', 'iterations'=>'int', 'ImagickKernel'=>'ImagickKernel', 'CHANNEL='=>'string'],
'Imagick::mosaicImages' => ['Imagick'],
'Imagick::motionBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'angle'=>'float', 'channel='=>'int'],
'Imagick::negateImage' => ['bool', 'gray'=>'bool', 'channel='=>'int'],
'Imagick::newImage' => ['bool', 'cols'=>'int', 'rows'=>'int', 'background'=>'mixed', 'format='=>'string'],
'Imagick::newPseudoImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'pseudostring'=>'string'],
'Imagick::next' => ['void'],
'Imagick::nextImage' => ['bool'],
'Imagick::normalizeImage' => ['bool', 'channel='=>'int'],
'Imagick::oilPaintImage' => ['bool', 'radius'=>'float'],
'Imagick::opaquePaintImage' => ['bool', 'target'=>'mixed', 'fill'=>'mixed', 'fuzz'=>'float', 'invert'=>'bool', 'channel='=>'int'],
'Imagick::optimizeImageLayers' => ['bool'],
'Imagick::orderedPosterizeImage' => ['bool', 'threshold_map'=>'string', 'channel='=>'int'],
'Imagick::paintFloodfillImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int', 'channel='=>'int'],
'Imagick::paintOpaqueImage' => ['bool', 'target'=>'mixed', 'fill'=>'mixed', 'fuzz'=>'float', 'channel='=>'int'],
'Imagick::paintTransparentImage' => ['bool', 'target'=>'mixed', 'alpha'=>'float', 'fuzz'=>'float'],
'Imagick::pingImage' => ['bool', 'filename'=>'string'],
'Imagick::pingImageBlob' => ['bool', 'image'=>'string'],
'Imagick::pingImageFile' => ['bool', 'filehandle'=>'resource', 'filename='=>'string'],
'Imagick::polaroidImage' => ['bool', 'properties'=>'imagickdraw', 'angle'=>'float'],
'Imagick::posterizeImage' => ['bool', 'levels'=>'int', 'dither'=>'bool'],
'Imagick::previewImages' => ['bool', 'preview'=>'int'],
'Imagick::previousImage' => ['bool'],
'Imagick::profileImage' => ['bool', 'name'=>'string', 'profile'=>'string'],
'Imagick::quantizeImage' => ['bool', 'numbercolors'=>'int', 'colorspace'=>'int', 'treedepth'=>'int', 'dither'=>'bool', 'measureerror'=>'bool'],
'Imagick::quantizeImages' => ['bool', 'numbercolors'=>'int', 'colorspace'=>'int', 'treedepth'=>'int', 'dither'=>'bool', 'measureerror'=>'bool'],
'Imagick::queryFontMetrics' => ['array', 'properties'=>'imagickdraw', 'text'=>'string', 'multiline='=>'bool'],
'Imagick::queryFonts' => ['array', 'pattern='=>'string'],
'Imagick::queryFormats' => ['array', 'pattern='=>'string'],
'Imagick::radialBlurImage' => ['bool', 'angle'=>'float', 'channel='=>'int'],
'Imagick::raiseImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int', 'raise'=>'bool'],
'Imagick::randomThresholdImage' => ['bool', 'low'=>'float', 'high'=>'float', 'channel='=>'int'],
'Imagick::readImage' => ['bool', 'filename'=>'string'],
'Imagick::readImageBlob' => ['bool', 'image'=>'string', 'filename='=>'string'],
'Imagick::readImageFile' => ['bool', 'filehandle'=>'resource', 'filename='=>'string'],
'Imagick::readImages' => ['Imagick', 'filenames'=>'string'],
'Imagick::recolorImage' => ['bool', 'matrix'=>'array'],
'Imagick::reduceNoiseImage' => ['bool', 'radius'=>'float'],
'Imagick::remapImage' => ['bool', 'replacement'=>'imagick', 'dither'=>'int'],
'Imagick::removeImage' => ['bool'],
'Imagick::removeImageProfile' => ['string', 'name'=>'string'],
'Imagick::render' => ['bool'],
'Imagick::resampleImage' => ['bool', 'x_resolution'=>'float', 'y_resolution'=>'float', 'filter'=>'int', 'blur'=>'float'],
'Imagick::resetImagePage' => ['bool', 'page'=>'string'],
'Imagick::resetIterator' => [''],
'Imagick::resizeImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'filter'=>'int', 'blur'=>'float', 'bestfit='=>'bool'],
'Imagick::rewind' => ['void'],
'Imagick::rollImage' => ['bool', 'x'=>'int', 'y'=>'int'],
'Imagick::rotateImage' => ['bool', 'background'=>'mixed', 'degrees'=>'float'],
'Imagick::rotationalBlurImage' => ['void', 'angle'=>'string', 'CHANNEL='=>'string'],
'Imagick::roundCorners' => ['bool', 'x_rounding'=>'float', 'y_rounding'=>'float', 'stroke_width='=>'float', 'displace='=>'float', 'size_correction='=>'float'],
'Imagick::roundCornersImage' => ['', 'xRounding'=>'', 'yRounding'=>'', 'strokeWidth'=>'', 'displace'=>'', 'sizeCorrection'=>''],
'Imagick::sampleImage' => ['bool', 'columns'=>'int', 'rows'=>'int'],
'Imagick::scaleImage' => ['bool', 'cols'=>'int', 'rows'=>'int', 'bestfit='=>'bool'],
'Imagick::segmentImage' => ['bool', 'colorspace'=>'int', 'cluster_threshold'=>'float', 'smooth_threshold'=>'float', 'verbose='=>'bool'],
'Imagick::selectiveBlurImage' => ['void', 'radius'=>'float', 'sigma'=>'float', 'threshold'=>'float', 'CHANNEL'=>'int'],
'Imagick::separateImageChannel' => ['bool', 'channel'=>'int'],
'Imagick::sepiaToneImage' => ['bool', 'threshold'=>'float'],
'Imagick::setAntiAlias' => ['int', 'antialias'=>'bool'],
'Imagick::setBackgroundColor' => ['bool', 'background'=>'mixed'],
'Imagick::setColorspace' => ['bool', 'colorspace'=>'int'],
'Imagick::setCompression' => ['bool', 'compression'=>'int'],
'Imagick::setCompressionQuality' => ['bool', 'quality'=>'int'],
'Imagick::setFilename' => ['bool', 'filename'=>'string'],
'Imagick::setFirstIterator' => ['bool'],
'Imagick::setFont' => ['bool', 'font'=>'string'],
'Imagick::setFormat' => ['bool', 'format'=>'string'],
'Imagick::setGravity' => ['bool', 'gravity'=>'int'],
'Imagick::setImage' => ['bool', 'replace'=>'imagick'],
'Imagick::setImageAlpha' => ['bool', 'alpha'=>'float'],
'Imagick::setImageAlphaChannel' => ['bool', 'mode'=>'int'],
'Imagick::setImageArtifact' => ['bool', 'artifact'=>'string', 'value'=>'string'],
'Imagick::setImageAttribute' => ['void', 'key'=>'string', 'value'=>'string'],
'Imagick::setImageBackgroundColor' => ['bool', 'background'=>'mixed'],
'Imagick::setImageBias' => ['bool', 'bias'=>'float'],
'Imagick::setImageBiasQuantum' => ['void', 'bias'=>'string'],
'Imagick::setImageBluePrimary' => ['bool', 'x'=>'float', 'y'=>'float'],
'Imagick::setImageBorderColor' => ['bool', 'border'=>'mixed'],
'Imagick::setImageChannelDepth' => ['bool', 'channel'=>'int', 'depth'=>'int'],
'Imagick::setImageChannelMask' => ['', 'channel'=>'int'],
'Imagick::setImageClipMask' => ['bool', 'clip_mask'=>'imagick'],
'Imagick::setImageColormapColor' => ['bool', 'index'=>'int', 'color'=>'imagickpixel'],
'Imagick::setImageColorspace' => ['bool', 'colorspace'=>'int'],
'Imagick::setImageCompose' => ['bool', 'compose'=>'int'],
'Imagick::setImageCompression' => ['bool', 'compression'=>'int'],
'Imagick::setImageCompressionQuality' => ['bool', 'quality'=>'int'],
'Imagick::setImageDelay' => ['bool', 'delay'=>'int'],
'Imagick::setImageDepth' => ['bool', 'depth'=>'int'],
'Imagick::setImageDispose' => ['bool', 'dispose'=>'int'],
'Imagick::setImageExtent' => ['bool', 'columns'=>'int', 'rows'=>'int'],
'Imagick::setImageFilename' => ['bool', 'filename'=>'string'],
'Imagick::setImageFormat' => ['bool', 'format'=>'string'],
'Imagick::setImageGamma' => ['bool', 'gamma'=>'float'],
'Imagick::setImageGravity' => ['bool', 'gravity'=>'int'],
'Imagick::setImageGreenPrimary' => ['bool', 'x'=>'float', 'y'=>'float'],
'Imagick::setImageIndex' => ['bool', 'index'=>'int'],
'Imagick::setImageInterlaceScheme' => ['bool', 'interlace_scheme'=>'int'],
'Imagick::setImageInterpolateMethod' => ['bool', 'method'=>'int'],
'Imagick::setImageIterations' => ['bool', 'iterations'=>'int'],
'Imagick::setImageMatte' => ['bool', 'matte'=>'bool'],
'Imagick::setImageMatteColor' => ['bool', 'matte'=>'mixed'],
'Imagick::setImageOpacity' => ['bool', 'opacity'=>'float'],
'Imagick::setImageOrientation' => ['bool', 'orientation'=>'int'],
'Imagick::setImagePage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'],
'Imagick::setImageProfile' => ['bool', 'name'=>'string', 'profile'=>'string'],
'Imagick::setImageProgressMonitor' => ['', 'filename'=>''],
'Imagick::setImageProperty' => ['bool', 'name'=>'string', 'value'=>'string'],
'Imagick::setImageRedPrimary' => ['bool', 'x'=>'float', 'y'=>'float'],
'Imagick::setImageRenderingIntent' => ['bool', 'rendering_intent'=>'int'],
'Imagick::setImageResolution' => ['bool', 'x_resolution'=>'float', 'y_resolution'=>'float'],
'Imagick::setImageScene' => ['bool', 'scene'=>'int'],
'Imagick::setImageTicksPerSecond' => ['bool', 'ticks_per-second'=>'int'],
'Imagick::setImageType' => ['bool', 'image_type'=>'int'],
'Imagick::setImageUnits' => ['bool', 'units'=>'int'],
'Imagick::setImageVirtualPixelMethod' => ['bool', 'method'=>'int'],
'Imagick::setImageWhitePoint' => ['bool', 'x'=>'float', 'y'=>'float'],
'Imagick::setInterlaceScheme' => ['bool', 'interlace_scheme'=>'int'],
'Imagick::setIteratorIndex' => ['bool', 'index'=>'int'],
'Imagick::setLastIterator' => ['bool'],
'Imagick::setOption' => ['bool', 'key'=>'string', 'value'=>'string'],
'Imagick::setPage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'],
'Imagick::setPointSize' => ['bool', 'point_size'=>'float'],
'Imagick::setProgressMonitor' => ['void', 'callback'=>'callable'],
'Imagick::setRegistry' => ['void', 'key'=>'string', 'value'=>'string'],
'Imagick::setResolution' => ['bool', 'x_resolution'=>'float', 'y_resolution'=>'float'],
'Imagick::setResourceLimit' => ['bool', 'type'=>'int', 'limit'=>'int'],
'Imagick::setSamplingFactors' => ['bool', 'factors'=>'array'],
'Imagick::setSize' => ['bool', 'columns'=>'int', 'rows'=>'int'],
'Imagick::setSizeOffset' => ['bool', 'columns'=>'int', 'rows'=>'int', 'offset'=>'int'],
'Imagick::setType' => ['bool', 'image_type'=>'int'],
'Imagick::shadeImage' => ['bool', 'gray'=>'bool', 'azimuth'=>'float', 'elevation'=>'float'],
'Imagick::shadowImage' => ['bool', 'opacity'=>'float', 'sigma'=>'float', 'x'=>'int', 'y'=>'int'],
'Imagick::sharpenImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'],
'Imagick::shaveImage' => ['bool', 'columns'=>'int', 'rows'=>'int'],
'Imagick::shearImage' => ['bool', 'background'=>'mixed', 'x_shear'=>'float', 'y_shear'=>'float'],
'Imagick::sigmoidalContrastImage' => ['bool', 'sharpen'=>'bool', 'alpha'=>'float', 'beta'=>'float', 'channel='=>'int'],
'Imagick::similarityImage' => ['Imagick', 'imagick'=>'Imagick', '&bestMatch'=>'array', '&similarity'=>'float', 'similarity_threshold'=>'float', 'metric'=>'int'],
'Imagick::sketchImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'angle'=>'float'],
'Imagick::smushImages' => ['Imagick', 'stack'=>'string', 'offset'=>'string'],
'Imagick::solarizeImage' => ['bool', 'threshold'=>'int'],
'Imagick::sparseColorImage' => ['bool', 'sparse_method'=>'int', 'arguments'=>'array', 'channel='=>'int'],
'Imagick::spliceImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'],
'Imagick::spreadImage' => ['bool', 'radius'=>'float'],
'Imagick::statisticImage' => ['void', 'type'=>'int', 'width'=>'int', 'height'=>'int', 'CHANNEL='=>'string'],
'Imagick::steganoImage' => ['Imagick', 'watermark_wand'=>'imagick', 'offset'=>'int'],
'Imagick::stereoImage' => ['bool', 'offset_wand'=>'imagick'],
'Imagick::stripImage' => ['bool'],
'Imagick::subImageMatch' => ['Imagick', 'Imagick'=>'Imagick', '&w_offset='=>'array', '&w_similarity='=>'float'],
'Imagick::swirlImage' => ['bool', 'degrees'=>'float'],
'Imagick::textureImage' => ['bool', 'texture_wand'=>'imagick'],
'Imagick::thresholdImage' => ['bool', 'threshold'=>'float', 'channel='=>'int'],
'Imagick::thumbnailImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'bestfit='=>'bool', 'fill='=>'bool'],
'Imagick::tintImage' => ['bool', 'tint'=>'mixed', 'opacity'=>'mixed'],
'Imagick::transformImage' => ['Imagick', 'crop'=>'string', 'geometry'=>'string'],
'Imagick::transformImageColorspace' => ['bool', 'colorspace'=>'int'],
'Imagick::transparentPaintImage' => ['bool', 'target'=>'mixed', 'alpha'=>'float', 'fuzz'=>'float', 'invert'=>'bool'],
'Imagick::transposeImage' => ['bool'],
'Imagick::transverseImage' => ['bool'],
'Imagick::trimImage' => ['bool', 'fuzz'=>'float'],
'Imagick::uniqueImageColors' => ['bool'],
'Imagick::unsharpMaskImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'amount'=>'float', 'threshold'=>'float', 'channel='=>'int'],
'Imagick::valid' => ['bool'],
'Imagick::vignetteImage' => ['bool', 'blackpoint'=>'float', 'whitepoint'=>'float', 'x'=>'int', 'y'=>'int'],
'Imagick::waveImage' => ['bool', 'amplitude'=>'float', 'length'=>'float'],
'Imagick::whiteThresholdImage' => ['bool', 'threshold'=>'mixed'],
'Imagick::writeImage' => ['bool', 'filename='=>'string'],
'Imagick::writeImageFile' => ['bool', 'filehandle'=>'resource'],
'Imagick::writeImages' => ['bool', 'filename'=>'string', 'adjoin'=>'bool'],
'Imagick::writeImagesFile' => ['bool', 'filehandle'=>'resource'],
'ImagickDraw::__construct' => ['void'],
'ImagickDraw::affine' => ['bool', 'affine'=>'array'],
'ImagickDraw::annotation' => ['bool', 'x'=>'float', 'y'=>'float', 'text'=>'string'],
'ImagickDraw::arc' => ['bool', 'sx'=>'float', 'sy'=>'float', 'ex'=>'float', 'ey'=>'float', 'sd'=>'float', 'ed'=>'float'],
'ImagickDraw::bezier' => ['bool', 'coordinates'=>'array'],
'ImagickDraw::circle' => ['bool', 'ox'=>'float', 'oy'=>'float', 'px'=>'float', 'py'=>'float'],
'ImagickDraw::clear' => ['bool'],
'ImagickDraw::clone' => ['ImagickDraw'],
'ImagickDraw::color' => ['bool', 'x'=>'float', 'y'=>'float', 'paintmethod'=>'int'],
'ImagickDraw::comment' => ['bool', 'comment'=>'string'],
'ImagickDraw::composite' => ['bool', 'compose'=>'int', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float', 'compositewand'=>'imagick'],
'ImagickDraw::destroy' => ['bool'],
'ImagickDraw::ellipse' => ['bool', 'ox'=>'float', 'oy'=>'float', 'rx'=>'float', 'ry'=>'float', 'start'=>'float', 'end'=>'float'],
'ImagickDraw::getBorderColor' => ['ImagickPixel'],
'ImagickDraw::getClipPath' => ['string'],
'ImagickDraw::getClipRule' => ['int'],
'ImagickDraw::getClipUnits' => ['int'],
'ImagickDraw::getDensity' => ['null|string'],
'ImagickDraw::getFillColor' => ['ImagickPixel'],
'ImagickDraw::getFillOpacity' => ['float'],
'ImagickDraw::getFillRule' => ['int'],
'ImagickDraw::getFont' => ['string'],
'ImagickDraw::getFontFamily' => ['string'],
'ImagickDraw::getFontResolution' => ['array'],
'ImagickDraw::getFontSize' => ['float'],
'ImagickDraw::getFontStretch' => ['int'],
'ImagickDraw::getFontStyle' => ['int'],
'ImagickDraw::getFontWeight' => ['int'],
'ImagickDraw::getGravity' => ['int'],
'ImagickDraw::getOpacity' => ['float'],
'ImagickDraw::getStrokeAntialias' => ['bool'],
'ImagickDraw::getStrokeColor' => ['ImagickPixel'],
'ImagickDraw::getStrokeDashArray' => ['array'],
'ImagickDraw::getStrokeDashOffset' => ['float'],
'ImagickDraw::getStrokeLineCap' => ['int'],
'ImagickDraw::getStrokeLineJoin' => ['int'],
'ImagickDraw::getStrokeMiterLimit' => ['int'],
'ImagickDraw::getStrokeOpacity' => ['float'],
'ImagickDraw::getStrokeWidth' => ['float'],
'ImagickDraw::getTextAlignment' => ['int'],
'ImagickDraw::getTextAntialias' => ['bool'],
'ImagickDraw::getTextDecoration' => ['int'],
'ImagickDraw::getTextDirection' => ['bool'],
'ImagickDraw::getTextEncoding' => ['string'],
'ImagickDraw::getTextInterlineSpacing' => ['float'],
'ImagickDraw::getTextInterwordSpacing' => ['float'],
'ImagickDraw::getTextKerning' => ['float'],
'ImagickDraw::getTextUnderColor' => ['ImagickPixel'],
'ImagickDraw::getVectorGraphics' => ['string'],
'ImagickDraw::line' => ['bool', 'sx'=>'float', 'sy'=>'float', 'ex'=>'float', 'ey'=>'float'],
'ImagickDraw::matte' => ['bool', 'x'=>'float', 'y'=>'float', 'paintmethod'=>'int'],
'ImagickDraw::pathClose' => ['bool'],
'ImagickDraw::pathCurveToAbsolute' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathCurveToQuadraticBezierAbsolute' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathCurveToQuadraticBezierRelative' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathCurveToQuadraticBezierSmoothAbsolute' => ['bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathCurveToQuadraticBezierSmoothRelative' => ['bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathCurveToRelative' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathCurveToSmoothAbsolute' => ['bool', 'x2'=>'float', 'y2'=>'float', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathCurveToSmoothRelative' => ['bool', 'x2'=>'float', 'y2'=>'float', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathEllipticArcAbsolute' => ['bool', 'rx'=>'float', 'ry'=>'float', 'x_axis_rotation'=>'float', 'large_arc_flag'=>'bool', 'sweep_flag'=>'bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathEllipticArcRelative' => ['bool', 'rx'=>'float', 'ry'=>'float', 'x_axis_rotation'=>'float', 'large_arc_flag'=>'bool', 'sweep_flag'=>'bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathFinish' => ['bool'],
'ImagickDraw::pathLineToAbsolute' => ['bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathLineToHorizontalAbsolute' => ['bool', 'x'=>'float'],
'ImagickDraw::pathLineToHorizontalRelative' => ['bool', 'x'=>'float'],
'ImagickDraw::pathLineToRelative' => ['bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathLineToVerticalAbsolute' => ['bool', 'y'=>'float'],
'ImagickDraw::pathLineToVerticalRelative' => ['bool', 'y'=>'float'],
'ImagickDraw::pathMoveToAbsolute' => ['bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathMoveToRelative' => ['bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::pathStart' => ['bool'],
'ImagickDraw::point' => ['bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::polygon' => ['bool', 'coordinates'=>'array'],
'ImagickDraw::polyline' => ['bool', 'coordinates'=>'array'],
'ImagickDraw::pop' => ['bool'],
'ImagickDraw::popClipPath' => ['bool'],
'ImagickDraw::popDefs' => ['bool'],
'ImagickDraw::popPattern' => ['bool'],
'ImagickDraw::push' => ['bool'],
'ImagickDraw::pushClipPath' => ['bool', 'clip_mask_id'=>'string'],
'ImagickDraw::pushDefs' => ['bool'],
'ImagickDraw::pushPattern' => ['bool', 'pattern_id'=>'string', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'],
'ImagickDraw::rectangle' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float'],
'ImagickDraw::render' => ['bool'],
'ImagickDraw::resetVectorGraphics' => ['void'],
'ImagickDraw::rotate' => ['bool', 'degrees'=>'float'],
'ImagickDraw::roundRectangle' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'rx'=>'float', 'ry'=>'float'],
'ImagickDraw::scale' => ['bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::setBorderColor' => ['bool', 'color'=>'ImagickPixel'],
'ImagickDraw::setClipPath' => ['bool', 'clip_mask'=>'string'],
'ImagickDraw::setClipRule' => ['bool', 'fill_rule'=>'int'],
'ImagickDraw::setClipUnits' => ['bool', 'clip_units'=>'int'],
'ImagickDraw::setDensity' => ['bool', 'density_string'=>'string'],
'ImagickDraw::setFillAlpha' => ['bool', 'opacity'=>'float'],
'ImagickDraw::setFillColor' => ['bool', 'fill_pixel'=>'imagickpixel'],
'ImagickDraw::setFillOpacity' => ['bool', 'fillopacity'=>'float'],
'ImagickDraw::setFillPatternURL' => ['bool', 'fill_url'=>'string'],
'ImagickDraw::setFillRule' => ['bool', 'fill_rule'=>'int'],
'ImagickDraw::setFont' => ['bool', 'font_name'=>'string'],
'ImagickDraw::setFontFamily' => ['bool', 'font_family'=>'string'],
'ImagickDraw::setFontResolution' => ['bool', 'x'=>'float', 'y'=>'float'],
'ImagickDraw::setFontSize' => ['bool', 'pointsize'=>'float'],
'ImagickDraw::setFontStretch' => ['bool', 'fontstretch'=>'int'],
'ImagickDraw::setFontStyle' => ['bool', 'style'=>'int'],
'ImagickDraw::setFontWeight' => ['bool', 'font_weight'=>'int'],
'ImagickDraw::setGravity' => ['bool', 'gravity'=>'int'],
'ImagickDraw::setOpacity' => ['void', 'opacity'=>'float'],
'ImagickDraw::setResolution' => ['void', 'x_resolution'=>'string', 'y_resolution'=>'string'],
'ImagickDraw::setStrokeAlpha' => ['bool', 'opacity'=>'float'],
'ImagickDraw::setStrokeAntialias' => ['bool', 'stroke_antialias'=>'bool'],
'ImagickDraw::setStrokeColor' => ['bool', 'stroke_pixel'=>'imagickpixel'],
'ImagickDraw::setStrokeDashArray' => ['bool', 'dasharray'=>'array'],
'ImagickDraw::setStrokeDashOffset' => ['bool', 'dash_offset'=>'float'],
'ImagickDraw::setStrokeLineCap' => ['bool', 'linecap'=>'int'],
'ImagickDraw::setStrokeLineJoin' => ['bool', 'linejoin'=>'int'],
'ImagickDraw::setStrokeMiterLimit' => ['bool', 'miterlimit'=>'int'],
'ImagickDraw::setStrokeOpacity' => ['bool', 'stroke_opacity'=>'float'],
'ImagickDraw::setStrokePatternURL' => ['bool', 'stroke_url'=>'string'],
'ImagickDraw::setStrokeWidth' => ['bool', 'stroke_width'=>'float'],
'ImagickDraw::setTextAlignment' => ['bool', 'alignment'=>'int'],
'ImagickDraw::setTextAntialias' => ['bool', 'antialias'=>'bool'],
'ImagickDraw::setTextDecoration' => ['bool', 'decoration'=>'int'],
'ImagickDraw::setTextDirection' => ['bool', 'direction'=>'int'],
'ImagickDraw::setTextEncoding' => ['bool', 'encoding'=>'string'],
'ImagickDraw::setTextInterlineSpacing' => ['void', 'spacing'=>'float'],
'ImagickDraw::setTextInterwordSpacing' => ['void', 'spacing'=>'float'],
'ImagickDraw::setTextKerning' => ['void', 'kerning'=>'float'],
'ImagickDraw::setTextUnderColor' => ['bool', 'under_color'=>'imagickpixel'],
'ImagickDraw::setVectorGraphics' => ['bool', 'xml'=>'string'],
'ImagickDraw::setViewbox' => ['bool', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int'],
'ImagickDraw::skewX' => ['bool', 'degrees'=>'float'],
'ImagickDraw::skewY' => ['bool', 'degrees'=>'float'],
'ImagickDraw::translate' => ['bool', 'x'=>'float', 'y'=>'float'],
'ImagickKernel::addKernel' => ['void', 'ImagickKernel'=>'ImagickKernel'],
'ImagickKernel::addUnityKernel' => ['void'],
'ImagickKernel::fromBuiltin' => ['ImagickKernel', 'kernelType'=>'string', 'kernelString'=>'string'],
'ImagickKernel::fromMatrix' => ['ImagickKernel', 'matrix'=>'array', 'origin='=>'array'],
'ImagickKernel::getMatrix' => ['array'],
'ImagickKernel::scale' => ['void'],
'ImagickKernel::separate' => ['array'],
'ImagickKernel::seperate' => ['void'],
'ImagickPixel::__construct' => ['void', 'color='=>'string'],
'ImagickPixel::clear' => ['bool'],
'ImagickPixel::clone' => ['void'],
'ImagickPixel::destroy' => ['bool'],
'ImagickPixel::getColor' => ['array', 'normalized='=>'bool'],
'ImagickPixel::getColorAsString' => ['string'],
'ImagickPixel::getColorCount' => ['int'],
'ImagickPixel::getColorQuantum' => ['mixed'],
'ImagickPixel::getColorValue' => ['float', 'color'=>'int'],
'ImagickPixel::getColorValueQuantum' => ['mixed'],
'ImagickPixel::getHSL' => ['array'],
'ImagickPixel::getIndex' => ['int'],
'ImagickPixel::isPixelSimilar' => ['bool', 'color'=>'ImagickPixel', 'fuzz'=>'float'],
'ImagickPixel::isPixelSimilarQuantum' => ['bool', 'color'=>'string', 'fuzz='=>'string'],
'ImagickPixel::isSimilar' => ['bool', 'color'=>'imagickpixel', 'fuzz'=>'float'],
'ImagickPixel::setColor' => ['bool', 'color'=>'string'],
'ImagickPixel::setcolorcount' => ['void', 'colorCount'=>'string'],
'ImagickPixel::setColorFromPixel' => ['bool', 'srcPixel'=>'ImagickPixel'],
'ImagickPixel::setColorValue' => ['bool', 'color'=>'int', 'value'=>'float'],
'ImagickPixel::setColorValueQuantum' => ['void', 'color'=>'int', 'value'=>'mixed'],
'ImagickPixel::setHSL' => ['bool', 'hue'=>'float', 'saturation'=>'float', 'luminosity'=>'float'],
'ImagickPixel::setIndex' => ['void', 'index'=>'int'],
'ImagickPixelIterator::__construct' => ['void', 'wand'=>'imagick'],
'ImagickPixelIterator::clear' => ['bool'],
'ImagickPixelIterator::current' => ['mixed'],
'ImagickPixelIterator::destroy' => ['bool'],
'ImagickPixelIterator::getCurrentIteratorRow' => ['array'],
'ImagickPixelIterator::getIteratorRow' => ['int'],
'ImagickPixelIterator::getNextIteratorRow' => ['array'],
'ImagickPixelIterator::getpixeliterator' => ['', 'Imagick'=>'Imagick'],
'ImagickPixelIterator::getpixelregioniterator' => ['', 'Imagick'=>'Imagick', 'x'=>'', 'y'=>'', 'columns'=>'', 'rows'=>''],
'ImagickPixelIterator::getPreviousIteratorRow' => ['array'],
'ImagickPixelIterator::key' => ['int|string'],
'ImagickPixelIterator::newPixelIterator' => ['bool', 'wand'=>'imagick'],
'ImagickPixelIterator::newPixelRegionIterator' => ['bool', 'wand'=>'imagick', 'x'=>'int', 'y'=>'int', 'columns'=>'int', 'rows'=>'int'],
'ImagickPixelIterator::next' => ['void'],
'ImagickPixelIterator::resetIterator' => ['bool'],
'ImagickPixelIterator::rewind' => ['void'],
'ImagickPixelIterator::setIteratorFirstRow' => ['bool'],
'ImagickPixelIterator::setIteratorLastRow' => ['bool'],
'ImagickPixelIterator::setIteratorRow' => ['bool', 'row'=>'int'],
'ImagickPixelIterator::syncIterator' => ['bool'],
'ImagickPixelIterator::valid' => ['bool'],
'imap_8bit' => ['string', 'text'=>'string'],
'imap_alerts' => ['array|false'],
'imap_append' => ['bool', 'stream_id'=>'resource', 'folder'=>'string', 'message'=>'string', 'options='=>'string', 'internal_date='=>'string'],
'imap_base64' => ['string', 'text'=>'string'],
'imap_binary' => ['string', 'text'=>'string'],
'imap_body' => ['string', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'],
'imap_bodystruct' => ['object', 'stream_id'=>'resource', 'msg_no'=>'int', 'section'=>'string'],
'imap_check' => ['object', 'stream_id'=>'resource'],
'imap_clearflag_full' => ['bool', 'stream_id'=>'resource', 'sequence'=>'string', 'flag'=>'string', 'options='=>'int'],
'imap_close' => ['bool', 'stream_id'=>'resource', 'options='=>'int'],
'imap_create' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'],
'imap_createmailbox' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'],
'imap_delete' => ['bool', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'],
'imap_deletemailbox' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'],
'imap_errors' => ['array|false'],
'imap_expunge' => ['bool', 'stream_id'=>'resource'],
'imap_fetch_overview' => ['array', 'stream_id'=>'resource', 'sequence'=>'string', 'options='=>'int'],
'imap_fetchbody' => ['string', 'stream_id'=>'resource', 'msg_no'=>'int', 'section'=>'string', 'options='=>'int'],
'imap_fetchheader' => ['string', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'],
'imap_fetchmime' => ['string', 'stream_id'=>'resource', 'msg_no'=>'int', 'section'=>'string', 'options='=>'int'],
'imap_fetchstructure' => ['object', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'],
'imap_fetchtext' => ['string', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'],
'imap_gc' => ['bool', 'stream_id'=>'resource', 'flags'=>'int'],
'imap_get_quota' => ['array', 'stream_id'=>'resource', 'qroot'=>'string'],
'imap_get_quotaroot' => ['array', 'stream_id'=>'resource', 'mbox'=>'string'],
'imap_getacl' => ['array', 'stream_id'=>'resource', 'mailbox'=>'string'],
'imap_getmailboxes' => ['array', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'],
'imap_getsubscribed' => ['array', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'],
'imap_header' => ['object', 'stream_id'=>'resource', 'msg_no'=>'int', 'from_length='=>'int', 'subject_length='=>'int', 'default_host='=>'string'],
'imap_headerinfo' => ['object', 'stream_id'=>'resource', 'msg_no'=>'int', 'from_length='=>'int', 'subject_length='=>'int', 'default_host='=>'string'],
'imap_headers' => ['array', 'stream_id'=>'resource'],
'imap_last_error' => ['string|false'],
'imap_list' => ['array', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'],
'imap_listmailbox' => ['array', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'],
'imap_listscan' => ['array', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string', 'content'=>'string'],
'imap_listsubscribed' => ['array', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'],
'imap_lsub' => ['array', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string'],
'imap_mail' => ['bool', 'to'=>'string', 'subject'=>'string', 'message'=>'string', 'additional_headers='=>'string', 'cc='=>'string', 'bcc='=>'string', 'rpath='=>'string'],
'imap_mail_compose' => ['string', 'envelope'=>'array', 'body'=>'array'],
'imap_mail_copy' => ['bool', 'stream_id'=>'resource', 'msglist'=>'string', 'mailbox'=>'string', 'options='=>'int'],
'imap_mail_move' => ['bool', 'stream_id'=>'resource', 'sequence'=>'string', 'mailbox'=>'string', 'options='=>'int'],
'imap_mailboxmsginfo' => ['object', 'stream_id'=>'resource'],
'imap_mime_header_decode' => ['array', 'str'=>'string'],
'imap_msgno' => ['int', 'stream_id'=>'resource', 'unique_msg_id'=>'int'],
'imap_mutf7_to_utf8' => ['string', 'in'=>'string'],
'imap_num_msg' => ['int', 'stream_id'=>'resource'],
'imap_num_recent' => ['int', 'stream_id'=>'resource'],
'imap_open' => ['resource', 'mailbox'=>'string', 'user'=>'string', 'password'=>'string', 'options='=>'int', 'n_retries='=>'int'],
'imap_ping' => ['bool', 'stream_id'=>'resource'],
'imap_qprint' => ['string', 'text'=>'string'],
'imap_rename' => ['bool', 'stream_id'=>'resource', 'old_name'=>'string', 'new_name'=>'string'],
'imap_renamemailbox' => ['bool', 'stream_id'=>'resource', 'old_name'=>'string', 'new_name'=>'string'],
'imap_reopen' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string', 'options='=>'int', 'n_retries='=>'int'],
'imap_rfc822_parse_adrlist' => ['array', 'address_string'=>'string', 'default_host'=>'string'],
'imap_rfc822_parse_headers' => ['object', 'headers'=>'string', 'default_host='=>'string'],
'imap_rfc822_write_address' => ['string', 'mailbox'=>'?string', 'host'=>'?string', 'personal'=>'?string'],
'imap_savebody' => ['bool', 'stream_id'=>'resource', 'file'=>'string|resource', 'msg_no'=>'int', 'section='=>'string', 'options='=>'int'],
'imap_scan' => ['array', 'stream_id'=>'resource', 'ref'=>'string', 'pattern'=>'string', 'content'=>'string'],
'imap_scanmailbox' => ['', 'stream_id'=>'resource', 'ref'=>'', 'pattern'=>'', 'content'=>''],
'imap_search' => ['array', 'stream_id'=>'resource', 'criteria'=>'string', 'options='=>'int', 'charset='=>'string'],
'imap_set_quota' => ['bool', 'stream_id'=>'resource', 'qroot'=>'string', 'mailbox_size'=>'int'],
'imap_setacl' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string', 'id'=>'string', 'rights'=>'string'],
'imap_setflag_full' => ['bool', 'stream_id'=>'resource', 'sequence'=>'string', 'flag'=>'string', 'options='=>'int'],
'imap_sort' => ['array', 'stream_id'=>'resource', 'criteria'=>'int', 'reverse'=>'int', 'options='=>'int', 'search_criteria='=>'string', 'charset='=>'string'],
'imap_status' => ['object', 'stream_id'=>'resource', 'mailbox'=>'string', 'options'=>'int'],
'imap_subscribe' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'],
'imap_thread' => ['array', 'stream_id'=>'resource', 'options='=>'int'],
'imap_timeout' => ['mixed', 'timeout_type'=>'int', 'timeout='=>'int'],
'imap_uid' => ['int', 'stream_id'=>'resource', 'msg_no'=>'int'],
'imap_undelete' => ['bool', 'stream_id'=>'resource', 'msg_no'=>'int', 'flags='=>'int'],
'imap_unsubscribe' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'],
'imap_utf7_decode' => ['string', 'buf'=>'string'],
'imap_utf7_encode' => ['string', 'buf'=>'string'],
'imap_utf8' => ['string', 'mime_encoded_text'=>'string'],
'imap_utf8_to_mutf7' => ['string', 'in'=>'string'],
'implode' => ['string', 'glue'=>'string', 'pieces'=>'array'],
'implode\'1' => ['string', 'pieces'=>'array', 'glue='=>'string'],
'import_request_variables' => ['bool', 'types'=>'string', 'prefix='=>'string'],
'in_array' => ['bool', 'needle'=>'mixed', 'haystack'=>'array', 'strict='=>'bool'],
'inclued_get_data' => ['array'],
'inet_ntop' => ['string', 'in_addr'=>'string'],
'inet_pton' => ['string', 'ip_address'=>'string'],
'InfiniteIterator::__construct' => ['void', 'it'=>'iterator'],
'InfiniteIterator::next' => ['void'],
'inflate_add' => ['string', 'context'=>'resource', 'encoded_data'=>'string', 'flush_mode'=>'int'],
'inflate_get_read_len' => ['int', 'resource'=>'resource'],
'inflate_get_status' => ['int', 'resource'=>'resource'],
'inflate_init' => ['resource', 'encoding'=>'int'],
'ingres_autocommit' => ['bool', 'link'=>'resource'],
'ingres_autocommit_state' => ['bool', 'link'=>'resource'],
'ingres_charset' => ['string', 'link'=>'resource'],
'ingres_close' => ['bool', 'link'=>'resource'],
'ingres_commit' => ['bool', 'link'=>'resource'],
'ingres_connect' => ['resource', 'database='=>'string', 'username='=>'string', 'password='=>'string', 'options='=>'array'],
'ingres_cursor' => ['string', 'result'=>'resource'],
'ingres_errno' => ['int', 'link='=>'resource'],
'ingres_error' => ['string', 'link='=>'resource'],
'ingres_errsqlstate' => ['string', 'link='=>'resource'],
'ingres_escape_string' => ['string', 'link'=>'resource', 'source_string'=>'string'],
'ingres_execute' => ['bool', 'result'=>'resource', 'params='=>'array', 'types='=>'string'],
'ingres_fetch_array' => ['array', 'result'=>'resource', 'result_type='=>'int'],
'ingres_fetch_assoc' => ['array', 'result'=>'resource'],
'ingres_fetch_object' => ['object', 'result'=>'resource', 'result_type='=>'int'],
'ingres_fetch_proc_return' => ['int', 'result'=>'resource'],
'ingres_fetch_row' => ['array', 'result'=>'resource'],
'ingres_field_length' => ['int', 'result'=>'resource', 'index'=>'int'],
'ingres_field_name' => ['string', 'result'=>'resource', 'index'=>'int'],
'ingres_field_nullable' => ['bool', 'result'=>'resource', 'index'=>'int'],
'ingres_field_precision' => ['int', 'result'=>'resource', 'index'=>'int'],
'ingres_field_scale' => ['int', 'result'=>'resource', 'index'=>'int'],
'ingres_field_type' => ['string', 'result'=>'resource', 'index'=>'int'],
'ingres_free_result' => ['bool', 'result'=>'resource'],
'ingres_next_error' => ['bool', 'link='=>'resource'],
'ingres_num_fields' => ['int', 'result'=>'resource'],
'ingres_num_rows' => ['int', 'result'=>'resource'],
'ingres_pconnect' => ['resource', 'database='=>'string', 'username='=>'string', 'password='=>'string', 'options='=>'array'],
'ingres_prepare' => ['mixed', 'link'=>'resource', 'query'=>'string'],
'ingres_query' => ['mixed', 'link'=>'resource', 'query'=>'string', 'params='=>'array', 'types='=>'string'],
'ingres_result_seek' => ['bool', 'result'=>'resource', 'position'=>'int'],
'ingres_rollback' => ['bool', 'link'=>'resource'],
'ingres_set_environment' => ['bool', 'link'=>'resource', 'options'=>'array'],
'ingres_unbuffered_query' => ['mixed', 'link'=>'resource', 'query'=>'string', 'params='=>'array', 'types='=>'string'],
'ini_alter' => ['string|false', 'varname'=>'string', 'newvalue'=>'string|int|float|bool'],
'ini_get' => ['string|false', 'varname'=>'string'],
'ini_get_all' => ['array', 'extension='=>'?string', 'details='=>'bool'],
'ini_restore' => ['void', 'varname'=>'string'],
'ini_set' => ['string|false', 'varname'=>'string', 'newvalue'=>'string|int|float|bool'],
'inotify_add_watch' => ['int', 'inotify_instance'=>'resource', 'pathname'=>'string', 'mask'=>'int'],
'inotify_init' => ['resource'],
'inotify_queue_len' => ['int', 'inotify_instance'=>'resource'],
'inotify_read' => ['array', 'inotify_instance'=>'resource'],
'inotify_rm_watch' => ['bool', 'inotify_instance'=>'resource', 'watch_descriptor'=>'int'],
'intdiv' => ['int', 'numerator'=>'int', 'divisor'=>'int'],
'interface_exists' => ['bool', 'classname'=>'string', 'autoload='=>'bool'],
'intl_error_name' => ['string', 'error_code'=>'int'],
'intl_get_error_code' => ['int'],
'intl_get_error_message' => ['string'],
'intl_is_failure' => ['bool', 'error_code'=>'int'],
'IntlBreakIterator::__construct' => ['void'],
'IntlBreakIterator::createCharacterInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'],
'IntlBreakIterator::createCodePointInstance' => ['IntlCodePointBreakIterator'],
'IntlBreakIterator::createLineInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'],
'IntlBreakIterator::createSentenceInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'],
'IntlBreakIterator::createTitleInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'],
'IntlBreakIterator::createWordInstance' => ['IntlRuleBasedBreakIterator', 'locale='=>'string'],
'IntlBreakIterator::current' => ['int'],
'IntlBreakIterator::first' => ['int'],
'IntlBreakIterator::following' => ['int', 'offset'=>'string'],
'IntlBreakIterator::getErrorCode' => ['int'],
'IntlBreakIterator::getErrorMessage' => ['string'],
'IntlBreakIterator::getLocale' => ['string', 'locale_type'=>'string'],
'IntlBreakIterator::getPartsIterator' => ['IntlPartsIterator', 'key_type='=>'string'],
'IntlBreakIterator::getText' => ['string'],
'IntlBreakIterator::isBoundary' => ['bool', 'offset'=>'string'],
'IntlBreakIterator::last' => ['int'],
'IntlBreakIterator::next' => ['int', 'offset='=>'string'],
'IntlBreakIterator::preceding' => ['int', 'offset'=>'string'],
'IntlBreakIterator::previous' => ['int'],
'IntlBreakIterator::setText' => ['bool', 'text'=>'string'],
'intlcal_add' => ['bool', 'cal'=>'IntlCalendar', 'field'=>'int', 'amount'=>'int'],
'intlcal_after' => ['bool', 'cal'=>'IntlCalendar', 'other'=>'IntlCalendar'],
'intlcal_before' => ['bool', 'cal'=>'IntlCalendar', 'other'=>'IntlCalendar'],
'intlcal_clear' => ['bool', 'cal'=>'IntlCalendar', 'field='=>'int'],
'intlcal_create_instance' => ['IntlCalendar', 'timeZone='=>'mixed', 'locale='=>'string'],
'intlcal_equals' => ['bool', 'cal'=>'IntlCalendar', 'other'=>'IntlCalendar'],
'intlcal_field_difference' => ['int', 'cal'=>'IntlCalendar', 'when'=>'float', 'field'=>'int'],
'intlcal_from_date_time' => ['IntlCalendar', 'dateTime'=>'DateTime|string'],
'intlcal_get' => ['int', 'cal'=>'IntlCalendar', 'field'=>'int'],
'intlcal_get_actual_maximum' => ['int', 'cal'=>'IntlCalendar', 'field'=>'int'],
'intlcal_get_actual_minimum' => ['int', 'cal'=>'IntlCalendar', 'field'=>'int'],
'intlcal_get_available_locales' => ['array'],
'intlcal_get_day_of_week_type' => ['int', 'cal'=>'IntlCalendar', 'dayOfWeek'=>'int'],
'intlcal_get_first_day_of_week' => ['int', 'cal'=>'IntlCalendar'],
'intlcal_get_greatest_minimum' => ['int', 'cal'=>'IntlCalendar', 'field'=>'int'],
'intlcal_get_keyword_values_for_locale' => ['Iterator', 'key'=>'string', 'locale'=>'string', 'commonlyUsed'=>'bool'],
'intlcal_get_least_maximum' => ['int', 'cal'=>'IntlCalendar', 'field'=>'int'],
'intlcal_get_locale' => ['string', 'cal'=>'IntlCalendar', 'localeType'=>'int'],
'intlcal_get_maximum' => ['int', 'cal'=>'IntlCalendar', 'field'=>'int'],
'intlcal_get_minimal_days_in_first_week' => ['int', 'cal'=>'IntlCalendar'],
'intlcal_get_minimum' => ['int', 'cal'=>'IntlCalendar', 'field'=>'int'],
'intlcal_get_now' => ['float'],
'intlcal_get_repeated_wall_time_option' => ['int', 'cal'=>'IntlCalendar'],
'intlcal_get_skipped_wall_time_option' => ['int', 'cal'=>'IntlCalendar'],
'intlcal_get_time' => ['float', 'cal'=>'IntlCalendar'],
'intlcal_get_time_zone' => ['IntlTimeZone', 'cal'=>'IntlCalendar'],
'intlcal_get_type' => ['string', 'cal'=>'IntlCalendar'],
'intlcal_get_weekend_transition' => ['int', 'cal'=>'IntlCalendar', 'dayOfWeek'=>'string'],
'intlcal_in_daylight_time' => ['bool', 'cal'=>'IntlCalendar'],
'intlcal_is_equivalent_to' => ['bool', 'cal'=>'IntlCalendar', 'other'=>'IntlCalendar'],
'intlcal_is_lenient' => ['bool', 'cal'=>'IntlCalendar'],
'intlcal_is_set' => ['bool', 'cal'=>'IntlCalendar', 'field'=>'int'],
'intlcal_is_weekend' => ['bool', 'cal'=>'IntlCalendar', 'date='=>'float'],
'intlcal_roll' => ['bool', 'cal'=>'IntlCalendar', 'field'=>'int', 'amountOrUpOrDown'=>'mixed'],
'intlcal_set' => ['bool', 'cal'=>'IntlCalendar', 'field'=>'int', 'value'=>'int'],
'intlcal_set\'1' => ['bool', 'cal'=>'IntlCalendar', 'year'=>'int', 'month'=>'int', 'dayOfMonth='=>'int', 'hour='=>'int', 'minute='=>'int', 'second='=>'int'],
'intlcal_set_first_day_of_week' => ['bool', 'cal'=>'IntlCalendar', 'dayOfWeek'=>'int'],
'intlcal_set_lenient' => ['bool', 'cal'=>'IntlCalendar', 'isLenient'=>'bool'],
'intlcal_set_repeated_wall_time_option' => ['bool', 'cal'=>'IntlCalendar', 'wallTimeOption'=>'int'],
'intlcal_set_skipped_wall_time_option' => ['bool', 'cal'=>'IntlCalendar', 'wallTimeOption'=>'int'],
'intlcal_set_time' => ['bool', 'cal'=>'IntlCalendar', 'date'=>'float'],
'intlcal_set_time_zone' => ['bool', 'cal'=>'IntlCalendar', 'timeZone'=>'mixed'],
'intlcal_to_date_time' => ['DateTime', 'cal'=>'IntlCalendar'],
'IntlCalendar::__construct' => ['void'],
'IntlCalendar::add' => ['bool', 'field'=>'int', 'amount'=>'int'],
'IntlCalendar::after' => ['bool', 'other'=>'IntlCalendar'],
'IntlCalendar::before' => ['bool', 'other'=>'IntlCalendar'],
'IntlCalendar::clear' => ['bool', 'field='=>'int'],
'IntlCalendar::createInstance' => ['IntlCalendar', 'timeZone='=>'mixed', 'locale='=>'string'],
'IntlCalendar::equals' => ['bool', 'other'=>'IntlCalendar'],
'IntlCalendar::fieldDifference' => ['int', 'when'=>'float', 'field'=>'int'],
'IntlCalendar::fromDateTime' => ['IntlCalendar', 'dateTime'=>'DateTime|string'],
'IntlCalendar::get' => ['int', 'field'=>'int'],
'IntlCalendar::getActualMaximum' => ['int', 'field'=>'int'],
'IntlCalendar::getActualMinimum' => ['int', 'field'=>'int'],
'IntlCalendar::getAvailableLocales' => ['array'],
'IntlCalendar::getDayOfWeekType' => ['int', 'dayOfWeek'=>'int'],
'IntlCalendar::getErrorCode' => ['int'],
'IntlCalendar::getErrorMessage' => ['string'],
'IntlCalendar::getFirstDayOfWeek' => ['int'],
'IntlCalendar::getGreatestMinimum' => ['int', 'field'=>'int'],
'IntlCalendar::getKeywordValuesForLocale' => ['Iterator', 'key'=>'string', 'locale'=>'string', 'commonlyUsed'=>'bool'],
'IntlCalendar::getLeastMaximum' => ['int', 'field'=>'int'],
'IntlCalendar::getLocale' => ['string', 'localeType'=>'int'],
'IntlCalendar::getMaximum' => ['int', 'field'=>'int'],
'IntlCalendar::getMinimalDaysInFirstWeek' => ['int'],
'IntlCalendar::getMinimum' => ['int', 'field'=>'int'],
'IntlCalendar::getNow' => ['float'],
'IntlCalendar::getRepeatedWallTimeOption' => ['int'],
'IntlCalendar::getSkippedWallTimeOption' => ['int'],
'IntlCalendar::getTime' => ['float'],
'IntlCalendar::getTimeZone' => ['IntlTimeZone'],
'IntlCalendar::getType' => ['string'],
'IntlCalendar::getWeekendTransition' => ['int', 'dayOfWeek'=>'string'],
'IntlCalendar::inDaylightTime' => ['bool'],
'IntlCalendar::isEquivalentTo' => ['bool', 'other'=>'IntlCalendar'],
'IntlCalendar::isLenient' => ['bool'],
'IntlCalendar::isSet' => ['bool', 'field'=>'int'],
'IntlCalendar::isWeekend' => ['bool', 'date='=>'float'],
'IntlCalendar::roll' => ['bool', 'field'=>'int', 'amountOrUpOrDown'=>'mixed'],
'IntlCalendar::set' => ['bool', 'field'=>'int', 'value'=>'int'],
'IntlCalendar::set\'1' => ['bool', 'year'=>'int', 'month'=>'int', 'dayOfMonth='=>'int', 'hour='=>'int', 'minute='=>'int', 'second='=>'int'],
'IntlCalendar::setFirstDayOfWeek' => ['bool', 'dayOfWeek'=>'int'],
'IntlCalendar::setLenient' => ['bool', 'isLenient'=>'string'],
'IntlCalendar::setMinimalDaysInFirstWeek' => ['bool', 'minimalDays'=>'int'],
'IntlCalendar::setRepeatedWallTimeOption' => ['bool', 'wallTimeOption'=>'int'],
'IntlCalendar::setSkippedWallTimeOption' => ['bool', 'wallTimeOption'=>'int'],
'IntlCalendar::setTime' => ['bool', 'date'=>'float'],
'IntlCalendar::setTimeZone' => ['bool', 'timeZone'=>'mixed'],
'IntlCalendar::toDateTime' => ['DateTime'],
'IntlChar::charAge' => ['array', 'char'=>'int|string'],
'IntlChar::charDigitValue' => ['int', 'codepoint'=>'mixed'],
'IntlChar::charDirection' => ['int', 'codepoint'=>'mixed'],
'IntlChar::charFromName' => ['int', 'name'=>'string', 'namechoice='=>'int'],
'IntlChar::charMirror' => ['mixed', 'codepoint'=>'mixed'],
'IntlChar::charName' => ['string', 'char'=>'int|string', 'namechoice='=>'int'],
'IntlChar::charType' => ['int', 'codepoint'=>'mixed'],
'IntlChar::chr' => ['string', 'codepoint'=>'mixed'],
'IntlChar::digit' => ['int', 'char'=>'int|string', 'radix='=>'int'],
'IntlChar::enumCharNames' => ['void', 'start'=>'mixed', 'limit'=>'mixed', 'callback'=>'callable', 'nameChoice='=>'int'],
'IntlChar::enumCharTypes' => ['void', 'cb='=>'callable'],
'IntlChar::foldCase' => ['int|string', 'char'=>'int|string', 'options='=>'int'],
'IntlChar::forDigit' => ['int', 'digit'=>'int', 'radix'=>'int'],
'IntlChar::getBidiPairedBracket' => ['mixed', 'codepoint'=>'mixed'],
'IntlChar::getBlockCode' => ['int', 'char'=>'int|string'],
'IntlChar::getCombiningClass' => ['int', 'codepoint'=>'mixed'],
'IntlChar::getFC_NFKC_Closure' => ['string', 'char'=>'int|string'],
'IntlChar::getIntPropertyMaxValue' => ['int', 'property'=>'int'],
'IntlChar::getIntPropertyMinValue' => ['int', 'property'=>'int'],
'IntlChar::getIntPropertyMxValue' => ['int', 'property'=>'int'],
'IntlChar::getIntPropertyValue' => ['int', 'char'=>'int|string', 'property'=>'int'],
'IntlChar::getNumericValue' => ['float', 'char'=>'int|string'],
'IntlChar::getPropertyEnum' => ['int', 'alias'=>'string'],
'IntlChar::getPropertyName' => ['string', 'property'=>'int', 'namechoice='=>'int'],
'IntlChar::getPropertyValueEnum' => ['int', 'property'=>'int', 'name'=>'string'],
'IntlChar::getPropertyValueName' => ['string', 'prop'=>'int', 'val'=>'int', 'namechoice='=>'int'],
'IntlChar::getUnicodeVersion' => ['array'],
'IntlChar::hasBinaryProperty' => ['bool', 'char'=>'int|string', 'property'=>'int'],
'IntlChar::isalnum' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isalpha' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isbase' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isblank' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::iscntrl' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isdefined' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isdigit' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isgraph' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isIDIgnorable' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isIDPart' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isIDStart' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isISOControl' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isJavaIDPart' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isJavaIDStart' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isJavaSpaceChar' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::islower' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isMirrored' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isprint' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::ispunct' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isspace' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::istitle' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isUAlphabetic' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isULowercase' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isupper' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isUUppercase' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isUWhiteSpace' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isWhitespace' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::isxdigit' => ['bool', 'codepoint'=>'mixed'],
'IntlChar::ord' => ['int', 'character'=>'mixed'],
'IntlChar::tolower' => ['mixed', 'codepoint'=>'mixed'],
'IntlChar::totitle' => ['mixed', 'codepoint'=>'mixed'],
'IntlChar::toupper' => ['mixed', 'codepoint'=>'mixed'],
'IntlCodePointBreakIterator::getLastCodePoint' => ['int'],
'IntlDateFormatter::__construct' => ['void', 'locale'=>'string', 'datetype'=>'?int', 'timetype'=>'?int', 'timezone='=>'null|int|IntlTimeZone|DateTimeZone', 'calendar='=>'null|int|IntlCalendar', 'pattern='=>'string'],
'IntlDateFormatter::create' => ['IntlDateFormatter', 'locale'=>'string', 'datetype'=>'int', 'timetype'=>'int', 'timezone='=>'int', 'calendar='=>'int|IntlCalendar', 'pattern='=>'string'],
'IntlDateFormatter::format' => ['string', 'args'=>''],
'IntlDateFormatter::formatObject' => ['string', 'object'=>'object', 'format='=>'mixed', 'locale='=>'string'],
'IntlDateFormatter::getCalendar' => ['int'],
'IntlDateFormatter::getCalendarObject' => ['IntlCalendar'],
'IntlDateFormatter::getDateType' => ['int'],
'IntlDateFormatter::getErrorCode' => ['int'],
'IntlDateFormatter::getErrorMessage' => ['string'],
'IntlDateFormatter::getLocale' => ['string'],
'IntlDateFormatter::getPattern' => ['string'],
'IntlDateFormatter::getTimeType' => ['int'],
'IntlDateFormatter::getTimeZone' => ['IntlTimeZone'],
'IntlDateFormatter::getTimeZoneId' => ['string'],
'IntlDateFormatter::isLenient' => ['bool'],
'IntlDateFormatter::localtime' => ['array', 'text_to_parse'=>'string', '&w_parse_pos='=>'int'],
'IntlDateFormatter::parse' => ['int', 'text_to_parse'=>'string', '&rw_parse_pos='=>'int'],
'IntlDateFormatter::setCalendar' => ['bool', 'calendar'=>''],
'IntlDateFormatter::setLenient' => ['bool', 'lenient'=>'bool'],
'IntlDateFormatter::setPattern' => ['bool', 'pattern'=>'string'],
'IntlDateFormatter::setTimeZone' => ['bool', 'timezone'=>''],
'IntlDateFormatter::setTimeZoneId' => ['bool', 'zone'=>'string', 'fmt='=>'IntlDateFormatter'],
'IntlGregorianCalendar::getGregorianChange' => ['float'],
'IntlGregorianCalendar::isLeapYear' => ['bool', 'year'=>'int'],
'IntlGregorianCalendar::setGregorianChange' => ['bool', 'date'=>'float'],
'IntlIterator::current' => ['mixed'],
'IntlIterator::key' => ['string'],
'IntlIterator::next' => ['void'],
'IntlIterator::rewind' => ['void'],
'IntlIterator::valid' => ['bool'],
'IntlPartsIterator::getBreakIterator' => ['IntlBreakIterator'],
'IntlRuleBasedBreakIterator::__construct' => ['void', 'rules'=>'string', 'areCompiled='=>'string'],
'IntlRuleBasedBreakIterator::createCharacterInstance' => ['IntlRuleBasedBreakIterator', 'locale'=>'string'],
'IntlRuleBasedBreakIterator::createCodePointInstance' => ['IntlCodePointBreakIterator'],
'IntlRuleBasedBreakIterator::createLineInstance' => ['IntlRuleBasedBreakIterator', 'locale'=>'string'],
'IntlRuleBasedBreakIterator::createSentenceInstance' => ['IntlRuleBasedBreakIterator', 'locale'=>'string'],
'IntlRuleBasedBreakIterator::createTitleInstance' => ['IntlRuleBasedBreakIterator', 'locale'=>'string'],
'IntlRuleBasedBreakIterator::createWordInstance' => ['IntlRuleBasedBreakIterator', 'locale'=>'string'],
'IntlRuleBasedBreakIterator::current' => ['int'],
'IntlRuleBasedBreakIterator::first' => ['int'],
'IntlRuleBasedBreakIterator::following' => ['int', 'offset'=>'string'],
'IntlRuleBasedBreakIterator::getBinaryRules' => ['string'],
'IntlRuleBasedBreakIterator::getErrorCode' => ['int'],
'IntlRuleBasedBreakIterator::getErrorMessage' => ['string'],
'IntlRuleBasedBreakIterator::getLocale' => ['string', 'locale_type'=>'string'],
'IntlRuleBasedBreakIterator::getPartsIterator' => ['IntlPartsIterator', 'key_type='=>'string'],
'IntlRuleBasedBreakIterator::getRules' => ['string'],
'IntlRuleBasedBreakIterator::getRuleStatus' => ['int'],
'IntlRuleBasedBreakIterator::getRuleStatusVec' => ['array'],
'IntlRuleBasedBreakIterator::getText' => ['string'],
'IntlRuleBasedBreakIterator::isBoundary' => ['bool', 'offset'=>'string'],
'IntlRuleBasedBreakIterator::last' => ['int'],
'IntlRuleBasedBreakIterator::next' => ['int', 'offset='=>'string'],
'IntlRuleBasedBreakIterator::preceding' => ['int', 'offset'=>'string'],
'IntlRuleBasedBreakIterator::previous' => ['int'],
'IntlRuleBasedBreakIterator::setText' => ['bool', 'text'=>'string'],
'IntlTimeZone::countEquivalentIDs' => ['int', 'zoneId'=>'string'],
'IntlTimeZone::createDefault' => ['IntlTimeZone'],
'IntlTimeZone::createEnumeration' => ['IntlIterator', 'countryOrRawOffset='=>'mixed'],
'IntlTimeZone::createTimeZone' => ['IntlTimeZone', 'zoneId'=>'string'],
'IntlTimeZone::createTimeZoneIDEnumeration' => ['IntlIterator', 'zoneType'=>'int', 'region='=>'string', 'rawOffset='=>'int'],
'IntlTimeZone::fromDateTimeZone' => ['IntlTimeZone', 'zoneId'=>'DateTimeZone'],
'IntlTimeZone::getCanonicalID' => ['string', 'zoneId'=>'string', '&w_isSystemID='=>'bool'],
'IntlTimeZone::getDisplayName' => ['string', 'isDaylight='=>'bool', 'style='=>'int', 'locale='=>'string'],
'IntlTimeZone::getDSTSavings' => ['int'],
'IntlTimeZone::getEquivalentID' => ['string', 'zoneId'=>'string', 'index'=>'int'],
'IntlTimeZone::getErrorCode' => ['int'],
'IntlTimeZone::getErrorMessage' => ['string'],
'IntlTimeZone::getGMT' => ['IntlTimeZone'],
'IntlTimeZone::getID' => ['string'],
'IntlTimeZone::getIDForWindowsID' => ['string', 'timezone'=>'string', 'region='=>'string'],
'IntlTimeZone::getOffset' => ['int', 'date'=>'float', 'local'=>'bool', '&w_rawOffset'=>'int', '&w_dstOffset'=>'int'],
'IntlTimeZone::getRawOffset' => ['int'],
'IntlTimeZone::getRegion' => ['string', 'zoneId'=>'string'],
'IntlTimeZone::getTZDataVersion' => ['string'],
'IntlTimeZone::getUnknown' => ['IntlTimeZone'],
'IntlTimeZone::getWindowsID' => ['string', 'timezone'=>'string'],
'IntlTimeZone::hasSameRules' => ['bool', 'otherTimeZone'=>'IntlTimeZone'],
'IntlTimeZone::toDateTimeZone' => ['DateTimeZone'],
'IntlTimeZone::useDaylightTime' => ['bool'],
'intltz_count_equivalent_ids' => ['int', 'zoneId'=>'string'],
'intltz_create_enumeration' => ['IntlIterator', 'countryOrRawOffset'=>'mixed'],
'intltz_create_time_zone' => ['IntlTimeZone', 'zoneId'=>'string'],
'intltz_from_date_time_zone' => ['IntlTimeZone', 'zoneId'=>'DateTimeZone'],
'intltz_get_canonical_id' => ['string', 'zoneId'=>'string', '&isSystemID'=>'bool'],
'intltz_get_display_name' => ['string', 'obj'=>'IntlTimeZone', 'isDaylight'=>'bool', 'style'=>'int', 'locale'=>'string'],
'intltz_get_dst_savings' => ['int', 'obj'=>'IntlTimeZone'],
'intltz_get_equivalent_id' => ['string', 'zoneId'=>'string', 'index'=>'int'],
'intltz_get_error_code' => ['int', 'obj'=>'IntlTimeZone'],
'intltz_get_error_message' => ['string', 'obj'=>'IntlTimeZone'],
'intltz_get_id' => ['string', 'obj'=>'IntlTimeZone'],
'intltz_get_offset' => ['int', 'obj'=>'IntlTimeZone', 'date'=>'float', 'local'=>'bool', '&rawOffset'=>'int', '&dstOffset'=>'int'],
'intltz_get_raw_offset' => ['int', 'obj'=>'IntlTimeZone'],
'intltz_get_tz_data_version' => ['string', 'obj'=>'IntlTimeZone'],
'intltz_getGMT' => ['IntlTimeZone'],
'intltz_has_same_rules' => ['bool', 'obj'=>'IntlTimeZone', 'otherTimeZone'=>'IntlTimeZone'],
'intltz_to_date_time_zone' => ['DateTimeZone', 'obj'=>''],
'intltz_use_daylight_time' => ['bool', 'obj'=>''],
'intlz_create_default' => ['IntlTimeZone'],
'intval' => ['int', 'var'=>'mixed', 'base='=>'int'],
'InvalidArgumentException::__clone' => ['void'],
'InvalidArgumentException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?InvalidArgumentException'],
'InvalidArgumentException::__toString' => ['string'],
'InvalidArgumentException::getCode' => ['int'],
'InvalidArgumentException::getFile' => ['string'],
'InvalidArgumentException::getLine' => ['int'],
'InvalidArgumentException::getMessage' => ['string'],
'InvalidArgumentException::getPrevious' => ['Throwable|InvalidArgumentException'],
'InvalidArgumentException::getTrace' => ['array'],
'InvalidArgumentException::getTraceAsString' => ['string'],
'ip2long' => ['int|false', 'ip_address'=>'string'],
'iptcembed' => ['array', 'iptcdata'=>'string', 'jpeg_file_name'=>'string', 'spool='=>'int'],
'iptcparse' => ['array|false', 'iptcdata'=>'string'],
'is_a' => ['bool', 'object_or_string'=>'object|string', 'class_name'=>'string', 'allow_string='=>'bool'],
'is_array' => ['bool', 'var'=>'mixed'],
'is_bool' => ['bool', 'var'=>'mixed'],
'is_callable' => ['bool', 'var'=>'mixed', 'syntax_only='=>'bool', '&w_callable_name='=>'string'],
'is_dir' => ['bool', 'filename'=>'string'],
'is_double' => ['bool', 'var'=>''],
'is_executable' => ['bool', 'filename'=>'string'],
'is_file' => ['bool', 'filename'=>'string'],
'is_finite' => ['bool', 'val'=>'float'],
'is_float' => ['bool', 'var'=>'mixed'],
'is_infinite' => ['bool', 'val'=>'float'],
'is_int' => ['bool', 'var'=>'mixed'],
'is_integer' => ['bool', 'var'=>''],
'is_iterable' => ['bool', 'var'=>'mixed'],
'is_link' => ['bool', 'filename'=>'string'],
'is_long' => ['bool', 'var'=>''],
'is_nan' => ['bool', 'val'=>'float'],
'is_null' => ['bool', 'var'=>'mixed'],
'is_numeric' => ['bool', 'value'=>'mixed'],
'is_object' => ['bool', 'var'=>'mixed'],
'is_readable' => ['bool', 'filename'=>'string'],
'is_real' => ['bool', 'var'=>''],
'is_resource' => ['bool', 'var'=>'mixed'],
'is_scalar' => ['bool', 'value'=>'mixed'],
'is_soap_fault' => ['bool', 'object'=>'mixed'],
'is_string' => ['bool', 'var'=>'mixed'],
'is_subclass_of' => ['bool', 'object_or_string'=>'mixed', 'class_name'=>'string', 'allow_string='=>'bool'],
'is_tainted' => ['bool', 'string'=>'string'],
'is_uploaded_file' => ['bool', 'path'=>'string'],
'is_writable' => ['bool', 'filename'=>'string'],
'is_writeable' => ['bool', 'filename'=>'string'],
'isset' => ['bool', 'var'=>'mixed', 'rest='=>'...mixed='],
'Iterator::current' => ['mixed'],
'Iterator::key' => ['int|string'],
'Iterator::next' => ['void'],
'Iterator::rewind' => ['void'],
'Iterator::valid' => ['bool'],
'iterator_apply' => ['int', 'it'=>'Traversable', 'function'=>'callable(mixed):bool', 'params='=>'array'],
'iterator_count' => ['int', 'it'=>'Traversable'],
'iterator_to_array' => ['array', 'it'=>'Traversable', 'use_keys='=>'bool'],
'IteratorAggregate::getIterator' => ['Traversable'],
'IteratorIterator::__construct' => ['void', 'it'=>'Traversable'],
'IteratorIterator::current' => ['mixed'],
'IteratorIterator::getInnerIterator' => ['Traversable'],
'IteratorIterator::key' => ['int|string|float|bool'],
'IteratorIterator::next' => ['void'],
'IteratorIterator::rewind' => ['void'],
'IteratorIterator::valid' => ['bool'],
'java_last_exception_clear' => [''],
'java_last_exception_get' => ['object'],
'java_reload' => ['array', 'new_jarpath'=>'new_jarpath'],
'java_require' => ['array', 'new_classpath'=>'new_classpath'],
'java_set_encoding' => ['array', 'encoding'=>'encoding'],
'java_set_ignore_case' => ['void', 'ignore'=>'ignore'],
'java_throw_exceptions' => ['void', 'throw'=>'throw'],
'JavaException::getCause' => ['object'],
'jddayofweek' => ['mixed', 'juliandaycount'=>'int', 'mode'=>'int'],
'jdmonthname' => ['string', 'juliandaycount'=>'int', 'mode'=>'int'],
'jdtofrench' => ['string', 'juliandaycount'=>'int'],
'jdtogregorian' => ['string', 'juliandaycount'=>'int'],
'jdtojewish' => ['string', 'juliandaycount'=>'int', 'hebrew='=>'bool', 'fl='=>'int'],
'jdtojulian' => ['string', 'juliandaycount'=>'int'],
'jdtounix' => ['int|false', 'jday'=>'int'],
'jewishtojd' => ['int', 'month'=>'int', 'day'=>'int', 'year'=>'int'],
'jobqueue_license_info' => ['array'],
'join' => ['string', 'glue'=>'string', 'pieces'=>'array'],
'join\'1' => ['string', 'pieces'=>'array'],
'jpeg2wbmp' => ['bool', 'jpegname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'],
'json_decode' => ['mixed', 'json'=>'string', 'assoc='=>'bool', 'depth='=>'int', 'options='=>'int'],
'json_encode' => ['string|false', 'data'=>'mixed', 'options='=>'int', 'depth='=>'int'],
'json_last_error' => ['int'],
'json_last_error_msg' => ['string'],
'JsonIncrementalParser::__construct' => ['void', 'depth'=>'', 'options'=>''],
'JsonIncrementalParser::get' => ['', 'options'=>''],
'JsonIncrementalParser::getError' => [''],
'JsonIncrementalParser::parse' => ['', 'json'=>''],
'JsonIncrementalParser::parseFile' => ['', 'filename'=>''],
'JsonIncrementalParser::reset' => [''],
'JsonSerializable::jsonSerialize' => ['mixed'],
'Judy::__construct' => ['void', 'judy_type'=>'int'],
'Judy::__destruct' => [''],
'Judy::byCount' => ['int', 'nth_index'=>'int'],
'Judy::count' => ['int', 'index_start='=>'int', 'index_end='=>'int'],
'Judy::first' => ['mixed', 'index='=>'mixed'],
'Judy::firstEmpty' => ['int', 'index='=>'mixed'],
'Judy::free' => ['int'],
'Judy::getType' => ['int'],
'Judy::last' => ['void', 'index='=>'string'],
'Judy::lastEmpty' => ['int', 'index='=>'int'],
'Judy::memoryUsage' => ['int'],
'Judy::next' => ['mixed', 'index'=>'mixed'],
'Judy::nextEmpty' => ['int', 'index'=>'int'],
'Judy::offsetExists' => ['bool', 'offset'=>'mixed'],
'Judy::offsetGet' => ['mixed', 'offset'=>'mixed'],
'Judy::offsetSet' => ['bool', 'offset'=>'mixed', 'value'=>'mixed'],
'Judy::offsetUnset' => ['bool', 'offset'=>'mixed'],
'Judy::prev' => ['mixed', 'index'=>'mixed'],
'Judy::prevEmpty' => ['int', 'index'=>'mixed'],
'Judy::size' => ['void'],
'judy_type' => ['int', 'array'=>'judy'],
'judy_version' => ['string'],
'juliantojd' => ['int', 'month'=>'int', 'day'=>'int', 'year'=>'int'],
'kadm5_chpass_principal' => ['bool', 'handle'=>'resource', 'principal'=>'string', 'password'=>'string'],
'kadm5_create_principal' => ['bool', 'handle'=>'resource', 'principal'=>'string', 'password='=>'string', 'options='=>'array'],
'kadm5_delete_principal' => ['bool', 'handle'=>'resource', 'principal'=>'string'],
'kadm5_destroy' => ['bool', 'handle'=>'resource'],
'kadm5_flush' => ['bool', 'handle'=>'resource'],
'kadm5_get_policies' => ['array', 'handle'=>'resource'],
'kadm5_get_principal' => ['array', 'handle'=>'resource', 'principal'=>'string'],
'kadm5_get_principals' => ['array', 'handle'=>'resource'],
'kadm5_init_with_password' => ['resource', 'admin_server'=>'string', 'realm'=>'string', 'principal'=>'string', 'password'=>'string'],
'kadm5_modify_principal' => ['bool', 'handle'=>'resource', 'principal'=>'string', 'options'=>'array'],
'key' => ['int|string|false', 'array_arg'=>'array'],
'key_exists' => ['bool', 'key'=>'string|int', 'search'=>'array'],
'krsort' => ['bool', '&rw_array_arg'=>'array', 'sort_flags='=>'int'],
'ksort' => ['bool', '&rw_array_arg'=>'array', 'sort_flags='=>'int'],
'KTaglib_ID3v2_AttachedPictureFrame::getDescription' => ['string'],
'KTaglib_ID3v2_AttachedPictureFrame::getMimeType' => ['string'],
'KTaglib_ID3v2_AttachedPictureFrame::getType' => ['int'],
'KTaglib_ID3v2_AttachedPictureFrame::savePicture' => ['bool', 'filename'=>'string'],
'KTaglib_ID3v2_AttachedPictureFrame::setMimeType' => ['string', 'type'=>'string'],
'KTaglib_ID3v2_AttachedPictureFrame::setPicture' => ['', 'filename'=>'string'],
'KTaglib_ID3v2_AttachedPictureFrame::setType' => ['', 'type'=>'int'],
'KTaglib_ID3v2_Frame::__toString' => ['string'],
'KTaglib_ID3v2_Frame::getSize' => ['int'],
'KTaglib_ID3v2_Tag::addFrame' => ['bool', 'frame'=>'ktaglib_id3v2_frame'],
'KTaglib_ID3v2_Tag::getFrameList' => ['array'],
'KTaglib_MPEG_AudioProperties::getBitrate' => ['int'],
'KTaglib_MPEG_AudioProperties::getChannels' => ['int'],
'KTaglib_MPEG_AudioProperties::getLayer' => ['int'],
'KTaglib_MPEG_AudioProperties::getLength' => ['int'],
'KTaglib_MPEG_AudioProperties::getSampleBitrate' => ['int'],
'KTaglib_MPEG_AudioProperties::getVersion' => ['int'],
'KTaglib_MPEG_AudioProperties::isCopyrighted' => ['bool'],
'KTaglib_MPEG_AudioProperties::isOriginal' => ['bool'],
'KTaglib_MPEG_AudioProperties::isProtectionEnabled' => ['bool'],
'KTaglib_MPEG_File::getAudioProperties' => ['KTaglib_MPEG_File'],
'KTaglib_MPEG_File::getID3v1Tag' => ['KTaglib_ID3v1_Tag', 'create='=>'bool'],
'KTaglib_MPEG_File::getID3v2Tag' => ['KTaglib_ID3v2_Tag', 'create='=>'bool'],
'KTaglib_Tag::getAlbum' => ['string'],
'KTaglib_Tag::getArtist' => ['string'],
'KTaglib_Tag::getComment' => ['string'],
'KTaglib_Tag::getGenre' => ['string'],
'KTaglib_Tag::getTitle' => ['string'],
'KTaglib_Tag::getTrack' => ['int'],
'KTaglib_Tag::getYear' => ['int'],
'KTaglib_Tag::isEmpty' => ['bool'],
'labelcacheObj::freeCache' => ['bool'],
'labelObj::__construct' => ['void'],
'labelObj::convertToString' => ['string'],
'labelObj::deleteStyle' => ['int', 'index'=>'int'],
'labelObj::free' => ['void'],
'labelObj::getBinding' => ['string', 'labelbinding'=>'mixed'],
'labelObj::getExpressionString' => ['string'],
'labelObj::getStyle' => ['styleObj', 'index'=>'int'],
'labelObj::getTextString' => ['string'],
'labelObj::moveStyleDown' => ['int', 'index'=>'int'],
'labelObj::moveStyleUp' => ['int', 'index'=>'int'],
'labelObj::removeBinding' => ['int', 'labelbinding'=>'mixed'],
'labelObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'labelObj::setBinding' => ['int', 'labelbinding'=>'mixed', 'value'=>'string'],
'labelObj::setExpression' => ['int', 'expression'=>'string'],
'labelObj::setText' => ['int', 'text'=>'string'],
'labelObj::updateFromString' => ['int', 'snippet'=>'string'],
'Lapack::eigenValues' => ['array', 'a'=>'array', 'left='=>'array', 'right='=>'array'],
'Lapack::identity' => ['array', 'n'=>'int'],
'Lapack::leastSquaresByFactorisation' => ['array', 'a'=>'array', 'b'=>'array'],
'Lapack::leastSquaresBySVD' => ['array', 'a'=>'array', 'b'=>'array'],
'Lapack::pseudoInverse' => ['array', 'a'=>'array'],
'Lapack::singularValues' => ['array', 'a'=>'array'],
'Lapack::solveLinearEquation' => ['array', 'a'=>'array', 'b'=>'array'],
'layerObj::addFeature' => ['int', 'shape'=>'shapeObj'],
'layerObj::applySLD' => ['int', 'sldxml'=>'string', 'namedlayer'=>'string'],
'layerObj::applySLDURL' => ['int', 'sldurl'=>'string', 'namedlayer'=>'string'],
'layerObj::clearProcessing' => ['void'],
'layerObj::close' => ['void'],
'layerObj::convertToString' => ['string'],
'layerObj::draw' => ['int', 'image'=>'imageObj'],
'layerObj::drawQuery' => ['int', 'image'=>'imageObj'],
'layerObj::free' => ['void'],
'layerObj::generateSLD' => ['string'],
'layerObj::getClass' => ['classObj', 'classIndex'=>'int'],
'layerObj::getClassIndex' => ['int', 'shape'=>'', 'classgroup'=>'', 'numclasses'=>''],
'layerObj::getExtent' => ['rectObj'],
'layerObj::getFilterString' => ['string'],
'layerObj::getGridIntersectionCoordinates' => ['array'],
'layerObj::getItems' => ['array'],
'layerObj::getMetaData' => ['int', 'name'=>'string'],
'layerObj::getNumResults' => ['int'],
'layerObj::getProcessing' => ['array'],
'layerObj::getProjection' => ['string'],
'layerObj::getResult' => ['resultObj', 'index'=>'int'],
'layerObj::getResultsBounds' => ['rectObj'],
'layerObj::getShape' => ['shapeObj', 'result'=>'resultObj'],
'layerObj::getWMSFeatureInfoURL' => ['string', 'clickX'=>'int', 'clickY'=>'int', 'featureCount'=>'int', 'infoFormat'=>'string'],
'layerObj::isVisible' => ['bool'],
'layerObj::moveclassdown' => ['int', 'index'=>'int'],
'layerObj::moveclassup' => ['int', 'index'=>'int'],
'layerObj::ms_newLayerObj' => ['layerObj', 'map'=>'MapObj', 'layer'=>'layerObj'],
'layerObj::nextShape' => ['shapeObj'],
'layerObj::open' => ['int'],
'layerObj::queryByAttributes' => ['int', 'qitem'=>'string', 'qstring'=>'string', 'mode'=>'int'],
'layerObj::queryByFeatures' => ['int', 'slayer'=>'int'],
'layerObj::queryByPoint' => ['int', 'point'=>'pointObj', 'mode'=>'int', 'buffer'=>'float'],
'layerObj::queryByRect' => ['int', 'rect'=>'rectObj'],
'layerObj::queryByShape' => ['int', 'shape'=>'shapeObj'],
'layerObj::removeClass' => ['classObj', 'index'=>'int'],
'layerObj::removeMetaData' => ['int', 'name'=>'string'],
'layerObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'layerObj::setConnectionType' => ['int', 'connectiontype'=>'int', 'plugin_library'=>'string'],
'layerObj::setFilter' => ['int', 'expression'=>'string'],
'layerObj::setMetaData' => ['int', 'name'=>'string', 'value'=>'string'],
'layerObj::setProjection' => ['int', 'proj_params'=>'string'],
'layerObj::setWKTProjection' => ['int', 'proj_params'=>'string'],
'layerObj::updateFromString' => ['int', 'snippet'=>'string'],
'lcfirst' => ['string', 'str'=>'string'],
'lcg_value' => ['float'],
'lchgrp' => ['bool', 'filename'=>'string', 'group'=>'string|int'],
'lchown' => ['bool', 'filename'=>'string', 'user'=>'string|int'],
'ldap_8859_to_t61' => ['string', 'value'=>'string'],
'ldap_add' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array'],
'ldap_bind' => ['bool', 'link_identifier'=>'resource', 'dn='=>'string', 'password='=>'string'],
'ldap_close' => ['bool', 'link_identifier'=>'resource'],
'ldap_compare' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'attr'=>'string', 'value'=>'string'],
'ldap_connect' => ['resource', 'host='=>'string', 'port='=>'int', 'wallet='=>'string', 'wallet_passwd='=>'string', 'authmode='=>'int'],
'ldap_control_paged_result' => ['bool', 'link_identifier'=>'resource', 'pagesize'=>'int', 'iscritical'=>'bool', 'cookie'=>'string'],
'ldap_control_paged_result_response' => ['bool', 'link_identifier'=>'resource', 'result_identifier'=>'resource', '&w_cookie'=>'string', '&w_estimated'=>'int'],
'ldap_count_entries' => ['int', 'link_identifier'=>'resource', 'result'=>'resource'],
'ldap_delete' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string'],
'ldap_dn2ufn' => ['string', 'dn'=>'string'],
'ldap_err2str' => ['string', 'errno'=>'int'],
'ldap_errno' => ['int', 'link_identifier'=>'resource'],
'ldap_error' => ['string', 'link_identifier'=>'resource'],
'ldap_escape' => ['string', 'value'=>'string', 'ignore='=>'string', 'flags='=>'int'],
'ldap_exop' => ['mixed', 'link'=>'resource', 'reqoid'=>'string', 'reqdata='=>'string', 'servercontrols='=>'array', 'retdata='=>'string', 'retoid='=>'string'],
'ldap_exop_passwd' => ['mixed', 'link'=>'resource', 'user='=>'string', 'oldpw='=>'string', 'newpw='=>'string', 'serverctrls='=>'array'],
'ldap_exop_refresh' => ['int', 'link'=>'resource', 'dn'=>'string', 'ttl'=>'int'],
'ldap_exop_whoami' => ['string', 'link'=>'resource'],
'ldap_explode_dn' => ['array', 'dn'=>'string', 'with_attrib'=>'int'],
'ldap_first_attribute' => ['string', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource'],
'ldap_first_entry' => ['resource', 'link_identifier'=>'resource', 'result_identifier'=>'resource'],
'ldap_first_reference' => ['resource', 'link_identifier'=>'resource', 'result_identifier'=>'resource'],
'ldap_free_result' => ['bool', 'result_identifier'=>'resource'],
'ldap_get_attributes' => ['array', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource'],
'ldap_get_dn' => ['string', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource'],
'ldap_get_entries' => ['array', 'link_identifier'=>'resource', 'result_identifier'=>'resource'],
'ldap_get_option' => ['bool', 'link_identifier'=>'resource', 'option'=>'int', '&w_retval'=>'mixed'],
'ldap_get_values' => ['array', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource', 'attribute'=>'string'],
'ldap_get_values_len' => ['array', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource', 'attribute'=>'string'],
'ldap_list' => ['resource', 'link'=>'resource|array', 'base_dn'=>'string', 'filter'=>'string', 'attrs='=>'array', 'attrsonly='=>'int', 'sizelimit='=>'int', 'timelimit='=>'int', 'deref='=>'int'],
'ldap_mod_add' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array'],
'ldap_mod_del' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array'],
'ldap_mod_replace' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array'],
'ldap_modify' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'entry'=>'array'],
'ldap_modify_batch' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'modifs'=>'array'],
'ldap_next_attribute' => ['string', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource'],
'ldap_next_entry' => ['resource', 'link_identifier'=>'resource', 'result_entry_identifier'=>'resource'],
'ldap_next_reference' => ['resource', 'link_identifier'=>'resource', 'reference_entry_identifier'=>'resource'],
'ldap_parse_exop' => ['bool', 'link'=>'resource', 'result'=>'resource', 'retdata='=>'string', 'retoid='=>'string'],
'ldap_parse_reference' => ['bool', 'link_identifier'=>'resource', 'reference_entry_identifier'=>'resource', 'referrals'=>'array'],
'ldap_parse_result' => ['bool', 'link_identifier'=>'resource', 'result'=>'resource', 'errcode'=>'int', 'matcheddn='=>'string', 'errmsg='=>'string', 'referrals='=>'array'],
'ldap_read' => ['resource', 'link'=>'resource|array', 'base_dn'=>'string', 'filter'=>'string', 'attrs='=>'array', 'attrsonly='=>'int', 'sizelimit='=>'int', 'timelimit='=>'int', 'deref='=>'int'],
'ldap_rename' => ['bool', 'link_identifier'=>'resource', 'dn'=>'string', 'newrdn'=>'string', 'newparent'=>'string', 'deleteoldrdn'=>'bool'],
'ldap_sasl_bind' => ['bool', 'link_identifier'=>'resource', 'binddn='=>'string', 'password='=>'string', 'sasl_mech='=>'string', 'sasl_realm='=>'string', 'sasl_authc_id='=>'string', 'sasl_authz_id='=>'string', 'props='=>'string'],
'ldap_search' => ['resource', 'link_identifier'=>'resource|array', 'base_dn'=>'string', 'filter'=>'string', 'attrs='=>'array', 'attrsonly='=>'int', 'sizelimit='=>'int', 'timelimit='=>'int', 'deref='=>'int'],
'ldap_set_option' => ['bool', 'link_identifier'=>'resource', 'option'=>'int', 'newval'=>'mixed'],
'ldap_set_rebind_proc' => ['bool', 'link_identifier'=>'resource', 'callback'=>'string'],
'ldap_sort' => ['bool', 'link_identifier'=>'resource', 'result_identifier'=>'resource', 'sortfilter'=>'string'],
'ldap_start_tls' => ['bool', 'link_identifier'=>'resource'],
'ldap_t61_to_8859' => ['string', 'value'=>'string'],
'ldap_unbind' => ['bool', 'link_identifier'=>'resource'],
'leak' => ['', 'num_bytes'=>'int'],
'leak_variable' => ['', 'variable'=>'', 'leak_data'=>'bool'],
'legendObj::convertToString' => ['string'],
'legendObj::free' => ['void'],
'legendObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'legendObj::updateFromString' => ['int', 'snippet'=>'string'],
'LengthException::__clone' => ['void'],
'LengthException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?LengthException'],
'LengthException::__toString' => ['string'],
'LengthException::getCode' => ['int'],
'LengthException::getFile' => ['string'],
'LengthException::getLine' => ['int'],
'LengthException::getMessage' => ['string'],
'LengthException::getPrevious' => ['Throwable|LengthException'],
'LengthException::getTrace' => ['array'],
'LengthException::getTraceAsString' => ['string'],
'levenshtein' => ['int', 'str1'=>'string', 'str2'=>'string'],
'levenshtein\'1' => ['int', 'str1'=>'string', 'str2'=>'string', 'cost_ins'=>'int', 'cost_rep'=>'int', 'cost_del'=>'int'],
'libxml_clear_errors' => ['void'],
'libxml_disable_entity_loader' => ['bool', 'disable='=>'bool'],
'libxml_get_errors' => ['array<int,libXMLError>'],
'libxml_get_last_error' => ['object'],
'libxml_set_external_entity_loader' => ['bool', 'resolver_function'=>'callable'],
'libxml_set_streams_context' => ['void', 'streams_context'=>'resource'],
'libxml_use_internal_errors' => ['bool', 'use_errors='=>'bool'],
'LimitIterator::__construct' => ['void', 'iterator'=>'Iterator', 'offset='=>'int', 'count='=>'int'],
'LimitIterator::current' => ['mixed'],
'LimitIterator::getInnerIterator' => ['Iterator'],
'LimitIterator::getPosition' => ['int'],
'LimitIterator::key' => ['mixed'],
'LimitIterator::next' => ['void'],
'LimitIterator::rewind' => ['void'],
'LimitIterator::seek' => ['int', 'position'=>'int'],
'LimitIterator::valid' => ['bool'],
'lineObj::__construct' => ['void'],
'lineObj::add' => ['int', 'point'=>'pointObj'],
'lineObj::addXY' => ['int', 'x'=>'float', 'y'=>'float', 'm'=>'float'],
'lineObj::addXYZ' => ['int', 'x'=>'float', 'y'=>'float', 'z'=>'float', 'm'=>'float'],
'lineObj::ms_newLineObj' => ['lineObj'],
'lineObj::point' => ['pointObj', 'i'=>'int'],
'lineObj::project' => ['int', 'in'=>'projectionObj', 'out'=>'projectionObj'],
'link' => ['int', 'target'=>'string', 'link'=>'string'],
'linkinfo' => ['int', 'filename'=>'string'],
'litespeed_request_headers' => ['array'],
'litespeed_response_headers' => ['array'],
'Locale::acceptFromHttp' => ['string|false', 'header'=>'string'],
'Locale::canonicalize' => ['string', 'locale'=>'string'],
'Locale::composeLocale' => ['string', 'subtags'=>'array'],
'Locale::filterMatches' => ['bool', 'langtag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'],
'Locale::getAllVariants' => ['array', 'locale'=>'string'],
'Locale::getDefault' => ['string'],
'Locale::getDisplayLanguage' => ['string', 'locale'=>'string', 'in_locale='=>'string'],
'Locale::getDisplayName' => ['string', 'locale'=>'string', 'in_locale='=>'string'],
'Locale::getDisplayRegion' => ['string', 'locale'=>'string', 'in_locale='=>'string'],
'Locale::getDisplayScript' => ['string', 'locale'=>'string', 'in_locale='=>'string'],
'Locale::getDisplayVariant' => ['string', 'locale'=>'string', 'in_locale='=>'string'],
'Locale::getKeywords' => ['array|false', 'locale'=>'string'],
'Locale::getPrimaryLanguage' => ['string', 'locale'=>'string'],
'Locale::getRegion' => ['string', 'locale'=>'string'],
'Locale::getScript' => ['string', 'locale'=>'string'],
'Locale::lookup' => ['string', 'langtag'=>'array', 'locale'=>'string', 'canonicalize='=>'bool', 'default='=>'string'],
'Locale::parseLocale' => ['array', 'locale'=>'string'],
'Locale::setDefault' => ['bool', 'locale'=>'string'],
'locale_accept_from_http' => ['string|false', 'header'=>'string'],
'locale_canonicalize' => ['', 'arg1'=>''],
'locale_compose' => ['string|false', 'subtags'=>'array'],
'locale_filter_matches' => ['bool', 'langtag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'],
'locale_get_all_variants' => ['array', 'locale'=>'string'],
'locale_get_default' => ['string'],
'locale_get_display_language' => ['string', 'locale'=>'string', 'in_locale='=>'string'],
'locale_get_display_name' => ['string', 'locale'=>'string', 'in_locale='=>'string'],
'locale_get_display_region' => ['string', 'locale'=>'string', 'in_locale='=>'string'],
'locale_get_display_script' => ['string', 'locale'=>'string', 'in_locale='=>'string'],
'locale_get_display_variant' => ['string', 'locale'=>'string', 'in_locale='=>'string'],
'locale_get_keywords' => ['array|false', 'locale'=>'string'],
'locale_get_primary_language' => ['string', 'locale'=>'string'],
'locale_get_region' => ['string', 'locale'=>'string'],
'locale_get_script' => ['string', 'locale'=>'string'],
'locale_lookup' => ['string', 'langtag'=>'array', 'locale'=>'string', 'canonicalize='=>'bool', 'default='=>'string'],
'locale_parse' => ['array', 'locale'=>'string'],
'locale_set_default' => ['bool', 'locale'=>'string'],
'localeconv' => ['array'],
'localtime' => ['array', 'timestamp='=>'int', 'associative_array='=>'bool'],
'log' => ['float', 'number'=>'float', 'base='=>'float'],
'log10' => ['float', 'number'=>'float'],
'log1p' => ['float', 'number'=>'float'],
'LogicException::__clone' => ['void'],
'LogicException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?LogicException'],
'LogicException::__toString' => ['string'],
'LogicException::getCode' => ['int'],
'LogicException::getFile' => ['string'],
'LogicException::getLine' => ['int'],
'LogicException::getMessage' => ['string'],
'LogicException::getPrevious' => ['Throwable|LogicException'],
'LogicException::getTrace' => ['array'],
'LogicException::getTraceAsString' => ['string'],
'long2ip' => ['string', 'proper_address'=>'int'],
'lstat' => ['array', 'filename'=>'string'],
'ltrim' => ['string', 'str'=>'string', 'character_mask='=>'string'],
'Lua::__call' => ['mixed', 'lua_func'=>'callable', 'args='=>'array', 'use_self='=>'int'],
'Lua::__construct' => ['void', 'lua_script_file'=>'string'],
'Lua::assign' => ['mixed', 'name'=>'string', 'value'=>'string'],
'Lua::call' => ['mixed', 'lua_func'=>'callable', 'args='=>'array', 'use_self='=>'int'],
'Lua::eval' => ['mixed', 'statements'=>'string'],
'Lua::getVersion' => ['string'],
'Lua::include' => ['mixed', 'file'=>'string'],
'Lua::registerCallback' => ['mixed', 'name'=>'string', 'function'=>'callable'],
'LuaClosure::__invoke' => ['void', 'arg'=>'mixed', '...args='=>'mixed'],
'lzf_compress' => ['string', 'data'=>'string'],
'lzf_decompress' => ['string', 'data'=>'string'],
'lzf_optimized_for' => ['int'],
'm_checkstatus' => ['int', 'conn'=>'resource', 'identifier'=>'int'],
'm_completeauthorizations' => ['int', 'conn'=>'resource', 'array'=>'int'],
'm_connect' => ['int', 'conn'=>'resource'],
'm_connectionerror' => ['string', 'conn'=>'resource'],
'm_deletetrans' => ['bool', 'conn'=>'resource', 'identifier'=>'int'],
'm_destroyconn' => ['bool', 'conn'=>'resource'],
'm_destroyengine' => ['void'],
'm_getcell' => ['string', 'conn'=>'resource', 'identifier'=>'int', 'column'=>'string', 'row'=>'int'],
'm_getcellbynum' => ['string', 'conn'=>'resource', 'identifier'=>'int', 'column'=>'int', 'row'=>'int'],
'm_getcommadelimited' => ['string', 'conn'=>'resource', 'identifier'=>'int'],
'm_getheader' => ['string', 'conn'=>'resource', 'identifier'=>'int', 'column_num'=>'int'],
'm_initconn' => ['resource'],
'm_initengine' => ['int', 'location'=>'string'],
'm_iscommadelimited' => ['int', 'conn'=>'resource', 'identifier'=>'int'],
'm_maxconntimeout' => ['bool', 'conn'=>'resource', 'secs'=>'int'],
'm_monitor' => ['int', 'conn'=>'resource'],
'm_numcolumns' => ['int', 'conn'=>'resource', 'identifier'=>'int'],
'm_numrows' => ['int', 'conn'=>'resource', 'identifier'=>'int'],
'm_parsecommadelimited' => ['int', 'conn'=>'resource', 'identifier'=>'int'],
'm_responsekeys' => ['array', 'conn'=>'resource', 'identifier'=>'int'],
'm_responseparam' => ['string', 'conn'=>'resource', 'identifier'=>'int', 'key'=>'string'],
'm_returnstatus' => ['int', 'conn'=>'resource', 'identifier'=>'int'],
'm_setblocking' => ['int', 'conn'=>'resource', 'tf'=>'int'],
'm_setdropfile' => ['int', 'conn'=>'resource', 'directory'=>'string'],
'm_setip' => ['int', 'conn'=>'resource', 'host'=>'string', 'port'=>'int'],
'm_setssl' => ['int', 'conn'=>'resource', 'host'=>'string', 'port'=>'int'],
'm_setssl_cafile' => ['int', 'conn'=>'resource', 'cafile'=>'string'],
'm_setssl_files' => ['int', 'conn'=>'resource', 'sslkeyfile'=>'string', 'sslcertfile'=>'string'],
'm_settimeout' => ['int', 'conn'=>'resource', 'seconds'=>'int'],
'm_sslcert_gen_hash' => ['string', 'filename'=>'string'],
'm_transactionssent' => ['int', 'conn'=>'resource'],
'm_transinqueue' => ['int', 'conn'=>'resource'],
'm_transkeyval' => ['int', 'conn'=>'resource', 'identifier'=>'int', 'key'=>'string', 'value'=>'string'],
'm_transnew' => ['int', 'conn'=>'resource'],
'm_transsend' => ['int', 'conn'=>'resource', 'identifier'=>'int'],
'm_uwait' => ['int', 'microsecs'=>'int'],
'm_validateidentifier' => ['int', 'conn'=>'resource', 'tf'=>'int'],
'm_verifyconnection' => ['bool', 'conn'=>'resource', 'tf'=>'int'],
'm_verifysslcert' => ['bool', 'conn'=>'resource', 'tf'=>'int'],
'magic_quotes_runtime' => ['', 'new_setting'=>''],
'mail' => ['bool', 'to'=>'string', 'subject'=>'string', 'message'=>'string', 'additional_headers='=>'string', 'additional_parameters='=>'string'],
'mailparse_determine_best_xfer_encoding' => ['string', 'fp'=>'resource'],
'mailparse_msg_create' => ['resource'],
'mailparse_msg_extract_part' => ['void', 'mimemail'=>'resource', 'msgbody'=>'string', 'callbackfunc='=>'callable'],
'mailparse_msg_extract_part_file' => ['string', 'mimemail'=>'resource', 'filename'=>'mixed', 'callbackfunc='=>'callable'],
'mailparse_msg_extract_whole_part_file' => ['string', 'mimemail'=>'resource', 'filename'=>'string', 'callbackfunc='=>'callable'],
'mailparse_msg_free' => ['bool', 'mimemail'=>'resource'],
'mailparse_msg_get_part' => ['resource', 'mimemail'=>'resource', 'mimesection'=>'string'],
'mailparse_msg_get_part_data' => ['array', 'mimemail'=>'resource'],
'mailparse_msg_get_structure' => ['array', 'mimemail'=>'resource'],
'mailparse_msg_parse' => ['bool', 'mimemail'=>'resource', 'data'=>'string'],
'mailparse_msg_parse_file' => ['resource', 'filename'=>'string'],
'mailparse_rfc822_parse_addresses' => ['array', 'addresses'=>'string'],
'mailparse_stream_encode' => ['bool', 'sourcefp'=>'resource', 'destfp'=>'resource', 'encoding'=>'string'],
'mailparse_uudecode_all' => ['array', 'fp'=>'resource'],
'mapObj::__construct' => ['void', 'map_file_name'=>'string', 'new_map_path'=>'string'],
'mapObj::appendOutputFormat' => ['int', 'outputFormat'=>'outputformatObj'],
'mapObj::applyconfigoptions' => ['int'],
'mapObj::applySLD' => ['int', 'sldxml'=>'string'],
'mapObj::applySLDURL' => ['int', 'sldurl'=>'string'],
'mapObj::convertToString' => ['string'],
'mapObj::draw' => ['imageObj'],
'mapObj::drawLabelCache' => ['int', 'image'=>'imageObj'],
'mapObj::drawLegend' => ['imageObj'],
'mapObj::drawQuery' => ['imageObj'],
'mapObj::drawReferenceMap' => ['imageObj'],
'mapObj::drawScaleBar' => ['imageObj'],
'mapObj::embedLegend' => ['int', 'image'=>'imageObj'],
'mapObj::embedScalebar' => ['int', 'image'=>'imageObj'],
'mapObj::free' => ['void'],
'mapObj::generateSLD' => ['string'],
'mapObj::getAllGroupNames' => ['array'],
'mapObj::getAllLayerNames' => ['array'],
'mapObj::getColorbyIndex' => ['colorObj', 'iCloIndex'=>'int'],
'mapObj::getConfigOption' => ['string', 'key'=>'string'],
'mapObj::getLabel' => ['labelcacheMemberObj', 'index'=>'int'],
'mapObj::getLayer' => ['layerObj', 'index'=>'int'],
'mapObj::getLayerByName' => ['layerObj', 'layer_name'=>'string'],
'mapObj::getLayersDrawingOrder' => ['array'],
'mapObj::getLayersIndexByGroup' => ['array', 'groupname'=>'string'],
'mapObj::getMetaData' => ['int', 'name'=>'string'],
'mapObj::getNumSymbols' => ['int'],
'mapObj::getOutputFormat' => ['outputformatObj', 'index'=>'int'],
'mapObj::getProjection' => ['string'],
'mapObj::getSymbolByName' => ['int', 'symbol_name'=>'string'],
'mapObj::getSymbolObjectById' => ['symbolObj', 'symbolid'=>'int'],
'mapObj::loadMapContext' => ['int', 'filename'=>'string', 'unique_layer_name'=>'bool'],
'mapObj::loadOWSParameters' => ['int', 'request'=>'OwsrequestObj', 'version'=>'string'],
'mapObj::moveLayerDown' => ['int', 'layerindex'=>'int'],
'mapObj::moveLayerUp' => ['int', 'layerindex'=>'int'],
'mapObj::ms_newMapObjFromString' => ['MapObj', 'map_file_string'=>'string', 'new_map_path'=>'string'],
'mapObj::offsetExtent' => ['int', 'x'=>'float', 'y'=>'float'],
'mapObj::owsDispatch' => ['int', 'request'=>'OwsrequestObj'],
'mapObj::prepareImage' => ['imageObj'],
'mapObj::prepareQuery' => ['void'],
'mapObj::processLegendTemplate' => ['string', 'params'=>'array'],
'mapObj::processQueryTemplate' => ['string', 'params'=>'array', 'generateimages'=>'bool'],
'mapObj::processTemplate' => ['string', 'params'=>'array', 'generateimages'=>'bool'],
'mapObj::queryByFeatures' => ['int', 'slayer'=>'int'],
'mapObj::queryByIndex' => ['int', 'layerindex'=>'', 'tileindex'=>'', 'shapeindex'=>'', 'addtoquery'=>''],
'mapObj::queryByPoint' => ['int', 'point'=>'pointObj', 'mode'=>'int', 'buffer'=>'float'],
'mapObj::queryByRect' => ['int', 'rect'=>'rectObj'],
'mapObj::queryByShape' => ['int', 'shape'=>'shapeObj'],
'mapObj::removeLayer' => ['layerObj', 'nIndex'=>'int'],
'mapObj::removeMetaData' => ['int', 'name'=>'string'],
'mapObj::removeOutputFormat' => ['int', 'name'=>'string'],
'mapObj::save' => ['int', 'filename'=>'string'],
'mapObj::saveMapContext' => ['int', 'filename'=>'string'],
'mapObj::saveQuery' => ['int', 'filename'=>'string', 'results'=>'int'],
'mapObj::scaleExtent' => ['int', 'zoomfactor'=>'float', 'minscaledenom'=>'float', 'maxscaledenom'=>'float'],
'mapObj::selectOutputFormat' => ['int', 'type'=>'string'],
'mapObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'mapObj::setCenter' => ['int', 'center'=>'pointObj'],
'mapObj::setConfigOption' => ['int', 'key'=>'string', 'value'=>'string'],
'mapObj::setExtent' => ['void', 'minx'=>'float', 'miny'=>'float', 'maxx'=>'float', 'maxy'=>'float'],
'mapObj::setFontSet' => ['int', 'fileName'=>'string'],
'mapObj::setMetaData' => ['int', 'name'=>'string', 'value'=>'string'],
'mapObj::setProjection' => ['int', 'proj_params'=>'string', 'bSetUnitsAndExtents'=>'bool'],
'mapObj::setRotation' => ['int', 'rotation_angle'=>'float'],
'mapObj::setSize' => ['int', 'width'=>'int', 'height'=>'int'],
'mapObj::setSymbolSet' => ['int', 'fileName'=>'string'],
'mapObj::setWKTProjection' => ['int', 'proj_params'=>'string', 'bSetUnitsAndExtents'=>'bool'],
'mapObj::zoomPoint' => ['int', 'nZoomFactor'=>'int', 'oPixelPos'=>'pointObj', 'nImageWidth'=>'int', 'nImageHeight'=>'int', 'oGeorefExt'=>'rectObj'],
'mapObj::zoomRectangle' => ['int', 'oPixelExt'=>'rectObj', 'nImageWidth'=>'int', 'nImageHeight'=>'int', 'oGeorefExt'=>'rectObj'],
'mapObj::zoomScale' => ['int', 'nScaleDenom'=>'float', 'oPixelPos'=>'pointObj', 'nImageWidth'=>'int', 'nImageHeight'=>'int', 'oGeorefExt'=>'rectObj', 'oMaxGeorefExt'=>'rectObj'],
'max' => ['', 'arg1'=>'array'],
'max\'1' => ['', 'arg1'=>'', 'arg2'=>'', '...args='=>''],
'maxdb::__construct' => ['void', 'host='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'],
'maxdb::affected_rows' => ['int', 'link'=>''],
'maxdb::auto_commit' => ['bool', 'link'=>'', 'mode'=>'bool'],
'maxdb::change_user' => ['bool', 'link'=>'', 'user'=>'string', 'password'=>'string', 'database'=>'string'],
'maxdb::character_set_name' => ['string', 'link'=>''],
'maxdb::close' => ['bool', 'link'=>''],
'maxdb::commit' => ['bool', 'link'=>''],
'maxdb::disable_reads_from_master' => ['', 'link'=>''],
'maxdb::errno' => ['int', 'link'=>''],
'maxdb::error' => ['string', 'link'=>''],
'maxdb::field_count' => ['int', 'link'=>''],
'maxdb::get_host_info' => ['string', 'link'=>''],
'maxdb::info' => ['string', 'link'=>''],
'maxdb::insert_id' => ['', 'link'=>''],
'maxdb::kill' => ['bool', 'link'=>'', 'processid'=>'int'],
'maxdb::more_results' => ['bool', 'link'=>''],
'maxdb::multi_query' => ['bool', 'link'=>'', 'query'=>'string'],
'maxdb::next_result' => ['bool', 'link'=>''],
'maxdb::num_rows' => ['int', 'result'=>''],
'maxdb::options' => ['bool', 'link'=>'', 'option'=>'int', 'value'=>''],
'maxdb::ping' => ['bool', 'link'=>''],
'maxdb::prepare' => ['maxdb_stmt', 'link'=>'', 'query'=>'string'],
'maxdb::protocol_version' => ['string', 'link'=>''],
'maxdb::query' => ['', 'link'=>'', 'query'=>'string', 'resultmode='=>'int'],
'maxdb::real_connect' => ['bool', 'link'=>'', 'hostname='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'],
'maxdb::real_escape_string' => ['string', 'link'=>'', 'escapestr'=>'string'],
'maxdb::real_query' => ['bool', 'link'=>'', 'query'=>'string'],
'maxdb::rollback' => ['bool', 'link'=>''],
'maxdb::rpl_query_type' => ['int', 'link'=>''],
'maxdb::select_db' => ['bool', 'link'=>'', 'dbname'=>'string'],
'maxdb::send_query' => ['bool', 'link'=>'', 'query'=>'string'],
'maxdb::server_info' => ['string', 'link'=>''],
'maxdb::server_version' => ['int', 'link'=>''],
'maxdb::sqlstate' => ['string', 'link'=>''],
'maxdb::ssl_set' => ['bool', 'link'=>'', 'key'=>'string', 'cert'=>'string', 'ca'=>'string', 'capath'=>'string', 'cipher'=>'string'],
'maxdb::stat' => ['string', 'link'=>''],
'maxdb::stmt_init' => ['object', 'link'=>''],
'maxdb::store_result' => ['bool', 'link'=>''],
'maxdb::thread_id' => ['int', 'link'=>''],
'maxdb::use_result' => ['resource', 'link'=>''],
'maxdb::warning_count' => ['int', 'link'=>''],
'maxdb_affected_rows' => ['int', 'link'=>'resource'],
'maxdb_autocommit' => ['bool', 'link'=>'', 'mode'=>'bool'],
'maxdb_change_user' => ['bool', 'link'=>'', 'user'=>'string', 'password'=>'string', 'database'=>'string'],
'maxdb_character_set_name' => ['string', 'link'=>''],
'maxdb_close' => ['bool', 'link'=>''],
'maxdb_commit' => ['bool', 'link'=>''],
'maxdb_connect' => ['resource', 'host='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'],
'maxdb_connect_errno' => ['int'],
'maxdb_connect_error' => ['string'],
'maxdb_data_seek' => ['bool', 'result'=>'', 'offset'=>'int'],
'maxdb_debug' => ['void', 'debug'=>'string'],
'maxdb_disable_reads_from_master' => ['', 'link'=>''],
'maxdb_disable_rpl_parse' => ['bool', 'link'=>'resource'],
'maxdb_dump_debug_info' => ['bool', 'link'=>'resource'],
'maxdb_embedded_connect' => ['resource', 'dbname='=>'string'],
'maxdb_enable_reads_from_master' => ['bool', 'link'=>'resource'],
'maxdb_enable_rpl_parse' => ['bool', 'link'=>'resource'],
'maxdb_errno' => ['int', 'link'=>'resource'],
'maxdb_error' => ['string', 'link'=>'resource'],
'maxdb_fetch_array' => ['', 'result'=>'', 'resulttype='=>'int'],
'maxdb_fetch_assoc' => ['array', 'result'=>''],
'maxdb_fetch_field' => ['', 'result'=>''],
'maxdb_fetch_field_direct' => ['', 'result'=>'', 'fieldnr'=>'int'],
'maxdb_fetch_fields' => ['', 'result'=>''],
'maxdb_fetch_lengths' => ['array', 'result'=>'resource'],
'maxdb_fetch_object' => ['object', 'result'=>'object'],
'maxdb_fetch_row' => ['', 'result'=>''],
'maxdb_field_count' => ['int', 'link'=>''],
'maxdb_field_seek' => ['bool', 'result'=>'', 'fieldnr'=>'int'],
'maxdb_field_tell' => ['int', 'result'=>'resource'],
'maxdb_free_result' => ['', 'result'=>''],
'maxdb_get_client_info' => ['string'],
'maxdb_get_client_version' => ['int'],
'maxdb_get_host_info' => ['string', 'link'=>'resource'],
'maxdb_get_proto_info' => ['string', 'link'=>'resource'],
'maxdb_get_server_info' => ['string', 'link'=>'resource'],
'maxdb_get_server_version' => ['int', 'link'=>'resource'],
'maxdb_info' => ['string', 'link'=>'resource'],
'maxdb_init' => ['resource'],
'maxdb_insert_id' => ['mixed', 'link'=>'resource'],
'maxdb_kill' => ['bool', 'link'=>'', 'processid'=>'int'],
'maxdb_master_query' => ['bool', 'link'=>'resource', 'query'=>'string'],
'maxdb_more_results' => ['bool', 'link'=>'resource'],
'maxdb_multi_query' => ['bool', 'link'=>'', 'query'=>'string'],
'maxdb_next_result' => ['bool', 'link'=>'resource'],
'maxdb_num_fields' => ['int', 'result'=>'resource'],
'maxdb_num_rows' => ['int', 'result'=>'resource'],
'maxdb_options' => ['bool', 'link'=>'', 'option'=>'int', 'value'=>''],
'maxdb_ping' => ['bool', 'link'=>''],
'maxdb_prepare' => ['maxdb_stmt', 'link'=>'', 'query'=>'string'],
'maxdb_query' => ['', 'link'=>'', 'query'=>'string', 'resultmode='=>'int'],
'maxdb_real_connect' => ['bool', 'link'=>'', 'hostname='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'],
'maxdb_real_escape_string' => ['string', 'link'=>'', 'escapestr'=>'string'],
'maxdb_real_query' => ['bool', 'link'=>'', 'query'=>'string'],
'maxdb_report' => ['bool', 'flags'=>'int'],
'maxdb_result::current_field' => ['int', 'result'=>''],
'maxdb_result::data_seek' => ['bool', 'result'=>'', 'offset'=>'int'],
'maxdb_result::fetch_array' => ['', 'result'=>'', 'resulttype='=>'int'],
'maxdb_result::fetch_assoc' => ['array', 'result'=>''],
'maxdb_result::fetch_field' => ['', 'result'=>''],
'maxdb_result::fetch_field_direct' => ['', 'result'=>'', 'fieldnr'=>'int'],
'maxdb_result::fetch_fields' => ['', 'result'=>''],
'maxdb_result::fetch_object' => ['object', 'result'=>'object'],
'maxdb_result::fetch_row' => ['', 'result'=>''],
'maxdb_result::field_count' => ['int', 'result'=>''],
'maxdb_result::field_seek' => ['bool', 'result'=>'', 'fieldnr'=>'int'],
'maxdb_result::free' => ['', 'result'=>''],
'maxdb_result::lengths' => ['array', 'result'=>''],
'maxdb_rollback' => ['bool', 'link'=>''],
'maxdb_rpl_parse_enabled' => ['int', 'link'=>'resource'],
'maxdb_rpl_probe' => ['bool', 'link'=>'resource'],
'maxdb_rpl_query_type' => ['int', 'link'=>''],
'maxdb_select_db' => ['bool', 'link'=>'resource', 'dbname'=>'string'],
'maxdb_send_query' => ['bool', 'link'=>'', 'query'=>'string'],
'maxdb_server_end' => ['void'],
'maxdb_server_init' => ['bool', 'server='=>'array', 'groups='=>'array'],
'maxdb_sqlstate' => ['string', 'link'=>'resource'],
'maxdb_ssl_set' => ['bool', 'link'=>'', 'key'=>'string', 'cert'=>'string', 'ca'=>'string', 'capath'=>'string', 'cipher'=>'string'],
'maxdb_stat' => ['string', 'link'=>''],
'maxdb_stmt::affected_rows' => ['int', 'stmt'=>''],
'maxdb_stmt::bind_param' => ['bool', 'stmt'=>'', 'types'=>'string', '&...rw_var'=>''],
'maxdb_stmt::bind_param\'1' => ['bool', 'stmt'=>'', 'types'=>'string', '&rw_var'=>'array'],
'maxdb_stmt::bind_result' => ['bool', 'stmt'=>'', '&rw_var1'=>'', '&...rw_vars='=>''],
'maxdb_stmt::close' => ['bool', 'stmt'=>''],
'maxdb_stmt::close_long_data' => ['bool', 'stmt'=>'', 'param_nr'=>'int'],
'maxdb_stmt::data_seek' => ['bool', 'statement'=>'', 'offset'=>'int'],
'maxdb_stmt::errno' => ['int', 'stmt'=>''],
'maxdb_stmt::error' => ['string', 'stmt'=>''],
'maxdb_stmt::execute' => ['bool', 'stmt'=>''],
'maxdb_stmt::fetch' => ['bool', 'stmt'=>''],
'maxdb_stmt::free_result' => ['', 'stmt'=>''],
'maxdb_stmt::num_rows' => ['int', 'stmt'=>''],
'maxdb_stmt::param_count' => ['int', 'stmt'=>''],
'maxdb_stmt::prepare' => ['', 'stmt'=>'', 'query'=>'string'],
'maxdb_stmt::reset' => ['bool', 'stmt'=>''],
'maxdb_stmt::result_metadata' => ['resource', 'stmt'=>''],
'maxdb_stmt::send_long_data' => ['bool', 'stmt'=>'', 'param_nr'=>'int', 'data'=>'string'],
'maxdb_stmt::stmt_send_long_data' => ['bool', 'param_nr'=>'int', 'data'=>'string'],
'maxdb_stmt::store_result' => ['bool'],
'maxdb_stmt_affected_rows' => ['int', 'stmt'=>'resource'],
'maxdb_stmt_bind_param' => ['bool', 'stmt'=>'', 'types'=>'string', 'var1'=>'', '...args='=>'', 'var='=>'array'],
'maxdb_stmt_bind_result' => ['bool', 'stmt'=>'', '&rw_var1'=>'', '&...rw_vars='=>''],
'maxdb_stmt_close' => ['bool', 'stmt'=>''],
'maxdb_stmt_close_long_data' => ['bool', 'stmt'=>'', 'param_nr'=>'int'],
'maxdb_stmt_data_seek' => ['bool', 'statement'=>'', 'offset'=>'int'],
'maxdb_stmt_errno' => ['int', 'stmt'=>'resource'],
'maxdb_stmt_error' => ['string', 'stmt'=>'resource'],
'maxdb_stmt_execute' => ['bool', 'stmt'=>''],
'maxdb_stmt_fetch' => ['bool', 'stmt'=>''],
'maxdb_stmt_free_result' => ['', 'stmt'=>''],
'maxdb_stmt_init' => ['object', 'link'=>''],
'maxdb_stmt_num_rows' => ['int', 'stmt'=>'resource'],
'maxdb_stmt_param_count' => ['int', 'stmt'=>'resource'],
'maxdb_stmt_prepare' => ['', 'stmt'=>'', 'query'=>'string'],
'maxdb_stmt_reset' => ['bool', 'stmt'=>''],
'maxdb_stmt_result_metadata' => ['resource', 'stmt'=>''],
'maxdb_stmt_send_long_data' => ['bool', 'stmt'=>'', 'param_nr'=>'int', 'data'=>'string'],
'maxdb_stmt_sqlstate' => ['string', 'stmt'=>'resource'],
'maxdb_stmt_store_result' => ['bool', 'stmt'=>''],
'maxdb_store_result' => ['bool', 'link'=>''],
'maxdb_thread_id' => ['int', 'link'=>'resource'],
'maxdb_thread_safe' => ['bool'],
'maxdb_use_result' => ['resource', 'link'=>''],
'maxdb_warning_count' => ['int', 'link'=>'resource'],
'mb_check_encoding' => ['bool', 'var='=>'string', 'encoding='=>'string'],
'mb_chr' => ['string', 'cp'=>'int', 'encoding='=>'string'],
'mb_convert_case' => ['string', 'sourcestring'=>'string', 'mode'=>'int', 'encoding='=>'string'],
'mb_convert_encoding' => ['string', 'str'=>'string', 'to-encoding'=>'string', 'from-encoding='=>'mixed'],
'mb_convert_kana' => ['string', 'str'=>'string', 'option='=>'string', 'encoding='=>'string'],
'mb_convert_variables' => ['string', 'to-encoding'=>'string', 'from-encoding'=>'array|string', '&rw_vars'=>'string|array|object', '&...rw_vars='=>'string|array|object'],
'mb_decode_mimeheader' => ['string', 'string'=>'string'],
'mb_decode_numericentity' => ['string', 'string'=>'string', 'convmap'=>'array', 'encoding'=>'string'],
'mb_detect_encoding' => ['string|false', 'str'=>'string', 'encoding_list='=>'mixed', 'strict='=>'bool'],
'mb_detect_order' => ['bool|array', 'encoding-list='=>'mixed'],
'mb_encode_mimeheader' => ['string', 'str'=>'string', 'charset='=>'string', 'transfer-encoding='=>'string', 'linefeed='=>'string', 'indent='=>'int'],
'mb_encode_numericentity' => ['string', 'string'=>'string', 'convmap'=>'array', 'encoding'=>'string', 'is_hex='=>'bool'],
'mb_encoding_aliases' => ['array|false', 'encoding'=>'string'],
'mb_ereg' => ['int', 'pattern'=>'string', 'string'=>'string', '&w_registers='=>'array'],
'mb_ereg_match' => ['bool', 'pattern'=>'string', 'string'=>'string', 'option='=>'string'],
'mb_ereg_replace' => ['string', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string', 'option='=>'string'],
'mb_ereg_replace_callback' => ['string', 'pattern'=>'string', 'callback'=>'string', 'string'=>'string', 'option='=>'string'],
'mb_ereg_search' => ['bool', 'pattern='=>'string', 'option='=>'string'],
'mb_ereg_search_getpos' => ['int'],
'mb_ereg_search_getregs' => ['array|false'],
'mb_ereg_search_init' => ['bool', 'string'=>'string', 'pattern='=>'string', 'option='=>'string'],
'mb_ereg_search_pos' => ['array', 'pattern='=>'string', 'option='=>'string'],
'mb_ereg_search_regs' => ['array|false', 'pattern='=>'string', 'option='=>'string'],
'mb_ereg_search_setpos' => ['bool', 'position'=>'int'],
'mb_eregi' => ['int', 'pattern'=>'string', 'string'=>'string', '&w_registers='=>'array'],
'mb_eregi_replace' => ['string', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string', 'option='=>'string'],
'mb_get_info' => ['mixed', 'type='=>'string'],
'mb_http_input' => ['mixed', 'type='=>'string'],
'mb_http_output' => ['string', 'encoding='=>'string'],
'mb_internal_encoding' => ['string', 'encoding='=>'string'],
'mb_language' => ['string', 'language='=>'string'],
'mb_list_encodings' => ['array'],
'mb_ord' => ['int', 'str'=>'string', 'enc='=>'string'],
'mb_output_handler' => ['string', 'contents'=>'string', 'status'=>'int'],
'mb_parse_str' => ['bool', 'encoded_string'=>'string', '&w_result='=>'array'],
'mb_preferred_mime_name' => ['string', 'encoding'=>'string'],
'mb_regex_encoding' => ['string', 'encoding='=>'string'],
'mb_regex_set_options' => ['string', 'options='=>'string'],
'mb_scrub' => ['string', 'str'=>'string', 'enc='=>'string'],
'mb_send_mail' => ['bool', 'to'=>'string', 'subject'=>'string', 'message'=>'string', 'additional_headers='=>'string', 'additional_parameter='=>'string'],
'mb_split' => ['array', 'pattern'=>'string', 'string'=>'string', 'limit='=>'int'],
'mb_strcut' => ['string', 'str'=>'string', 'start'=>'int', 'length='=>'int', 'encoding='=>'string'],
'mb_strimwidth' => ['string', 'str'=>'string', 'start'=>'int', 'width'=>'int', 'trimmarker='=>'string', 'encoding='=>'string'],
'mb_stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'],
'mb_stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'],
'mb_strlen' => ['int|false', 'str'=>'string', 'encoding='=>'string'],
'mb_strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'],
'mb_strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'],
'mb_strrichr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'],
'mb_strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'],
'mb_strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'],
'mb_strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'],
'mb_strtolower' => ['string', 'str'=>'string', 'encoding='=>'string'],
'mb_strtoupper' => ['string', 'str'=>'string', 'encoding='=>'string'],
'mb_strwidth' => ['int', 'str'=>'string', 'encoding='=>'string'],
'mb_substitute_character' => ['mixed', 'substchar='=>'mixed'],
'mb_substr' => ['string', 'str'=>'string', 'start'=>'int', 'length='=>'?int', 'encoding='=>'string'],
'mb_substr_count' => ['int', 'haystack'=>'string', 'needle'=>'string', 'encoding='=>'string'],
'mcrypt_cbc' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'],
'mcrypt_cfb' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'],
'mcrypt_create_iv' => ['string', 'size'=>'int', 'source='=>'int'],
'mcrypt_decrypt' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'string', 'iv='=>'string'],
'mcrypt_ecb' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'],
'mcrypt_enc_get_algorithms_name' => ['string', 'td'=>'resource'],
'mcrypt_enc_get_block_size' => ['int', 'td'=>'resource'],
'mcrypt_enc_get_iv_size' => ['int', 'td'=>'resource'],
'mcrypt_enc_get_key_size' => ['int', 'td'=>'resource'],
'mcrypt_enc_get_modes_name' => ['string', 'td'=>'resource'],
'mcrypt_enc_get_supported_key_sizes' => ['array', 'td'=>'resource'],
'mcrypt_enc_is_block_algorithm' => ['bool', 'td'=>'resource'],
'mcrypt_enc_is_block_algorithm_mode' => ['bool', 'td'=>'resource'],
'mcrypt_enc_is_block_mode' => ['bool', 'td'=>'resource'],
'mcrypt_enc_self_test' => ['int', 'td'=>'resource'],
'mcrypt_encrypt' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'string', 'iv='=>'string'],
'mcrypt_generic' => ['string', 'td'=>'resource', 'data'=>'string'],
'mcrypt_generic_deinit' => ['bool', 'td'=>'resource'],
'mcrypt_generic_end' => ['bool', 'td'=>'resource'],
'mcrypt_generic_init' => ['int', 'td'=>'resource', 'key'=>'string', 'iv'=>'string'],
'mcrypt_get_block_size' => ['int', 'cipher'=>'string', 'module'=>'string'],
'mcrypt_get_cipher_name' => ['string', 'cipher'=>'string'],
'mcrypt_get_iv_size' => ['int', 'cipher'=>'string', 'module'=>'string'],
'mcrypt_get_key_size' => ['int', 'cipher'=>'string', 'module'=>'string'],
'mcrypt_list_algorithms' => ['array', 'lib_dir='=>'string'],
'mcrypt_list_modes' => ['array', 'lib_dir='=>'string'],
'mcrypt_module_close' => ['bool', 'td'=>'resource'],
'mcrypt_module_get_algo_block_size' => ['int', 'algorithm'=>'string', 'lib_dir='=>'string'],
'mcrypt_module_get_algo_key_size' => ['int', 'algorithm'=>'string', 'lib_dir='=>'string'],
'mcrypt_module_get_supported_key_sizes' => ['array', 'algorithm'=>'string', 'lib_dir='=>'string'],
'mcrypt_module_is_block_algorithm' => ['bool', 'algorithm'=>'string', 'lib_dir='=>'string'],
'mcrypt_module_is_block_algorithm_mode' => ['bool', 'mode'=>'string', 'lib_dir='=>'string'],
'mcrypt_module_is_block_mode' => ['bool', 'mode'=>'string', 'lib_dir='=>'string'],
'mcrypt_module_open' => ['resource', 'cipher'=>'string', 'cipher_directory'=>'string', 'mode'=>'string', 'mode_directory'=>'string'],
'mcrypt_module_self_test' => ['bool', 'algorithm'=>'string', 'lib_dir='=>'string'],
'mcrypt_ofb' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'],
'md5' => ['string', 'str'=>'string', 'raw_output='=>'bool'],
'md5_file' => ['string', 'filename'=>'string', 'raw_output='=>'bool'],
'mdecrypt_generic' => ['string', 'td'=>'resource', 'data'=>'string'],
'Memcache::add' => ['bool', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'],
'Memcache::addServer' => ['bool', 'host'=>'string', 'port='=>'int', 'persistent='=>'bool', 'weight='=>'int', 'timeout='=>'int', 'retry_interval='=>'int', 'status='=>'bool', 'failure_callback='=>'callable', 'timeoutms='=>'int'],
'Memcache::close' => ['bool'],
'Memcache::connect' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'],
'Memcache::decrement' => ['int', 'key'=>'string', 'value='=>'int'],
'Memcache::delete' => ['bool', 'key'=>'string', 'timeout='=>'int'],
'Memcache::flush' => ['bool'],
'Memcache::get' => ['array', 'key'=>'string', 'flags='=>'array', 'keys='=>'array'],
'Memcache::getExtendedStats' => ['array', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'],
'Memcache::getServerStatus' => ['int', 'host'=>'string', 'port='=>'int'],
'Memcache::getStats' => ['array', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'],
'Memcache::getVersion' => ['string'],
'Memcache::increment' => ['int', 'key'=>'string', 'value='=>'int'],
'Memcache::pconnect' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'],
'Memcache::replace' => ['bool', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'],
'Memcache::set' => ['bool', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'],
'Memcache::setCompressThreshold' => ['bool', 'threshold'=>'int', 'min_savings='=>'float'],
'Memcache::setServerParams' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'int', 'retry_interval='=>'int', 'status='=>'bool', 'failure_callback='=>'callable'],
'memcache_debug' => ['bool', 'on_off'=>'bool'],
'Memcached::add' => ['bool', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'],
'Memcached::addByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'],
'Memcached::addServer' => ['bool', 'host'=>'string', 'port'=>'int', 'weight='=>'int'],
'Memcached::addServers' => ['bool', 'servers'=>'array'],
'Memcached::append' => ['bool', 'key'=>'string', 'value'=>'string'],
'Memcached::appendByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'value'=>'string'],
'Memcached::cas' => ['bool', 'cas_token'=>'float', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'],
'Memcached::casByKey' => ['bool', 'cas_token'=>'float', 'server_key'=>'string', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'],
'Memcached::decrement' => ['int|false', 'key'=>'string', 'offset='=>'int', 'initial_value='=>'int', 'expiry='=>'int'],
'Memcached::decrementByKey' => ['int|false', 'server_key'=>'string', 'key'=>'string', 'offset='=>'int', 'initial_value='=>'int', 'expiry='=>'int'],
'Memcached::delete' => ['bool', 'key'=>'string', 'time='=>'int'],
'Memcached::deleteByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'time='=>'int'],
'Memcached::deleteMulti' => ['bool', 'keys'=>'array', 'time='=>'int'],
'Memcached::deleteMultiByKey' => ['bool', 'server_key'=>'string', 'keys'=>'array', 'time='=>'int'],
'Memcached::fetch' => ['array'],
'Memcached::fetchAll' => ['array'],
'Memcached::flush' => ['bool', 'delay='=>'int'],
'Memcached::get' => ['mixed', 'key'=>'string', 'cache_cb='=>'?callable', 'flags='=>'int'],
'Memcached::getAllKeys' => ['array'],
'Memcached::getByKey' => ['mixed', 'server_key'=>'string', 'key'=>'string', 'value_cb='=>'?callable', 'flags='=>'int'],
'Memcached::getDelayed' => ['bool', 'keys'=>'array', 'with_cas='=>'bool', 'value_cb='=>'callable'],
'Memcached::getDelayedByKey' => ['bool', 'server_key'=>'string', 'keys'=>'array', 'with_cas='=>'bool', 'value_cb='=>'?callable'],
'Memcached::getMulti' => ['array|false', 'keys'=>'array', 'flags='=>'int'],
'Memcached::getMultiByKey' => ['array', 'server_key'=>'string', 'keys'=>'array', 'flags='=>'int'],
'Memcached::getOption' => ['mixed', 'option'=>'int'],
'Memcached::getResultCode' => ['int'],
'Memcached::getResultMessage' => ['string'],
'Memcached::getServerByKey' => ['array', 'server_key'=>'string'],
'Memcached::getServerList' => ['array'],
'Memcached::getStats' => ['array', 'type='=>'?string'],
'Memcached::getVersion' => ['array'],
'Memcached::increment' => ['int|false', 'key'=>'string', 'offset='=>'int', 'initial_value='=>'int', 'expiry='=>'int'],
'Memcached::incrementByKey' => ['int|false', 'server_key'=>'string', 'key'=>'string', 'offset='=>'int', 'initial_value='=>'int', 'expiry='=>'int'],
'Memcached::isPersistent' => ['bool'],
'Memcached::isPristine' => ['bool'],
'Memcached::prepend' => ['bool', 'key'=>'string', 'value'=>'string'],
'Memcached::prependByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'value'=>'string'],
'Memcached::quit' => ['bool'],
'Memcached::replace' => ['bool', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'],
'Memcached::replaceByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'],
'Memcached::resetServerList' => ['bool'],
'Memcached::set' => ['bool', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'],
'Memcached::setByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'value'=>'mixed', 'expiration='=>'int'],
'Memcached::setMulti' => ['bool', 'items'=>'array', 'expiration='=>'int'],
'Memcached::setMultiByKey' => ['bool', 'server_key'=>'string', 'items'=>'array', 'expiration='=>'int'],
'Memcached::setOption' => ['bool', 'option'=>'int', 'value'=>'mixed'],
'Memcached::setOptions' => ['bool', 'options'=>'array'],
'Memcached::setSaslAuthData' => ['void', 'username'=>'string', 'password'=>'string'],
'Memcached::touch' => ['bool', 'key'=>'string', 'expiration'=>'int'],
'Memcached::touchByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'expiration'=>'int'],
'memory_get_peak_usage' => ['int', 'real_usage='=>'bool'],
'memory_get_usage' => ['int', 'real_usage='=>'bool'],
'MessageFormatter::__construct' => ['void', 'locale'=>'string', 'pattern'=>'string'],
'MessageFormatter::create' => ['MessageFormatter', 'locale'=>'string', 'pattern'=>'string'],
'MessageFormatter::format' => ['', 'args'=>'array'],
'MessageFormatter::formatMessage' => ['array', 'locale'=>'string', 'pattern'=>'string', 'args'=>'array'],
'MessageFormatter::getErrorCode' => ['int'],
'MessageFormatter::getErrorMessage' => ['string'],
'MessageFormatter::getLocale' => ['string'],
'MessageFormatter::getPattern' => ['string'],
'MessageFormatter::parse' => ['array', 'source'=>'string'],
'MessageFormatter::parseMessage' => ['array', 'locale'=>'string', 'pattern'=>'string', 'source'=>'string', 'value'=>'string'],
'MessageFormatter::setPattern' => ['bool', 'pattern'=>'string'],
'MesssageFormatter::create' => ['MessageFormatter', 'locale'=>'string', 'pattern'=>'string'],
'metaphone' => ['string', 'text'=>'string', 'phones='=>'int'],
'method_exists' => ['bool', 'object'=>'object|string', 'method'=>'string'],
'mhash' => ['string', 'hash'=>'int', 'data'=>'string', 'key='=>'string'],
'mhash_count' => ['int'],
'mhash_get_block_size' => ['int', 'hash'=>'int'],
'mhash_get_hash_name' => ['string', 'hash'=>'int'],
'mhash_keygen_s2k' => ['string', 'hash'=>'int', 'input_password'=>'string', 'salt'=>'string', 'bytes'=>'int'],
'microtime' => ['mixed', 'get_as_float='=>'bool'],
'mime_content_type' => ['string', 'filename_or_stream'=>'string'],
'min' => ['', 'arg1'=>'array'],
'min\'1' => ['', 'arg1'=>'', 'arg2'=>'', '...args='=>''],
'ming_keypress' => ['int', 'char'=>'string'],
'ming_setcubicthreshold' => ['void', 'threshold'=>'int'],
'ming_setscale' => ['void', 'scale'=>'float'],
'ming_setswfcompression' => ['void', 'level'=>'int'],
'ming_useconstants' => ['void', 'use'=>'int'],
'ming_useswfversion' => ['void', 'version'=>'int'],
'mkdir' => ['bool', 'pathname'=>'string', 'mode='=>'int', 'recursive='=>'bool', 'context='=>'resource'],
'mktime' => ['int', 'hour='=>'int', 'min='=>'int', 'sec='=>'int', 'mon='=>'int', 'day='=>'int', 'year='=>'int'],
'money_format' => ['string', 'format'=>'string', 'value'=>'float'],
'Mongo::__get' => ['MongoDB', 'dbname'=>'string'],
'Mongo::__toString' => ['string'],
'Mongo::close' => ['bool'],
'Mongo::connect' => ['bool'],
'Mongo::connectUtil' => ['bool'],
'Mongo::dropDB' => ['array', 'db'=>''],
'Mongo::forceError' => ['bool'],
'Mongo::getConnections' => ['array'],
'Mongo::getHosts' => ['array'],
'Mongo::getPoolSize' => ['int'],
'Mongo::getReadPreference' => ['array'],
'Mongo::getSlave' => ['string'],
'Mongo::getSlaveOkay' => ['bool'],
'Mongo::getWriteConcern' => ['array'],
'Mongo::killCursor' => ['', 'server_hash'=>'string', 'id'=>'MongoInt64|int'],
'Mongo::lastError' => ['array|null'],
'Mongo::listDBs' => ['array'],
'Mongo::pairConnect' => ['bool'],
'Mongo::pairPersistConnect' => ['bool', 'username='=>'string', 'password='=>'string'],
'Mongo::persistConnect' => ['bool', 'username='=>'string', 'password='=>'string'],
'Mongo::poolDebug' => ['array'],
'Mongo::prevError' => ['array'],
'Mongo::resetError' => ['array'],
'Mongo::selectCollection' => ['MongoCollection', 'db'=>'string', 'collection'=>'string'],
'Mongo::selectDB' => ['MongoDB', 'name'=>'string'],
'Mongo::setPoolSize' => ['bool', 'size'=>'int'],
'Mongo::setReadPreference' => ['bool', 'readPreference'=>'string', 'tags='=>'array'],
'Mongo::setSlaveOkay' => ['bool', 'ok='=>'bool'],
'Mongo::switchSlave' => ['string'],
'MongoBinData::__construct' => ['void', 'data'=>'string', 'type='=>'int'],
'MongoBinData::__toString' => ['string'],
'MongoClient::__construct' => ['void', 'server='=>'string', 'options='=>'array', 'driver_options'=>'array'],
'MongoClient::__get' => ['MongoDB', 'dbname'=>'string'],
'MongoClient::__toString' => ['string'],
'MongoClient::close' => ['bool', 'connection='=>'bool|string'],
'MongoClient::connect' => ['bool'],
'MongoClient::dropDB' => ['array', 'db'=>'mixed'],
'MongoClient::getConnections' => ['array'],
'MongoClient::getHosts' => ['array'],
'MongoClient::getReadPreference' => ['array'],
'MongoClient::getWriteConcern' => ['array'],
'MongoClient::killCursor' => ['bool', 'server_hash'=>'string', 'id'=>'int|MongoInt64'],
'MongoClient::listDBs' => ['array'],
'MongoClient::selectCollection' => ['MongoCollection', 'db'=>'string', 'collection'=>'string'],
'MongoClient::selectDB' => ['MongoDB', 'name'=>'string'],
'MongoClient::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags='=>'array'],
'MongoClient::setWriteConcern' => ['bool', 'w'=>'mixed', 'wtimeout='=>'int'],
'MongoClient::switchSlave' => ['string'],
'MongoCode::__construct' => ['void', 'code'=>'string', 'scope='=>'array'],
'MongoCode::__toString' => ['string'],
'MongoCollection::__construct' => ['void', 'db'=>'MongoDB', 'name'=>'string'],
'MongoCollection::__get' => ['MongoCollection', 'name'=>'string'],
'MongoCollection::__toString' => ['string'],
'MongoCollection::aggregate' => ['array', 'op'=>'array', 'op='=>'array', '...args='=>'array'],
'MongoCollection::aggregate\'1' => ['array', 'pipeline'=>'array', 'options='=>'array'],
'MongoCollection::aggregateCursor' => ['MongoCommandCursor', 'command'=>'array', 'options='=>'array'],
'MongoCollection::batchInsert' => ['mixed', 'a'=>'array', 'options='=>'array'],
'MongoCollection::count' => ['int', 'query='=>'array', 'limit='=>'int', 'skip='=>'int'],
'MongoCollection::createDBRef' => ['array', 'a'=>'array'],
'MongoCollection::createIndex' => ['bool', 'keys'=>'array', 'options='=>'array'],
'MongoCollection::deleteIndex' => ['array', 'keys'=>'string|array'],
'MongoCollection::deleteIndexes' => ['array'],
'MongoCollection::distinct' => ['array', 'key'=>'string', 'query='=>'array'],
'MongoCollection::drop' => ['array'],
'MongoCollection::ensureIndex' => ['bool', 'keys'=>'array', 'options='=>'array'],
'MongoCollection::find' => ['MongoCursor', 'query='=>'array', 'fields='=>'array'],
'MongoCollection::findAndModify' => ['array', 'query'=>'array', 'update='=>'array', 'fields='=>'array', 'options='=>'array'],
'MongoCollection::findOne' => ['array', 'query='=>'array', 'fields='=>'array'],
'MongoCollection::getDBRef' => ['array', 'ref'=>'array'],
'MongoCollection::getIndexInfo' => ['array'],
'MongoCollection::getName' => ['string'],
'MongoCollection::getReadPreference' => ['array'],
'MongoCollection::getSlaveOkay' => ['bool'],
'MongoCollection::getWriteConcern' => ['array'],
'MongoCollection::group' => ['array', 'keys'=>'mixed', 'initial'=>'array', 'reduce'=>'mongocode', 'options='=>'array'],
'MongoCollection::insert' => ['bool|array', 'a'=>'array', 'options='=>'array'],
'MongoCollection::parallelCollectionScan' => ['array[MongoCommandCursor]', 'num_cursors'=>'int'],
'MongoCollection::remove' => ['bool|array', 'criteria='=>'array', 'options='=>'array'],
'MongoCollection::save' => ['mixed', 'a'=>'array', 'options='=>'array'],
'MongoCollection::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags='=>'array'],
'MongoCollection::setSlaveOkay' => ['bool', 'ok='=>'bool'],
'MongoCollection::setWriteConcern' => ['bool', 'w'=>'mixed', 'wtimeout='=>'int'],
'MongoCollection::toIndexString' => ['string', 'keys'=>'mixed'],
'MongoCollection::update' => ['bool', 'criteria'=>'array', 'newobj'=>'array', 'options='=>'array'],
'MongoCollection::validate' => ['array', 'scan_data='=>'bool'],
'MongoCommandCursor::__construct' => ['void', 'connection'=>'MongoClient', 'ns'=>'string', 'command'=>'array'],
'MongoCommandCursor::batchSize' => ['MongoCommandCursor', 'batchSize'=>'int'],
'MongoCommandCursor::createFromDocument' => ['MongoCommandCursor', 'connection'=>'MongoClient', 'hash'=>'string', 'document'=>'array'],
'MongoCommandCursor::current' => ['array'],
'MongoCommandCursor::dead' => ['bool'],
'MongoCommandCursor::getReadPreference' => ['array'],
'MongoCommandCursor::info' => ['array'],
'MongoCommandCursor::key' => ['int'],
'MongoCommandCursor::next' => ['void'],
'MongoCommandCursor::rewind' => ['array'],
'MongoCommandCursor::setReadPreference' => ['MongoCommandCursor', 'read_preference'=>'string', 'tags='=>'array'],
'MongoCommandCursor::timeout' => ['MongoCommandCursor', 'ms'=>'int'],
'MongoCommandCursor::valid' => ['bool'],
'MongoCursor::__construct' => ['void', 'connection'=>'MongoClient', 'ns'=>'string', 'query='=>'array', 'fields='=>'array'],
'MongoCursor::addOption' => ['MongoCursor', 'key'=>'string', 'value'=>'mixed'],
'MongoCursor::awaitData' => ['MongoCursor', 'wait='=>'bool'],
'MongoCursor::batchSize' => ['MongoCursor', 'num'=>'int'],
'MongoCursor::count' => ['int', 'foundonly='=>'bool'],
'MongoCursor::current' => ['array'],
'MongoCursor::dead' => ['bool'],
'MongoCursor::doQuery' => ['void'],
'MongoCursor::explain' => ['array'],
'MongoCursor::fields' => ['MongoCursor', 'f'=>'array'],
'MongoCursor::getNext' => ['array'],
'MongoCursor::getReadPreference' => ['array'],
'MongoCursor::hasNext' => ['bool'],
'MongoCursor::hint' => ['MongoCursor', 'key_pattern'=>'array'],
'MongoCursor::immortal' => ['MongoCursor', 'liveforever='=>'bool'],
'MongoCursor::info' => ['array'],
'MongoCursor::key' => ['string'],
'MongoCursor::limit' => ['MongoCursor', 'num'=>'int'],
'MongoCursor::maxTimeMS' => ['MongoCursor', 'ms'=>'int'],
'MongoCursor::next' => ['array'],
'MongoCursor::partial' => ['MongoCursor', 'okay='=>'bool'],
'MongoCursor::reset' => ['void'],
'MongoCursor::rewind' => ['void'],
'MongoCursor::setFlag' => ['MongoCursor', 'flag'=>'int', 'set='=>'bool'],
'MongoCursor::setReadPreference' => ['MongoCursor', 'read_preference'=>'string', 'tags='=>'array'],
'MongoCursor::skip' => ['MongoCursor', 'num'=>'int'],
'MongoCursor::slaveOkay' => ['MongoCursor', 'okay='=>'bool'],
'MongoCursor::snapshot' => ['MongoCursor'],
'MongoCursor::sort' => ['MongoCursor', 'fields'=>'array'],
'MongoCursor::tailable' => ['MongoCursor', 'tail='=>'bool'],
'MongoCursor::timeout' => ['MongoCursor', 'ms'=>'int'],
'MongoCursor::valid' => ['bool'],
'MongoCursorException::__clone' => ['void'],
'MongoCursorException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\Exception|?\Throwable'],
'MongoCursorException::__toString' => ['string'],
'MongoCursorException::__wakeup' => ['void'],
'MongoCursorException::getCode' => ['int'],
'MongoCursorException::getFile' => ['string'],
'MongoCursorException::getHost' => ['string'],
'MongoCursorException::getLine' => ['int'],
'MongoCursorException::getMessage' => ['string'],
'MongoCursorException::getPrevious' => ['Exception|Throwable'],
'MongoCursorException::getTrace' => ['array'],
'MongoCursorException::getTraceAsString' => ['string'],
'MongoCursorInterface::__construct' => ['void'],
'MongoCursorInterface::batchSize' => ['MongoCursorInterface', 'batchSize'=>'int'],
'MongoCursorInterface::current' => ['mixed'],
'MongoCursorInterface::dead' => ['bool'],
'MongoCursorInterface::getReadPreference' => ['array'],
'MongoCursorInterface::info' => ['array'],
'MongoCursorInterface::key' => ['int|string'],
'MongoCursorInterface::next' => ['void'],
'MongoCursorInterface::rewind' => ['void'],
'MongoCursorInterface::setReadPreference' => ['MongoCursorInterface', 'read_preference'=>'string', 'tags='=>'array'],
'MongoCursorInterface::timeout' => ['MongoCursorInterface', 'ms'=>'int'],
'MongoCursorInterface::valid' => ['bool'],
'MongoDate::__construct' => ['void', 'sec='=>'int', 'usec='=>'int'],
'MongoDate::__toString' => ['string'],
'MongoDate::toDateTime' => ['DateTime'],
'MongoDB::__construct' => ['void', 'conn'=>'MongoClient', 'name'=>'string'],
'MongoDB::__get' => ['MongoCollection', 'name'=>'string'],
'MongoDB::__toString' => ['string'],
'MongoDB::authenticate' => ['array', 'username'=>'string', 'password'=>'string'],
'MongoDB::command' => ['array', 'command'=>'array'],
'MongoDB::createCollection' => ['MongoCollection', 'name'=>'string', 'capped='=>'bool', 'size='=>'int', 'max='=>'int'],
'MongoDB::createDBRef' => ['array', 'collection'=>'string', 'a'=>''],
'MongoDB::drop' => ['array'],
'MongoDB::dropCollection' => ['array', 'coll'=>''],
'MongoDB::execute' => ['array', 'code'=>'', 'args='=>'array'],
'MongoDB::forceError' => ['bool'],
'MongoDB::getCollectionInfo' => ['array', 'options='=>'array'],
'MongoDB::getCollectionNames' => ['array', 'options='=>'array'],
'MongoDB::getDBRef' => ['array', 'ref'=>'array'],
'MongoDB::getGridFS' => ['MongoGridFS', 'prefix='=>'string'],
'MongoDB::getProfilingLevel' => ['int'],
'MongoDB::getReadPreference' => ['array'],
'MongoDB::getSlaveOkay' => ['bool'],
'MongoDB::getWriteConcern' => ['array'],
'MongoDB::lastError' => ['array'],
'MongoDB::listCollections' => ['array'],
'MongoDB::prevError' => ['array'],
'MongoDB::repair' => ['array', 'preserve_cloned_files='=>'bool', 'backup_original_files='=>'bool'],
'MongoDB::resetError' => ['array'],
'MongoDB::selectCollection' => ['MongoCollection', 'name'=>'string'],
'MongoDB::setProfilingLevel' => ['int', 'level'=>'int'],
'MongoDB::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags='=>'array'],
'MongoDB::setSlaveOkay' => ['bool', 'ok='=>'bool'],
'MongoDB::setWriteConcern' => ['bool', 'w'=>'mixed', 'wtimeout='=>'int'],
'MongoDB\BSON\Binary::__construct' => ['void', 'data'=>'string', 'type'=>'int'],
'MongoDB\BSON\Binary::getData' => ['string'],
'MongoDB\BSON\Binary::getType' => ['int'],
'MongoDB\BSON\Decimal128::__construct' => ['void', 'value='=>'string'],
'MongoDB\BSON\Decimal128::__toString' => ['string'],
'MongoDB\BSON\fromJSON' => ['string', 'json'=>'string'],
'MongoDB\BSON\fromPHP' => ['string', 'value'=>'array|object'],
'MongoDB\BSON\Javascript::__construct' => ['void', 'code'=>'string', 'scope='=>'array|object'],
'MongoDB\BSON\ObjectId::__construct' => ['void', 'id='=>'string'],
'MongoDB\BSON\ObjectId::__toString' => ['string'],
'MongoDB\BSON\Regex::__construct' => ['void', 'pattern'=>'string', 'flags='=>'string'],
'MongoDB\BSON\Regex::__toString' => ['string'],
'MongoDB\BSON\Regex::getFlags' => [''],
'MongoDB\BSON\Regex::getPattern' => ['string'],
'MongoDB\BSON\Serializable::bsonSerialize' => ['array|object'],
'MongoDB\BSON\Timestamp::__construct' => ['void', 'increment'=>'int', 'timestamp'=>'int'],
'MongoDB\BSON\Timestamp::__toString' => ['string'],
'MongoDB\BSON\toJSON' => ['string', 'bson'=>'string'],
'MongoDB\BSON\toPHP' => ['object', 'bson'=>'string', 'typeMap'=>'array'],
'MongoDB\BSON\Unserializable::bsonUnserialize' => ['', 'data'=>'array'],
'MongoDB\BSON\UTCDateTime::__construct' => ['void', 'milliseconds='=>'int'],
'MongoDB\BSON\UTCDateTime::__toString' => ['string'],
'MongoDB\BSON\UTCDateTime::toDateTime' => ['DateTime'],
'MongoDB\Driver\BulkWrite::__construct' => ['void', 'ordered='=>'bool'],
'MongoDB\Driver\BulkWrite::count' => ['int'],
'MongoDB\Driver\BulkWrite::delete' => ['void', 'filter'=>'array|object', 'deleteOptions='=>'array'],
'MongoDB\Driver\BulkWrite::insert' => ['MongoDB\Driver\ObjectID', 'document'=>'array|object'],
'MongoDB\Driver\BulkWrite::update' => ['void', 'filter'=>'array|object', 'newObj'=>'array|object', 'updateOptions='=>'array'],
'MongoDB\Driver\Command::__construct' => ['void', 'document'=>'array|object'],
'MongoDB\Driver\Cursor::__construct' => ['void', 'server'=>'Server', 'responseDocument'=>'string'],
'MongoDB\Driver\Cursor::getId' => ['MongoDB\Driver\CursorId'],
'MongoDB\Driver\Cursor::getServer' => ['MongoDB\Driver\Server'],
'MongoDB\Driver\Cursor::isDead' => ['bool'],
'MongoDB\Driver\Cursor::setTypeMap' => ['void', 'typemap'=>'array'],
'MongoDB\Driver\Cursor::toArray' => ['array'],
'MongoDB\Driver\CursorId::__construct' => ['void', 'id'=>'string'],
'MongoDB\Driver\CursorId::__toString' => ['string'],
'MongoDB\Driver\Exception\RuntimeException::__clone' => ['void'],
'MongoDB\Driver\Exception\RuntimeException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\RuntimeException|?\Throwable'],
'MongoDB\Driver\Exception\RuntimeException::__toString' => ['string'],
'MongoDB\Driver\Exception\RuntimeException::__wakeup' => ['void'],
'MongoDB\Driver\Exception\RuntimeException::getCode' => ['int'],
'MongoDB\Driver\Exception\RuntimeException::getFile' => ['string'],
'MongoDB\Driver\Exception\RuntimeException::getLine' => ['int'],
'MongoDB\Driver\Exception\RuntimeException::getMessage' => ['string'],
'MongoDB\Driver\Exception\RuntimeException::getPrevious' => ['RuntimeException|Throwable'],
'MongoDB\Driver\Exception\RuntimeException::getTrace' => ['array'],
'MongoDB\Driver\Exception\RuntimeException::getTraceAsString' => ['string'],
'MongoDB\Driver\Exception\WriteException::__clone' => ['void'],
'MongoDB\Driver\Exception\WriteException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\RuntimeException|?\Throwable'],
'MongoDB\Driver\Exception\WriteException::__toString' => ['string'],
'MongoDB\Driver\Exception\WriteException::__wakeup' => ['void'],
'MongoDB\Driver\Exception\WriteException::getCode' => ['int'],
'MongoDB\Driver\Exception\WriteException::getFile' => ['string'],
'MongoDB\Driver\Exception\WriteException::getLine' => ['int'],
'MongoDB\Driver\Exception\WriteException::getMessage' => ['string'],
'MongoDB\Driver\Exception\WriteException::getPrevious' => ['RuntimeException|Throwable'],
'MongoDB\Driver\Exception\WriteException::getTrace' => ['array'],
'MongoDB\Driver\Exception\WriteException::getTraceAsString' => ['string'],
'MongoDB\Driver\Exception\WriteException::getWriteResult' => ['MongoDB\Driver\WriteResult'],
'MongoDB\Driver\Manager::__construct' => ['void', 'uri'=>'string', 'options='=>'array', 'driverOptions='=>'array'],
'MongoDB\Driver\Manager::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'bulk'=>'MongoDB\Driver\BulkWrite', 'writeConcern='=>'MongoDB\Driver\WriteConcern'],
'MongoDB\Driver\Manager::executeCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'readPreference='=>'MongoDB\Driver\ReadPreference'],
'MongoDB\Driver\Manager::executeDelete' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'filter'=>'array|object', 'deleteOptions='=>'array', 'writeConcern='=>'MongoDB\Driver\WriteConcern'],
'MongoDB\Driver\Manager::executeInsert' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'document'=>'array|object', 'writeConcern='=>'MongoDB\Driver\WriteConcern'],
'MongoDB\Driver\Manager::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace'=>'string', 'query'=>'MongoDB\Driver\Query', 'readPreference='=>'MongoDB\Driver\ReadPreference'],
'MongoDB\Driver\Manager::executeUpdate' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'filter'=>'array|object', 'newObj'=>'array|object', 'updateOptions='=>'array', 'writeConcern='=>'MongoDB\Driver\WriteConcern'],
'MongoDB\Driver\Manager::getReadConcern' => ['MongoDB\Driver\ReadConcern'],
'MongoDB\Driver\Manager::getReadPreference' => ['MongoDB\Driver\ReadPreference'],
'MongoDB\Driver\Manager::getServers' => ['array'],
'MongoDB\Driver\Manager::getWriteConcern' => ['MongoDB\Driver\WriteConcern'],
'MongoDB\Driver\Manager::selectServer' => ['MongoDB\Driver\Server', 'readPreference'=>'MongoDB\Driver\ReadPreference'],
'MongoDB\Driver\Query::__construct' => ['void', 'filter'=>'array|object', 'queryOptions='=>'array'],
'MongoDB\Driver\ReadConcern::__construct' => ['void', 'level='=>'string'],
'MongoDB\Driver\ReadConcern::bsonSerialize' => ['object'],
'MongoDB\Driver\ReadConcern::getLevel' => ['null|string'],
'MongoDB\Driver\ReadPreference::__construct' => ['void', 'readPreference'=>'string', 'tagSets='=>'array'],
'MongoDB\Driver\ReadPreference::bsonSerialize' => ['object'],
'MongoDB\Driver\ReadPreference::getMode' => ['int'],
'MongoDB\Driver\ReadPreference::getTagSets' => ['array'],
'MongoDB\Driver\Server::__construct' => ['void', 'host'=>'string', 'port'=>'string', 'options='=>'array', 'driverOptions='=>'array'],
'MongoDB\Driver\Server::executeBulkWrite' => ['', 'namespace'=>'string', 'zwrite'=>'BulkWrite'],
'MongoDB\Driver\Server::executeCommand' => ['', 'db'=>'string', 'command'=>'Command'],
'MongoDB\Driver\Server::executeQuery' => ['', 'namespace'=>'string', 'zquery'=>'Query'],
'MongoDB\Driver\Server::getHost' => [''],
'MongoDB\Driver\Server::getInfo' => [''],
'MongoDB\Driver\Server::getLatency' => [''],
'MongoDB\Driver\Server::getPort' => [''],
'MongoDB\Driver\Server::getState' => [''],
'MongoDB\Driver\Server::getTags' => ['array'],
'MongoDB\Driver\Server::getType' => [''],
'MongoDB\Driver\Server::isArbiter' => ['bool'],
'MongoDB\Driver\Server::isDelayed' => [''],
'MongoDB\Driver\Server::isHidden' => ['bool'],
'MongoDB\Driver\Server::isPassive' => [''],
'MongoDB\Driver\Server::isPrimary' => ['bool'],
'MongoDB\Driver\Server::isSecondary' => ['bool'],
'MongoDB\Driver\WriteConcern::__construct' => ['void', 'wstring'=>'string', 'wtimeout='=>'int', 'journal='=>'bool', 'fsync='=>'bool'],
'MongoDB\Driver\WriteConcern::getJurnal' => ['bool|null'],
'MongoDB\Driver\WriteConcern::getW' => ['int|null|string'],
'MongoDB\Driver\WriteConcern::getWtimeout' => ['int'],
'MongoDB\Driver\WriteConcernError::getCode' => [''],
'MongoDB\Driver\WriteConcernError::getInfo' => [''],
'MongoDB\Driver\WriteConcernError::getMessage' => [''],
'MongoDB\Driver\WriteError::getCode' => [''],
'MongoDB\Driver\WriteError::getIndex' => [''],
'MongoDB\Driver\WriteError::getInfo' => ['mixed'],
'MongoDB\Driver\WriteError::getMessage' => [''],
'MongoDB\Driver\WriteException::getWriteResult' => [''],
'MongoDB\Driver\WriteResult::getDeletedCount' => ['int'],
'MongoDB\Driver\WriteResult::getInfo' => [''],
'MongoDB\Driver\WriteResult::getInsertedCount' => ['int'],
'MongoDB\Driver\WriteResult::getMatchedCount' => ['int'],
'MongoDB\Driver\WriteResult::getModifiedCount' => ['int'],
'MongoDB\Driver\WriteResult::getServer' => [''],
'MongoDB\Driver\WriteResult::getUpsertedCount' => ['int'],
'MongoDB\Driver\WriteResult::getUpsertedIds' => [''],
'MongoDB\Driver\WriteResult::getWriteConcernError' => [''],
'MongoDB\Driver\WriteResult::getWriteErrors' => [''],
'MongoDB\Driver\WriteResult::isAcknowledged' => ['bool'],
'MongoDBRef::create' => ['array', 'collection'=>'string', 'id'=>'mixed', 'database='=>'string'],
'MongoDBRef::get' => ['array', 'db'=>'mongodb', 'ref'=>'array'],
'MongoDBRef::isRef' => ['bool', 'ref'=>'mixed'],
'MongoDeleteBatch::__construct' => ['void', 'collection'=>'MongoCollection', 'write_options='=>'array'],
'MongoException::__clone' => ['void'],
'MongoException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\Exception|?\Throwable'],
'MongoException::__toString' => ['string'],
'MongoException::__wakeup' => ['void'],
'MongoException::getCode' => ['int'],
'MongoException::getFile' => ['string'],
'MongoException::getLine' => ['int'],
'MongoException::getMessage' => ['string'],
'MongoException::getPrevious' => ['Exception|Throwable'],
'MongoException::getTrace' => ['array'],
'MongoException::getTraceAsString' => ['string'],
'MongoGridFS::__construct' => ['void', 'db'=>'MongoDB', 'prefix='=>'string', 'chunks='=>'mixed'],
'MongoGridFS::__get' => ['MongoCollection', 'name'=>'string'],
'MongoGridFS::__toString' => ['string'],
'MongoGridFS::aggregate' => ['array', 'pipeline'=>'array', 'op'=>'array', 'pipelineOperators'=>'array'],
'MongoGridFS::aggregateCursor' => ['MongoCommandCursor', 'pipeline'=>'array', 'options'=>'array'],
'MongoGridFS::batchInsert' => ['mixed', 'a'=>'array', 'options='=>'array'],
'MongoGridFS::count' => ['int', 'query='=>'stdClass|array'],
'MongoGridFS::createDBRef' => ['array', 'a'=>'array'],
'MongoGridFS::createIndex' => ['array', 'keys'=>'array', 'options='=>'array'],
'MongoGridFS::delete' => ['bool', 'id'=>'mixed'],
'MongoGridFS::deleteIndex' => ['array', 'keys'=>'array|string'],
'MongoGridFS::deleteIndexes' => ['array'],
'MongoGridFS::distinct' => ['array|bool', 'key'=>'string', 'query='=>'?array'],
'MongoGridFS::drop' => ['array'],
'MongoGridFS::ensureIndex' => ['bool', 'keys'=>'array', 'options='=>'array'],
'MongoGridFS::find' => ['MongoGridFSCursor', 'query='=>'array', 'fields='=>'array'],
'MongoGridFS::findAndModify' => ['array', 'query'=>'array', 'update='=>'?array', 'fields='=>'?array', 'options='=>'?array'],
'MongoGridFS::findOne' => ['MongoGridFSFile', 'query='=>'mixed', 'fields='=>'mixed'],
'MongoGridFS::get' => ['MongoGridFSFile', 'id'=>'mixed'],
'MongoGridFS::getDBRef' => ['array', 'ref'=>'array'],
'MongoGridFS::getIndexInfo' => ['array'],
'MongoGridFS::getName' => ['string'],
'MongoGridFS::getReadPreference' => ['array'],
'MongoGridFS::getSlaveOkay' => ['bool'],
'MongoGridFS::group' => ['array', 'keys'=>'mixed', 'initial'=>'array', 'reduce'=>'MongoCode', 'condition='=>'array'],
'MongoGridFS::insert' => ['array|bool', 'a'=>'array|object', 'options='=>'array'],
'MongoGridFS::put' => ['mixed', 'filename'=>'string', 'extra='=>'array'],
'MongoGridFS::remove' => ['bool', 'criteria='=>'array', 'options='=>'array'],
'MongoGridFS::save' => ['array|bool', 'a'=>'array|object', 'options='=>'array'],
'MongoGridFS::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags'=>'array'],
'MongoGridFS::setSlaveOkay' => ['bool', 'ok='=>'bool|true'],
'MongoGridFS::storeBytes' => ['mixed', 'bytes'=>'string', 'extra='=>'array', 'options='=>'array'],
'MongoGridFS::storeFile' => ['mixed', 'filename'=>'string', 'extra='=>'array', 'options='=>'array'],
'MongoGridFS::storeUpload' => ['mixed', 'name'=>'string', 'filename='=>'string'],
'MongoGridFS::toIndexString' => ['string', 'keys'=>'mixed'],
'MongoGridFS::update' => ['bool', 'criteria'=>'array', 'newobj'=>'array', 'options='=>'array'],
'MongoGridFS::validate' => ['array', 'scan_data='=>'bool|false'],
'MongoGridFSCursor::__construct' => ['void', 'gridfs'=>'MongoGridFS', 'connection'=>'resource', 'ns'=>'string', 'query'=>'array', 'fields'=>'array'],
'MongoGridFSCursor::addOption' => ['MongoCursor', 'key'=>'string', 'value'=>'mixed'],
'MongoGridFSCursor::awaitData' => ['MongoCursor', 'wait='=>'bool|true'],
'MongoGridFSCursor::batchSize' => ['MongoCursor', 'batchSize'=>'int'],
'MongoGridFSCursor::count' => ['int', 'all='=>'bool|false'],
'MongoGridFSCursor::current' => ['MongoGridFSFile'],
'MongoGridFSCursor::dead' => ['bool'],
'MongoGridFSCursor::doQuery' => ['void'],
'MongoGridFSCursor::explain' => ['array'],
'MongoGridFSCursor::fields' => ['MongoCursor', 'f'=>'array'],
'MongoGridFSCursor::getNext' => ['MongoGridFSFile'],
'MongoGridFSCursor::getReadPreference' => ['array'],
'MongoGridFSCursor::hasNext' => ['bool'],
'MongoGridFSCursor::hint' => ['MongoCursor', 'key_pattern'=>'mixed'],
'MongoGridFSCursor::immortal' => ['MongoCursor', 'liveForever='=>'bool|true'],
'MongoGridFSCursor::info' => ['array'],
'MongoGridFSCursor::key' => ['string'],
'MongoGridFSCursor::limit' => ['MongoCursor', 'num'=>'int'],
'MongoGridFSCursor::maxTimeMS' => ['MongoCursor', 'ms'=>'int'],
'MongoGridFSCursor::next' => ['void'],
'MongoGridFSCursor::partial' => ['MongoCursor', 'okay='=>'bool|true'],
'MongoGridFSCursor::reset' => ['void'],
'MongoGridFSCursor::rewind' => ['void'],
'MongoGridFSCursor::setFlag' => ['MongoCursor', 'flag'=>'int', 'set='=>'bool|true'],
'MongoGridFSCursor::setReadPreference' => ['MongoCursor', 'read_preference'=>'string', 'tags'=>'array'],
'MongoGridFSCursor::skip' => ['MongoCursor', 'num'=>'int'],
'MongoGridFSCursor::slaveOkay' => ['MongoCursor', 'okay='=>'bool|true'],
'MongoGridFSCursor::snapshot' => ['MongoCursor'],
'MongoGridFSCursor::sort' => ['MongoCursor', 'fields'=>'array'],
'MongoGridFSCursor::tailable' => ['MongoCursor', 'tail='=>'bool|true'],
'MongoGridFSCursor::timeout' => ['MongoCursor', 'ms'=>'int'],
'MongoGridFSCursor::valid' => ['bool'],
'MongoGridfsFile::__construct' => ['void', 'gridfs'=>'MongoGridFS', 'file'=>'array'],
'MongoGridFSFile::getBytes' => ['string'],
'MongoGridFSFile::getFilename' => ['string'],
'MongoGridFSFile::getResource' => ['resource'],
'MongoGridFSFile::getSize' => ['int'],
'MongoGridFSFile::write' => ['int', 'filename='=>'string'],
'MongoId::__construct' => ['void', 'id='=>'string|MongoId'],
'MongoId::__set_state' => ['MongoId', 'props'=>'array'],
'MongoId::__toString' => ['string'],
'MongoId::getHostname' => ['string'],
'MongoId::getInc' => ['int'],
'MongoId::getPID' => ['int'],
'MongoId::getTimestamp' => ['int'],
'MongoId::isValid' => ['bool', 'value'=>'mixed'],
'MongoInsertBatch::__construct' => ['void', 'collection'=>'MongoCollection', 'write_options='=>'array'],
'MongoInt32::__construct' => ['void', 'value'=>'string'],
'MongoInt32::__toString' => ['string'],
'MongoInt64::__construct' => ['void', 'value'=>'string'],
'MongoInt64::__toString' => ['string'],
'MongoLog::getCallback' => ['callable'],
'MongoLog::getLevel' => ['int'],
'MongoLog::getModule' => ['int'],
'MongoLog::setCallback' => ['void', 'log_function'=>'callable'],
'MongoLog::setLevel' => ['void', 'level'=>'int'],
'MongoLog::setModule' => ['void', 'module'=>'int'],
'MongoPool::getSize' => ['int'],
'MongoPool::info' => ['array'],
'MongoPool::setSize' => ['bool', 'size'=>'int'],
'MongoRegex::__construct' => ['void', 'regex'=>'string'],
'MongoRegex::__toString' => ['string'],
'MongoResultException::__clone' => ['void'],
'MongoResultException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\Exception|?\Throwable'],
'MongoResultException::__toString' => ['string'],
'MongoResultException::__wakeup' => ['void'],
'MongoResultException::getCode' => ['int'],
'MongoResultException::getDocument' => ['array'],
'MongoResultException::getFile' => ['string'],
'MongoResultException::getLine' => ['int'],
'MongoResultException::getMessage' => ['string'],
'MongoResultException::getPrevious' => ['Exception|Throwable'],
'MongoResultException::getTrace' => ['array'],
'MongoResultException::getTraceAsString' => ['string'],
'MongoTimestamp::__construct' => ['void', 'sec='=>'int', 'inc='=>'int'],
'MongoTimestamp::__toString' => ['string'],
'MongoUpdateBatch::__construct' => ['void', 'collection'=>'MongoCollection', 'write_options='=>'array'],
'MongoUpdateBatch::add' => ['bool', 'item'=>'array'],
'MongoUpdateBatch::execute' => ['array', 'write_options'=>'array'],
'MongoWriteBatch::__construct' => ['void', 'collection'=>'MongoCollection', 'batch_type'=>'string', 'write_options'=>'array'],
'MongoWriteBatch::add' => ['bool', 'item'=>'array'],
'MongoWriteBatch::execute' => ['array', 'write_options'=>'array'],
'MongoWriteConcernException::__clone' => ['void'],
'MongoWriteConcernException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\Exception|?\Throwable'],
'MongoWriteConcernException::__toString' => ['string'],
'MongoWriteConcernException::__wakeup' => ['void'],
'MongoWriteConcernException::getCode' => ['int'],
'MongoWriteConcernException::getDocument' => ['array'],
'MongoWriteConcernException::getFile' => ['string'],
'MongoWriteConcernException::getLine' => ['int'],
'MongoWriteConcernException::getMessage' => ['string'],
'MongoWriteConcernException::getPrevious' => ['Exception|Throwable'],
'MongoWriteConcernException::getTrace' => ['array'],
'MongoWriteConcernException::getTraceAsString' => ['string'],
'monitor_custom_event' => ['void', 'class'=>'class', 'text'=>'text', 'severe='=>'severe', 'user_data='=>'user_data'],
'monitor_httperror_event' => ['void', 'error_code'=>'error_code', 'url'=>'url', 'severe='=>'severe'],
'monitor_license_info' => ['array'],
'monitor_pass_error' => ['void', 'errno'=>'', 'errstr'=>'', 'errfile'=>'', 'errline'=>''],
'monitor_set_aggregation_hint' => ['void', 'hint'=>'hint'],
'move_uploaded_file' => ['bool', 'path'=>'string', 'new_path'=>'string'],
'mqseries_back' => ['void', 'hconn'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_begin' => ['void', 'hconn'=>'resource', 'beginoptions'=>'array', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_close' => ['void', 'hconn'=>'resource', 'hobj'=>'resource', 'options'=>'int', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_cmit' => ['void', 'hconn'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_conn' => ['void', 'qmanagername'=>'string', 'hconn'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_connx' => ['void', 'qmanagername'=>'string', 'connoptions'=>'array', 'hconn'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_disc' => ['void', 'hconn'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_get' => ['void', 'hconn'=>'resource', 'hobj'=>'resource', 'md'=>'array', 'gmo'=>'array', 'bufferlength'=>'int', 'msg'=>'string', 'data_length'=>'int', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_inq' => ['void', 'hconn'=>'resource', 'hobj'=>'resource', 'selectorcount'=>'int', 'selectors'=>'array', 'intattrcount'=>'int', 'intattr'=>'resource', 'charattrlength'=>'int', 'charattr'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_open' => ['void', 'hconn'=>'resource', 'objdesc'=>'array', 'option'=>'int', 'hobj'=>'resource', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_put' => ['void', 'hconn'=>'resource', 'hobj'=>'resource', 'md'=>'array', 'pmo'=>'array', 'message'=>'string', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_put1' => ['void', 'hconn'=>'resource', 'objdesc'=>'resource', 'msgdesc'=>'resource', 'pmo'=>'resource', 'buffer'=>'string', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_set' => ['void', 'hconn'=>'resource', 'hobj'=>'resource', 'selectorcount'=>'int', 'selectors'=>'array', 'intattrcount'=>'int', 'intattrs'=>'array', 'charattrlength'=>'int', 'charattrs'=>'array', 'compcode'=>'resource', 'reason'=>'resource'],
'mqseries_strerror' => ['string', 'reason'=>'int'],
'ms_GetErrorObj' => ['errorObj'],
'ms_GetVersion' => ['string'],
'ms_GetVersionInt' => ['int'],
'ms_iogetStdoutBufferBytes' => ['int'],
'ms_iogetstdoutbufferstring' => ['void'],
'ms_ioinstallstdinfrombuffer' => ['void'],
'ms_ioinstallstdouttobuffer' => ['void'],
'ms_ioresethandlers' => ['void'],
'ms_iostripstdoutbuffercontentheaders' => ['void'],
'ms_iostripstdoutbuffercontenttype' => ['string'],
'ms_ResetErrorList' => ['void'],
'ms_TokenizeMap' => ['array', 'map_file_name'=>'string'],
'msession_connect' => ['bool', 'host'=>'string', 'port'=>'string'],
'msession_count' => ['int'],
'msession_create' => ['bool', 'session'=>'string', 'classname='=>'string', 'data='=>'string'],
'msession_destroy' => ['bool', 'name'=>'string'],
'msession_disconnect' => ['void'],
'msession_find' => ['array', 'name'=>'string', 'value'=>'string'],
'msession_get' => ['string', 'session'=>'string', 'name'=>'string', 'value'=>'string'],
'msession_get_array' => ['array', 'session'=>'string'],
'msession_get_data' => ['string', 'session'=>'string'],
'msession_inc' => ['string', 'session'=>'string', 'name'=>'string'],
'msession_list' => ['array'],
'msession_listvar' => ['array', 'name'=>'string'],
'msession_lock' => ['int', 'name'=>'string'],
'msession_plugin' => ['string', 'session'=>'string', 'val'=>'string', 'param='=>'string'],
'msession_randstr' => ['string', 'param'=>'int'],
'msession_set' => ['bool', 'session'=>'string', 'name'=>'string', 'value'=>'string'],
'msession_set_array' => ['void', 'session'=>'string', 'tuples'=>'array'],
'msession_set_data' => ['bool', 'session'=>'string', 'value'=>'string'],
'msession_timeout' => ['int', 'session'=>'string', 'param='=>'int'],
'msession_uniq' => ['string', 'param'=>'int', 'classname='=>'string', 'data='=>'string'],
'msession_unlock' => ['int', 'session'=>'string', 'key'=>'int'],
'msg_get_queue' => ['resource', 'key'=>'int', 'perms='=>'int'],
'msg_queue_exists' => ['bool', 'key'=>'int'],
'msg_receive' => ['bool', 'queue'=>'resource', 'desiredmsgtype'=>'int', '&w_msgtype'=>'int', 'maxsize'=>'int', '&w_message'=>'mixed', 'unserialize='=>'bool', 'flags='=>'int', '&w_errorcode='=>'int'],
'msg_remove_queue' => ['bool', 'queue'=>'resource'],
'msg_send' => ['bool', 'queue'=>'resource', 'msgtype'=>'int', 'message'=>'mixed', 'serialize='=>'bool', 'blocking='=>'bool', '&w_errorcode='=>'int'],
'msg_set_queue' => ['bool', 'queue'=>'resource', 'data'=>'array'],
'msg_stat_queue' => ['array', 'queue'=>'resource'],
'msgfmt_create' => ['MessageFormatter', 'locale'=>'string', 'pattern'=>'string'],
'msgfmt_format' => ['string', 'fmt'=>'messageformatter', 'args'=>'array'],
'msgfmt_format_message' => ['string', 'locale'=>'string', 'pattern'=>'string', 'args'=>'array'],
'msgfmt_get_error_code' => ['int', 'fmt'=>'messageformatter'],
'msgfmt_get_error_message' => ['string', 'fmt'=>'messageformatter'],
'msgfmt_get_locale' => ['string', 'formatter'=>'messageformatter'],
'msgfmt_get_pattern' => ['string', 'fmt'=>'messageformatter'],
'msgfmt_parse' => ['array', 'fmt'=>'messageformatter', 'value'=>'string'],
'msgfmt_parse_message' => ['array', 'locale'=>'string', 'pattern'=>'string', 'source'=>'string'],
'msgfmt_set_pattern' => ['bool', 'fmt'=>'messageformatter', 'pattern'=>'string'],
'msql_affected_rows' => ['int', 'result'=>'resource'],
'msql_close' => ['bool', 'link_identifier='=>'?resource'],
'msql_connect' => ['resource', 'hostname='=>'string'],
'msql_create_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource'],
'msql_data_seek' => ['bool', 'result'=>'resource', 'row_number'=>'int'],
'msql_db_query' => ['resource', 'database'=>'string', 'query'=>'string', 'link_identifier='=>'?resource'],
'msql_drop_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource'],
'msql_error' => ['string'],
'msql_fetch_array' => ['array', 'result'=>'resource', 'result_type='=>'int'],
'msql_fetch_field' => ['object', 'result'=>'resource', 'field_offset='=>'int'],
'msql_fetch_object' => ['object', 'result'=>'resource'],
'msql_fetch_row' => ['array', 'result'=>'resource'],
'msql_field_flags' => ['string', 'result'=>'resource', 'field_offset'=>'int'],
'msql_field_len' => ['int', 'result'=>'resource', 'field_offset'=>'int'],
'msql_field_name' => ['string', 'result'=>'resource', 'field_offset'=>'int'],
'msql_field_seek' => ['bool', 'result'=>'resource', 'field_offset'=>'int'],
'msql_field_table' => ['int', 'result'=>'resource', 'field_offset'=>'int'],
'msql_field_type' => ['string', 'result'=>'resource', 'field_offset'=>'int'],
'msql_free_result' => ['bool', 'result'=>'resource'],
'msql_list_dbs' => ['resource', 'link_identifier='=>'?resource'],
'msql_list_fields' => ['resource', 'database'=>'string', 'tablename'=>'string', 'link_identifier='=>'?resource'],
'msql_list_tables' => ['resource', 'database'=>'string', 'link_identifier='=>'?resource'],
'msql_num_fields' => ['int', 'result'=>'resource'],
'msql_num_rows' => ['int', 'query_identifier'=>'resource'],
'msql_pconnect' => ['resource', 'hostname='=>'string'],
'msql_query' => ['resource', 'query'=>'string', 'link_identifier='=>'?resource'],
'msql_result' => ['string', 'result'=>'resource', 'row'=>'int', 'field='=>'mixed'],
'msql_select_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'?resource'],
'mssql_bind' => ['bool', 'stmt'=>'resource', 'param_name'=>'string', 'var'=>'mixed', 'type'=>'int', 'is_output='=>'bool', 'is_null='=>'bool', 'maxlen='=>'int'],
'mssql_close' => ['bool', 'link_identifier='=>'resource'],
'mssql_connect' => ['resource', 'servername='=>'string', 'username='=>'string', 'password='=>'string', 'new_link='=>'bool'],
'mssql_data_seek' => ['bool', 'result_identifier'=>'resource', 'row_number'=>'int'],
'mssql_execute' => ['mixed', 'stmt'=>'resource', 'skip_results='=>'bool'],
'mssql_fetch_array' => ['array', 'result'=>'resource', 'result_type='=>'int'],
'mssql_fetch_assoc' => ['array', 'result_id'=>'resource'],
'mssql_fetch_batch' => ['int', 'result'=>'resource'],
'mssql_fetch_field' => ['object', 'result'=>'resource', 'field_offset='=>'int'],
'mssql_fetch_object' => ['object', 'result'=>'resource'],
'mssql_fetch_row' => ['array', 'result'=>'resource'],
'mssql_field_length' => ['int', 'result'=>'resource', 'offset='=>'int'],
'mssql_field_name' => ['string', 'result'=>'resource', 'offset='=>'int'],
'mssql_field_seek' => ['bool', 'result'=>'resource', 'field_offset'=>'int'],
'mssql_field_type' => ['string', 'result'=>'resource', 'offset='=>'int'],
'mssql_free_result' => ['bool', 'result'=>'resource'],
'mssql_free_statement' => ['bool', 'stmt'=>'resource'],
'mssql_get_last_message' => ['string'],
'mssql_guid_string' => ['string', 'binary'=>'string', 'short_format='=>'bool'],
'mssql_init' => ['resource', 'sp_name'=>'string', 'link_identifier='=>'resource'],
'mssql_min_error_severity' => ['void', 'severity'=>'int'],
'mssql_min_message_severity' => ['void', 'severity'=>'int'],
'mssql_next_result' => ['bool', 'result_id'=>'resource'],
'mssql_num_fields' => ['int', 'result'=>'resource'],
'mssql_num_rows' => ['int', 'result'=>'resource'],
'mssql_pconnect' => ['resource', 'servername='=>'string', 'username='=>'string', 'password='=>'string', 'new_link='=>'bool'],
'mssql_query' => ['mixed', 'query'=>'string', 'link_identifier='=>'resource', 'batch_size='=>'int'],
'mssql_result' => ['string', 'result'=>'resource', 'row'=>'int', 'field'=>'mixed'],
'mssql_rows_affected' => ['int', 'link_identifier'=>'resource'],
'mssql_select_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'resource'],
'mt_getrandmax' => ['int'],
'mt_rand' => ['int', 'min'=>'int', 'max'=>'int'],
'mt_rand\'1' => ['int'],
'mt_srand' => ['void', 'seed='=>'int', 'mode='=>'int'],
'MultipleIterator::__construct' => ['void', 'flags'=>'int'],
'MultipleIterator::attachIterator' => ['void', 'iterator'=>'iterator', 'infos='=>'string'],
'MultipleIterator::containsIterator' => ['bool', 'iterator'=>'iterator'],
'MultipleIterator::countIterators' => ['int'],
'MultipleIterator::current' => ['array'],
'MultipleIterator::detachIterator' => ['void', 'iterator'=>'iterator'],
'MultipleIterator::getFlags' => ['int'],
'MultipleIterator::key' => ['array'],
'MultipleIterator::next' => ['void'],
'MultipleIterator::rewind' => ['void'],
'MultipleIterator::setFlags' => ['int', 'flags'=>'int'],
'MultipleIterator::valid' => ['bool'],
'Mutex::create' => ['long', 'lock='=>'bool'],
'Mutex::destroy' => ['bool', 'mutex'=>'long'],
'Mutex::lock' => ['bool', 'mutex'=>'long'],
'Mutex::trylock' => ['bool', 'mutex'=>'long'],
'Mutex::unlock' => ['bool', 'mutex'=>'long', 'destroy='=>'bool'],
'mysql_affected_rows' => ['int', 'link_identifier='=>'resource'],
'mysql_client_encoding' => ['string', 'link_identifier='=>'resource'],
'mysql_close' => ['bool', 'link_identifier='=>'resource'],
'mysql_connect' => ['resource', 'server='=>'string', 'username='=>'string', 'password='=>'string', 'new_link='=>'bool', 'client_flags='=>'int'],
'mysql_create_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'resource'],
'mysql_data_seek' => ['bool', 'result'=>'resource', 'row_number'=>'int'],
'mysql_db_name' => ['string', 'result'=>'resource', 'row'=>'int', 'field='=>'mixed'],
'mysql_db_query' => ['resource', 'database'=>'string', 'query'=>'string', 'link_identifier='=>'resource'],
'mysql_drop_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'resource'],
'mysql_errno' => ['int', 'link_identifier='=>'resource'],
'mysql_error' => ['string', 'link_identifier='=>'resource'],
'mysql_escape_string' => ['string', 'unescaped_string'=>'string'],
'mysql_fetch_array' => ['array', 'result'=>'resource', 'result_type='=>'int'],
'mysql_fetch_assoc' => ['array', 'result'=>'resource'],
'mysql_fetch_field' => ['object', 'result'=>'resource', 'field_offset='=>'int'],
'mysql_fetch_lengths' => ['array', 'result'=>'resource'],
'mysql_fetch_object' => ['object', 'result'=>'resource', 'class_name='=>'string', 'params='=>'array'],
'mysql_fetch_row' => ['array', 'result'=>'resource'],
'mysql_field_flags' => ['string', 'result'=>'resource', 'field_offset'=>'int'],
'mysql_field_len' => ['int', 'result'=>'resource', 'field_offset'=>'int'],
'mysql_field_name' => ['string', 'result'=>'resource', 'field_offset'=>'int'],
'mysql_field_seek' => ['bool', 'result'=>'resource', 'field_offset'=>'int'],
'mysql_field_table' => ['string', 'result'=>'resource', 'field_offset'=>'int'],
'mysql_field_type' => ['string', 'result'=>'resource', 'field_offset'=>'int'],
'mysql_free_result' => ['bool', 'result'=>'resource'],
'mysql_get_client_info' => ['string'],
'mysql_get_host_info' => ['string', 'link_identifier='=>'resource'],
'mysql_get_proto_info' => ['int', 'link_identifier='=>'resource'],
'mysql_get_server_info' => ['string', 'link_identifier='=>'resource'],
'mysql_info' => ['string', 'link_identifier='=>'resource'],
'mysql_insert_id' => ['int', 'link_identifier='=>'resource'],
'mysql_list_dbs' => ['resource', 'link_identifier='=>'resource'],
'mysql_list_fields' => ['resource', 'database_name'=>'string', 'table_name'=>'string', 'link_identifier='=>'resource'],
'mysql_list_processes' => ['resource', 'link_identifier='=>'resource'],
'mysql_list_tables' => ['resource', 'database'=>'string', 'link_identifier='=>'resource'],
'mysql_num_fields' => ['int', 'result'=>'resource'],
'mysql_num_rows' => ['int', 'result'=>'resource'],
'mysql_pconnect' => ['resource', 'server='=>'string', 'username='=>'string', 'password='=>'string', 'client_flags='=>'int'],
'mysql_ping' => ['bool', 'link_identifier='=>'resource'],
'mysql_query' => ['resource', 'query'=>'string', 'link_identifier='=>'resource'],
'mysql_real_escape_string' => ['string', 'unescaped_string'=>'string', 'link_identifier='=>'resource'],
'mysql_result' => ['string', 'result'=>'resource', 'row'=>'int', 'field='=>'mixed'],
'mysql_select_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'resource'],
'mysql_set_charset' => ['bool', 'charset'=>'string', 'link_identifier='=>'resource'],
'mysql_stat' => ['string', 'link_identifier='=>'resource'],
'mysql_tablename' => ['string', 'result'=>'resource', 'i'=>'int'],
'mysql_thread_id' => ['int', 'link_identifier='=>'resource'],
'mysql_unbuffered_query' => ['resource', 'query'=>'string', 'link_identifier='=>'resource'],
'mysqli::__construct' => ['void', 'host='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'],
'mysqli::affected_rows' => ['int', 'link='=>'mysqli'],
'mysqli::autocommit' => ['bool', 'mode'=>'bool', 'link='=>'mysqli'],
'mysqli::begin_transaction' => ['bool', 'flags='=>'int', 'name='=>'string'],
'mysqli::change_user' => ['bool', 'user'=>'string', 'password'=>'string', 'database'=>'string', 'link='=>'mysqli'],
'mysqli::character_set_name' => ['string', 'link='=>'mysqli'],
'mysqli::client_info' => ['string', 'link='=>'mysqli'],
'mysqli::client_version' => ['int', 'link='=>'mysqli'],
'mysqli::close' => ['bool', 'link='=>'mysqli'],
'mysqli::commit' => ['bool', 'link='=>'mysqli'],
'mysqli::connect_errno' => ['int'],
'mysqli::connect_error' => ['string'],
'mysqli::debug' => ['bool', 'message'=>'string'],
'mysqli::disable_reads_from_master' => ['bool'],
'mysqli::dump_debug_info' => ['bool'],
'mysqli::errno' => ['int'],
'mysqli::error' => ['string'],
'mysqli::field_count' => ['int'],
'mysqli::get_charset' => ['object'],
'mysqli::get_client_info' => ['string'],
'mysqli::get_connection_stats' => ['array'],
'mysqli::get_warnings' => ['mysqli_warning'],
'mysqli::host_info' => ['string'],
'mysqli::info' => ['?string'],
'mysqli::init' => ['mysqli'],
'mysqli::insert_id' => ['int|string'],
'mysqli::kill' => ['bool', 'processid'=>'int', 'link='=>'mysqli'],
'mysqli::more_results' => ['bool'],
'mysqli::multi_query' => ['bool', 'query'=>'string'],
'mysqli::next_result' => ['bool'],
'mysqli::options' => ['bool', 'option'=>'int', 'value'=>''],
'mysqli::ping' => ['bool'],
'mysqli::poll' => ['int', 'read'=>'array', 'error'=>'array', 'reject'=>'array', 'sec'=>'int', 'usec='=>'int'],
'mysqli::prepare' => ['mysqli_stmt', 'query'=>'string', 'link='=>'mysqli'],
'mysqli::protocol_version' => ['int', 'link='=>'mysqli'],
'mysqli::query' => ['bool|mysqli_result', 'query'=>'string', 'resultmode='=>'int'],
'mysqli::real_connect' => ['bool', 'host='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string', 'flags='=>'int', 'link='=>'mysqli'],
'mysqli::real_escape_string' => ['string', 'escapestr'=>'string', 'link='=>'mysqli'],
'mysqli::real_query' => ['bool', 'query'=>'string', 'link='=>'mysqli'],
'mysqli::reap_async_query' => ['mysqli_result'],
'mysqli::release_savepoint' => ['bool', 'name'=>'string'],
'mysqli::rollback' => ['bool', 'link='=>'mysqli'],
'mysqli::rpl_query_type' => ['int', 'query'=>'string', 'link='=>'mysqli'],
'mysqli::savepoint' => ['bool', 'name'=>'string'],
'mysqli::select_db' => ['bool', 'dbname'=>'string', 'link='=>'mysqli'],
'mysqli::send_query' => ['bool', 'query'=>'string', 'link='=>'mysqli'],
'mysqli::server_info' => ['string', 'link='=>'mysqli'],
'mysqli::server_version' => ['int', 'link='=>'mysqli'],
'mysqli::set_charset' => ['bool', 'charset'=>'string', 'link='=>'mysqli'],
'mysqli::set_local_infile_default' => ['void', 'link='=>'mysqli'],
'mysqli::set_local_infile_handler' => ['bool', 'link'=>'mysqli', 'read_func='=>'callable'],
'mysqli::sqlstate' => ['string', 'link='=>'mysqli'],
'mysqli::ssl_set' => ['bool', 'key'=>'string', 'cert'=>'string', 'ca'=>'string', 'capath'=>'string', 'cipher'=>'string', 'link='=>'mysqli'],
'mysqli::stat' => ['string', 'link='=>'mysqli'],
'mysqli::stmt_init' => ['mysqli_stmt', 'link='=>'mysqli'],
'mysqli::store_result' => ['mysqli_result', 'link='=>'mysqli'],
'mysqli::thread_id' => ['int', 'link='=>'mysqli'],
'mysqli::thread_safe' => ['bool'],
'mysqli::use_result' => ['mysqli_result', 'link='=>'mysqli'],
'mysqli::warning_count' => ['int', 'link='=>'mysqli'],
'mysqli_affected_rows' => ['int', 'link'=>'mysqli'],
'mysqli_autocommit' => ['bool', 'link'=>'mysqli', 'mode'=>'bool'],
'mysqli_begin_transaction' => ['bool', 'link'=>'mysqli', 'flags'=>'int', 'name'=>'string'],
'mysqli_change_user' => ['bool', 'link'=>'mysqli', 'user'=>'string', 'password'=>'string', 'database'=>'string'],
'mysqli_character_set_name' => ['string', 'link'=>'mysqli'],
'mysqli_close' => ['bool', 'link'=>'mysqli'],
'mysqli_commit' => ['bool', 'link'=>'mysqli', 'flags='=>'int', 'name='=>'string'],
'mysqli_connect' => ['mysqli|false', 'hostname='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'],
'mysqli_connect_errno' => ['int'],
'mysqli_connect_error' => ['string'],
'mysqli_data_seek' => ['bool', 'result'=>'mysqli_result', 'offset'=>'int'],
'mysqli_debug' => ['', 'debug'=>'string'],
'mysqli_disable_reads_from_master' => ['bool', 'link'=>'mysqli'],
'mysqli_disable_rpl_parse' => ['bool', 'link'=>'mysqli'],
'mysqli_driver::embedded_server_end' => [''],
'mysqli_driver::embedded_server_start' => ['bool', 'start'=>'bool', 'arguments'=>'array', 'groups'=>'array'],
'mysqli_dump_debug_info' => ['bool', 'link'=>'mysqli'],
'mysqli_embedded_server_end' => [''],
'mysqli_embedded_server_start' => ['bool', 'start'=>'bool', 'arguments'=>'array', 'groups'=>'array'],
'mysqli_enable_reads_from_master' => ['bool', 'link'=>'mysqli'],
'mysqli_enable_rpl_parse' => ['bool', 'link'=>'mysqli'],
'mysqli_errno' => ['int', 'link'=>'mysqli'],
'mysqli_error' => ['string', 'link'=>'mysqli'],
'mysqli_error_list' => ['array', 'connection'=>'mysqli'],
'mysqli_fetch_all' => ['', 'result='=>'mysqli_result', 'resulttype='=>'int'],
'mysqli_fetch_array' => ['', 'result='=>'mysqli_result', 'resulttype='=>'int'],
'mysqli_fetch_assoc' => ['array<string,string>|null', 'result'=>'mysqli_result'],
'mysqli_fetch_field' => ['object', 'result'=>'mysqli_result'],
'mysqli_fetch_field_direct' => ['', 'result'=>'mysqli_result', 'offset'=>'int'],
'mysqli_fetch_fields' => ['array', 'result'=>'mysqli_result'],
'mysqli_fetch_lengths' => ['array', 'result'=>'mysqli_result'],
'mysqli_fetch_object' => ['', 'result='=>'mysqli_result', 'class_name='=>'string', 'ctor_params='=>'?array'],
'mysqli_fetch_row' => ['array', 'result'=>'mysqli_result'],
'mysqli_field_count' => ['int', 'link'=>'mysqli'],
'mysqli_field_seek' => ['bool', 'result'=>'mysqli_result', 'fieldnr'=>'int'],
'mysqli_field_tell' => ['int', 'result'=>'mysqli_result'],
'mysqli_free_result' => ['', 'link'=>'mysqli_result'],
'mysqli_get_cache_stats' => ['array'],
'mysqli_get_charset' => ['object', 'link'=>'mysqli'],
'mysqli_get_client_info' => ['string', 'link='=>'mysqli'],
'mysqli_get_client_stats' => ['array'],
'mysqli_get_client_version' => ['int', 'link'=>'mysqli'],
'mysqli_get_connection_stats' => ['array', 'link'=>'mysqli'],
'mysqli_get_host_info' => ['string', 'link'=>'mysqli'],
'mysqli_get_links_stats' => ['array'],
'mysqli_get_proto_info' => ['int', 'link'=>'mysqli'],
'mysqli_get_server_info' => ['string', 'link'=>'mysqli'],
'mysqli_get_server_version' => ['int', 'link'=>'mysqli'],
'mysqli_get_warnings' => ['object', 'link'=>'mysqli'],
'mysqli_info' => ['?string', 'link'=>'mysqli'],
'mysqli_init' => ['mysqli'],
'mysqli_insert_id' => ['int|string', 'link'=>'mysqli'],
'mysqli_kill' => ['bool', 'link'=>'mysqli', 'processid'=>'int'],
'mysqli_link_construct' => ['object'],
'mysqli_master_query' => ['bool', 'link'=>'mysqli', 'query'=>'string'],
'mysqli_more_results' => ['bool', 'link'=>'mysqli'],
'mysqli_multi_query' => ['bool', 'link'=>'mysqli', 'query'=>'string'],
'mysqli_next_result' => ['bool', 'link'=>'mysqli'],
'mysqli_num_fields' => ['int', 'link'=>'mysqli_result'],
'mysqli_num_rows' => ['int', 'link'=>'mysqli_result'],
'mysqli_options' => ['bool', 'link'=>'mysqli', 'option'=>'int', 'value'=>''],
'mysqli_ping' => ['bool', 'link'=>'mysqli'],
'mysqli_poll' => ['int', 'read'=>'array', 'write'=>'array', 'error'=>'array', 'sec'=>'int', 'usec='=>'int'],
'mysqli_prepare' => ['mysqli_stmt', 'link'=>'mysqli', 'query'=>'string'],
'mysqli_query' => ['bool|mysqli_result', 'link'=>'mysqli', 'query='=>'string', 'resultmode='=>'int'],
'mysqli_real_connect' => ['bool', 'link='=>'mysqli', 'hostname='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string', 'flags='=>'int'],
'mysqli_real_escape_string' => ['string', 'link'=>'mysqli', 'escapestr'=>'string'],
'mysqli_real_query' => ['bool', 'link'=>'mysqli', 'query'=>'string'],
'mysqli_reap_async_query' => ['mysqli_result', 'link'=>'mysqli'],
'mysqli_refresh' => ['bool', 'link'=>'mysqli', 'options'=>'int'],
'mysqli_release_savepoint' => ['bool', 'link'=>'mysqli', 'name'=>'string'],
'mysqli_report' => ['bool', 'flags'=>'int'],
'mysqli_result::__construct' => ['void', 'link'=>'mysqli', 'resultmode='=>'int'],
'mysqli_result::close' => ['void'],
'mysqli_result::current_field' => ['int'],
'mysqli_result::data_seek' => ['bool', 'offset'=>'int'],
'mysqli_result::fetch_all' => ['', 'resulttype='=>'int'],
'mysqli_result::fetch_array' => ['', 'resulttype='=>'int'],
'mysqli_result::fetch_assoc' => ['array<string,string>|null'],
'mysqli_result::fetch_field' => ['object'],
'mysqli_result::fetch_field_direct' => ['object', 'fieldnr'=>'int'],
'mysqli_result::fetch_fields' => ['array'],
'mysqli_result::fetch_object' => ['object', 'class_name='=>'string', 'params='=>'array'],
'mysqli_result::fetch_row' => [''],
'mysqli_result::field_count' => ['int'],
'mysqli_result::field_seek' => ['bool', 'fieldnr'=>'int'],
'mysqli_result::free' => [''],
'mysqli_result::free_result' => ['void'],
'mysqli_result::lengths' => ['array'],
'mysqli_result::num_rows' => ['int'],
'mysqli_rollback' => ['bool', 'link'=>'mysqli'],
'mysqli_rpl_parse_enabled' => ['int', 'link'=>'mysqli'],
'mysqli_rpl_probe' => ['bool', 'link'=>'mysqli'],
'mysqli_rpl_query_type' => ['int', 'query'=>'string', 'link'=>'mysqli'],
'mysqli_savepoint' => ['bool', 'link'=>'mysqli', 'name'=>'string'],
'mysqli_savepoint_libmysql' => ['bool'],
'mysqli_select_db' => ['bool', 'link'=>'mysqli', 'dbname'=>'string'],
'mysqli_send_query' => ['bool', 'query'=>'string', 'link'=>'mysqli'],
'mysqli_set_charset' => ['bool', 'link'=>'mysqli', 'csname'=>'string'],
'mysqli_set_local_infile_default' => ['', 'link'=>'mysqli'],
'mysqli_set_local_infile_handler' => ['bool', 'link'=>'mysqli', 'read_func'=>'callable'],
'mysqli_slave_query' => ['bool', 'link'=>'mysqli', 'query'=>'string'],
'mysqli_sqlstate' => ['string', 'link'=>'mysqli'],
'mysqli_ssl_set' => ['bool', 'link'=>'mysqli', 'key'=>'string', 'cert'=>'string', 'ca'=>'string', 'capath'=>'string', 'cipher'=>'string'],
'mysqli_stat' => ['', 'link'=>'mysqli'],
'mysqli_stmt::__construct' => ['void', 'link='=>'mysqli', 'query='=>'string'],
'mysqli_stmt::affected_rows' => ['int'],
'mysqli_stmt::attr_get' => ['false|int', 'attr'=>'int'],
'mysqli_stmt::attr_set' => ['bool', 'attr'=>'int', 'mode'=>'int'],
'mysqli_stmt::bind_param' => ['bool', 'types'=>'string', 'var1'=>'', '...args='=>''],
'mysqli_stmt::bind_result' => ['bool', 'var1'=>'', '...args='=>''],
'mysqli_stmt::close' => ['bool'],
'mysqli_stmt::data_seek' => ['', 'offset'=>'int'],
'mysqli_stmt::errno' => ['int'],
'mysqli_stmt::error' => ['string'],
'mysqli_stmt::error_list' => ['array'],
'mysqli_stmt::execute' => ['bool'],
'mysqli_stmt::fetch' => ['bool'],
'mysqli_stmt::field_count' => ['int'],
'mysqli_stmt::free_result' => ['', 'stmt='=>'mysqli_stmt'],
'mysqli_stmt::get_result' => ['mysqli_result|false'],
'mysqli_stmt::get_warnings' => ['object', 'stmt='=>'mysqli_stmt'],
'mysqli_stmt::insert_id' => ['mixed'],
'mysqli_stmt::more_results' => ['bool'],
'mysqli_stmt::next_result' => ['bool'],
'mysqli_stmt::num_rows' => ['int'],
'mysqli_stmt::param_count' => ['int'],
'mysqli_stmt::prepare' => ['bool', 'query'=>'string'],
'mysqli_stmt::reset' => ['bool'],
'mysqli_stmt::result_metadata' => ['mysqli_result'],
'mysqli_stmt::send_long_data' => ['bool', 'param_nr'=>'int', 'data'=>'string'],
'mysqli_stmt::sqlstate' => ['string'],
'mysqli_stmt::store_result' => ['bool'],
'mysqli_stmt_affected_rows' => ['int', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_attr_get' => ['false|int', 'stmt'=>'mysqli_stmt', 'attr'=>'int'],
'mysqli_stmt_attr_set' => ['bool', 'stmt'=>'mysqli_stmt', 'attr'=>'int', 'mode'=>'int'],
'mysqli_stmt_bind_param' => ['bool', 'stmt'=>'mysqli_stmt', 'types'=>'string', 'variable'=>'', '...args='=>''],
'mysqli_stmt_bind_result' => ['bool', 'stmt'=>'mysqli_stmt', 'var1='=>'', '...args='=>''],
'mysqli_stmt_close' => ['bool', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_data_seek' => ['', 'stmt'=>'mysqli_stmt', 'offset'=>'int'],
'mysqli_stmt_errno' => ['int', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_error' => ['string', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_error_list' => ['array', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_execute' => ['bool', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_fetch' => ['bool', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_field_count' => ['int', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_free_result' => [''],
'mysqli_stmt_get_result' => ['mysqli_result|false', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_get_warnings' => ['object', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_init' => ['mysqli_stmt', 'link'=>'mysqli'],
'mysqli_stmt_insert_id' => ['', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_more_results' => ['bool', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_next_result' => ['bool', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_num_rows' => ['int', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_param_count' => ['int', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_prepare' => ['bool', 'stmt'=>'mysqli_stmt', 'query'=>'string'],
'mysqli_stmt_reset' => ['bool', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_result_metadata' => ['mysqli_result', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_send_long_data' => ['bool', 'stmt'=>'mysqli_stmt', 'param_nr'=>'int', 'data'=>'string'],
'mysqli_stmt_sqlstate' => ['string', 'stmt'=>'mysqli_stmt'],
'mysqli_stmt_store_result' => ['bool', 'stmt'=>'mysqli_stmt'],
'mysqli_store_result' => ['mysqli_result|false', 'link'=>'mysqli', 'flags='=>'int'],
'mysqli_thread_id' => ['int', 'link'=>'mysqli'],
'mysqli_thread_safe' => ['bool'],
'mysqli_use_result' => ['mysqli_result|false', 'link'=>'mysqli'],
'mysqli_warning::__construct' => ['void'],
'mysqli_warning::next' => ['void'],
'mysqli_warning_count' => ['int', 'link'=>'mysqli'],
'mysqlnd_memcache_get_config' => ['array', 'connection'=>'mixed'],
'mysqlnd_memcache_set' => ['bool', 'mysql_connection'=>'mixed', 'memcache_connection='=>'Memcached', 'pattern='=>'string', 'callback='=>'callable'],
'mysqlnd_ms_dump_servers' => ['array', 'connection'=>'mixed'],
'mysqlnd_ms_fabric_select_global' => ['array', 'connection'=>'mixed', 'table_name'=>'mixed'],
'mysqlnd_ms_fabric_select_shard' => ['array', 'connection'=>'mixed', 'table_name'=>'mixed', 'shard_key'=>'mixed'],
'mysqlnd_ms_get_last_gtid' => ['string', 'connection'=>'mixed'],
'mysqlnd_ms_get_last_used_connection' => ['array', 'connection'=>'mixed'],
'mysqlnd_ms_get_stats' => ['array'],
'mysqlnd_ms_match_wild' => ['bool', 'table_name'=>'string', 'wildcard'=>'string'],
'mysqlnd_ms_query_is_select' => ['int', 'query'=>'string'],
'mysqlnd_ms_set_qos' => ['bool', 'connection'=>'mixed', 'service_level'=>'int', 'service_level_option='=>'int', 'option_value='=>'mixed'],
'mysqlnd_ms_set_user_pick_server' => ['bool', 'function'=>'string'],
'mysqlnd_ms_xa_begin' => ['int', 'connection'=>'mixed', 'gtrid'=>'string', 'timeout='=>'int'],
'mysqlnd_ms_xa_commit' => ['int', 'connection'=>'mixed', 'gtrid'=>'string'],
'mysqlnd_ms_xa_gc' => ['int', 'connection'=>'mixed', 'gtrid='=>'string', 'ignore_max_retries='=>'bool'],
'mysqlnd_ms_xa_rollback' => ['int', 'connection'=>'mixed', 'gtrid'=>'string'],
'mysqlnd_qc_change_handler' => ['bool', 'handler'=>''],
'mysqlnd_qc_clear_cache' => ['bool'],
'mysqlnd_qc_get_available_handlers' => ['array'],
'mysqlnd_qc_get_cache_info' => ['array'],
'mysqlnd_qc_get_core_stats' => ['array'],
'mysqlnd_qc_get_handler' => ['array'],
'mysqlnd_qc_get_normalized_query_trace_log' => ['array'],
'mysqlnd_qc_get_query_trace_log' => ['array'],
'mysqlnd_qc_set_cache_condition' => ['bool', 'condition_type'=>'int', 'condition'=>'mixed', 'condition_option'=>'mixed'],
'mysqlnd_qc_set_is_select' => ['mixed', 'callback'=>'string'],
'mysqlnd_qc_set_storage_handler' => ['bool', 'handler'=>'string'],
'mysqlnd_qc_set_user_handlers' => ['bool', 'get_hash'=>'string', 'find_query_in_cache'=>'string', 'return_to_cache'=>'string', 'add_query_to_cache_if_not_exists'=>'string', 'query_is_select'=>'string', 'update_query_run_time_stats'=>'string', 'get_stats'=>'string', 'clear_cache'=>'string'],
'mysqlnd_uh_convert_to_mysqlnd' => ['resource', '&rw_mysql_connection'=>'mysqli'],
'mysqlnd_uh_set_connection_proxy' => ['bool', '&rw_connection_proxy'=>'MysqlndUhConnection', '&rw_mysqli_connection='=>'mysqli'],
'mysqlnd_uh_set_statement_proxy' => ['bool', '&rw_statement_proxy'=>'MysqlndUhStatement'],
'MysqlndUhConnection::__construct' => ['void'],
'MysqlndUhConnection::changeUser' => ['bool', 'connection'=>'mysqlnd_connection', 'user'=>'string', 'password'=>'string', 'database'=>'string', 'silent'=>'bool', 'passwd_len'=>'int'],
'MysqlndUhConnection::charsetName' => ['string', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::close' => ['bool', 'connection'=>'mysqlnd_connection', 'close_type'=>'int'],
'MysqlndUhConnection::connect' => ['bool', 'connection'=>'mysqlnd_connection', 'host'=>'string', 'use"'=>'string', 'password'=>'string', 'database'=>'string', 'port'=>'int', 'socket'=>'string', 'mysql_flags'=>'int'],
'MysqlndUhConnection::endPSession' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::escapeString' => ['string', 'connection'=>'mysqlnd_connection', 'escape_string'=>'string'],
'MysqlndUhConnection::getAffectedRows' => ['int', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getErrorNumber' => ['int', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getErrorString' => ['string', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getFieldCount' => ['int', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getHostInformation' => ['string', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getLastInsertId' => ['int', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getLastMessage' => ['void', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getProtocolInformation' => ['string', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getServerInformation' => ['string', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getServerStatistics' => ['string', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getServerVersion' => ['int', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getSqlstate' => ['string', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getStatistics' => ['array', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getThreadId' => ['int', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::getWarningCount' => ['int', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::init' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::killConnection' => ['bool', 'connection'=>'mysqlnd_connection', 'pid'=>'int'],
'MysqlndUhConnection::listFields' => ['array', 'connection'=>'mysqlnd_connection', 'table'=>'string', 'achtung_wild'=>'string'],
'MysqlndUhConnection::listMethod' => ['void', 'connection'=>'mysqlnd_connection', 'query'=>'string', 'achtung_wild'=>'string', 'par1'=>'string'],
'MysqlndUhConnection::moreResults' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::nextResult' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::ping' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::query' => ['bool', 'connection'=>'mysqlnd_connection', 'query'=>'string'],
'MysqlndUhConnection::queryReadResultsetHeader' => ['bool', 'connection'=>'mysqlnd_connection', 'mysqlnd_stmt'=>'mysqlnd_statement'],
'MysqlndUhConnection::reapQuery' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::refreshServer' => ['bool', 'connection'=>'mysqlnd_connection', 'options'=>'int'],
'MysqlndUhConnection::restartPSession' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::selectDb' => ['bool', 'connection'=>'mysqlnd_connection', 'database'=>'string'],
'MysqlndUhConnection::sendClose' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::sendQuery' => ['bool', 'connection'=>'mysqlnd_connection', 'query'=>'string'],
'MysqlndUhConnection::serverDumpDebugInformation' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::setAutocommit' => ['bool', 'connection'=>'mysqlnd_connection', 'mode'=>'int'],
'MysqlndUhConnection::setCharset' => ['bool', 'connection'=>'mysqlnd_connection', 'charset'=>'string'],
'MysqlndUhConnection::setClientOption' => ['bool', 'connection'=>'mysqlnd_connection', 'option'=>'int', 'value'=>'int'],
'MysqlndUhConnection::setServerOption' => ['void', 'connection'=>'mysqlnd_connection', 'option'=>'int'],
'MysqlndUhConnection::shutdownServer' => ['void', 'MYSQLND_UH_RES_MYSQLND_NAME'=>'string', 'level'=>'string'],
'MysqlndUhConnection::simpleCommand' => ['bool', 'connection'=>'mysqlnd_connection', 'command'=>'int', 'arg'=>'string', 'ok_packet'=>'int', 'silent'=>'bool', 'ignore_upsert_status'=>'bool'],
'MysqlndUhConnection::simpleCommandHandleResponse' => ['bool', 'connection'=>'mysqlnd_connection', 'ok_packet'=>'int', 'silent'=>'bool', 'command'=>'int', 'ignore_upsert_status'=>'bool'],
'MysqlndUhConnection::sslSet' => ['bool', 'connection'=>'mysqlnd_connection', 'key'=>'string', 'cert'=>'string', 'ca'=>'string', 'capath'=>'string', 'cipher'=>'string'],
'MysqlndUhConnection::stmtInit' => ['resource', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::storeResult' => ['resource', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::txCommit' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::txRollback' => ['bool', 'connection'=>'mysqlnd_connection'],
'MysqlndUhConnection::useResult' => ['resource', 'connection'=>'mysqlnd_connection'],
'MysqlndUhPreparedStatement::__construct' => ['void'],
'MysqlndUhPreparedStatement::execute' => ['bool', 'statement'=>'mysqlnd_prepared_statement'],
'MysqlndUhPreparedStatement::prepare' => ['bool', 'statement'=>'mysqlnd_prepared_statement', 'query'=>'string'],
'natcasesort' => ['bool', '&rw_array_arg'=>'array'],
'natsort' => ['bool', '&rw_array_arg'=>'array'],
'ncurses_addch' => ['int', 'ch'=>'int'],
'ncurses_addchnstr' => ['int', 's'=>'string', 'n'=>'int'],
'ncurses_addchstr' => ['int', 's'=>'string'],
'ncurses_addnstr' => ['int', 's'=>'string', 'n'=>'int'],
'ncurses_addstr' => ['int', 'text'=>'string'],
'ncurses_assume_default_colors' => ['int', 'fg'=>'int', 'bg'=>'int'],
'ncurses_attroff' => ['int', 'attributes'=>'int'],
'ncurses_attron' => ['int', 'attributes'=>'int'],
'ncurses_attrset' => ['int', 'attributes'=>'int'],
'ncurses_baudrate' => ['int'],
'ncurses_beep' => ['int'],
'ncurses_bkgd' => ['int', 'attrchar'=>'int'],
'ncurses_bkgdset' => ['void', 'attrchar'=>'int'],
'ncurses_border' => ['int', 'left'=>'int', 'right'=>'int', 'top'=>'int', 'bottom'=>'int', 'tl_corner'=>'int', 'tr_corner'=>'int', 'bl_corner'=>'int', 'br_corner'=>'int'],
'ncurses_bottom_panel' => ['int', 'panel'=>'resource'],
'ncurses_can_change_color' => ['bool'],
'ncurses_cbreak' => ['bool'],
'ncurses_clear' => ['bool'],
'ncurses_clrtobot' => ['bool'],
'ncurses_clrtoeol' => ['bool'],
'ncurses_color_content' => ['int', 'color'=>'int', 'r'=>'int', 'g'=>'int', 'b'=>'int'],
'ncurses_color_set' => ['int', 'pair'=>'int'],
'ncurses_curs_set' => ['int', 'visibility'=>'int'],
'ncurses_def_prog_mode' => ['bool'],
'ncurses_def_shell_mode' => ['bool'],
'ncurses_define_key' => ['int', 'definition'=>'string', 'keycode'=>'int'],
'ncurses_del_panel' => ['bool', 'panel'=>'resource'],
'ncurses_delay_output' => ['int', 'milliseconds'=>'int'],
'ncurses_delch' => ['bool'],
'ncurses_deleteln' => ['bool'],
'ncurses_delwin' => ['bool', 'window'=>'resource'],
'ncurses_doupdate' => ['bool'],
'ncurses_echo' => ['bool'],
'ncurses_echochar' => ['int', 'character'=>'int'],
'ncurses_end' => ['int'],
'ncurses_erase' => ['bool'],
'ncurses_erasechar' => ['string'],
'ncurses_filter' => ['void'],
'ncurses_flash' => ['bool'],
'ncurses_flushinp' => ['bool'],
'ncurses_getch' => ['int'],
'ncurses_getmaxyx' => ['void', 'window'=>'resource', 'y'=>'int', 'x'=>'int'],
'ncurses_getmouse' => ['bool', 'mevent'=>'array'],
'ncurses_getyx' => ['void', 'window'=>'resource', 'y'=>'int', 'x'=>'int'],
'ncurses_halfdelay' => ['int', 'tenth'=>'int'],
'ncurses_has_colors' => ['bool'],
'ncurses_has_ic' => ['bool'],
'ncurses_has_il' => ['bool'],
'ncurses_has_key' => ['int', 'keycode'=>'int'],
'ncurses_hide_panel' => ['int', 'panel'=>'resource'],
'ncurses_hline' => ['int', 'charattr'=>'int', 'n'=>'int'],
'ncurses_inch' => ['string'],
'ncurses_init' => ['void'],
'ncurses_init_color' => ['int', 'color'=>'int', 'r'=>'int', 'g'=>'int', 'b'=>'int'],
'ncurses_init_pair' => ['int', 'pair'=>'int', 'fg'=>'int', 'bg'=>'int'],
'ncurses_insch' => ['int', 'character'=>'int'],
'ncurses_insdelln' => ['int', 'count'=>'int'],
'ncurses_insertln' => ['int'],
'ncurses_insstr' => ['int', 'text'=>'string'],
'ncurses_instr' => ['int', 'buffer'=>'string'],
'ncurses_isendwin' => ['bool'],
'ncurses_keyok' => ['int', 'keycode'=>'int', 'enable'=>'bool'],
'ncurses_keypad' => ['int', 'window'=>'resource', 'bf'=>'bool'],
'ncurses_killchar' => ['string'],
'ncurses_longname' => ['string'],
'ncurses_meta' => ['int', 'window'=>'resource', '8bit'=>'bool'],
'ncurses_mouse_trafo' => ['bool', 'y'=>'int', 'x'=>'int', 'toscreen'=>'bool'],
'ncurses_mouseinterval' => ['int', 'milliseconds'=>'int'],
'ncurses_mousemask' => ['int', 'newmask'=>'int', 'oldmask'=>'int'],
'ncurses_move' => ['int', 'y'=>'int', 'x'=>'int'],
'ncurses_move_panel' => ['int', 'panel'=>'resource', 'startx'=>'int', 'starty'=>'int'],
'ncurses_mvaddch' => ['int', 'y'=>'int', 'x'=>'int', 'c'=>'int'],
'ncurses_mvaddchnstr' => ['int', 'y'=>'int', 'x'=>'int', 's'=>'string', 'n'=>'int'],
'ncurses_mvaddchstr' => ['int', 'y'=>'int', 'x'=>'int', 's'=>'string'],
'ncurses_mvaddnstr' => ['int', 'y'=>'int', 'x'=>'int', 's'=>'string', 'n'=>'int'],
'ncurses_mvaddstr' => ['int', 'y'=>'int', 'x'=>'int', 's'=>'string'],
'ncurses_mvcur' => ['int', 'old_y'=>'int', 'old_x'=>'int', 'new_y'=>'int', 'new_x'=>'int'],
'ncurses_mvdelch' => ['int', 'y'=>'int', 'x'=>'int'],
'ncurses_mvgetch' => ['int', 'y'=>'int', 'x'=>'int'],
'ncurses_mvhline' => ['int', 'y'=>'int', 'x'=>'int', 'attrchar'=>'int', 'n'=>'int'],
'ncurses_mvinch' => ['int', 'y'=>'int', 'x'=>'int'],
'ncurses_mvvline' => ['int', 'y'=>'int', 'x'=>'int', 'attrchar'=>'int', 'n'=>'int'],
'ncurses_mvwaddstr' => ['int', 'window'=>'resource', 'y'=>'int', 'x'=>'int', 'text'=>'string'],
'ncurses_napms' => ['int', 'milliseconds'=>'int'],
'ncurses_new_panel' => ['resource', 'window'=>'resource'],
'ncurses_newpad' => ['resource', 'rows'=>'int', 'cols'=>'int'],
'ncurses_newwin' => ['resource', 'rows'=>'int', 'cols'=>'int', 'y'=>'int', 'x'=>'int'],
'ncurses_nl' => ['bool'],
'ncurses_nocbreak' => ['bool'],
'ncurses_noecho' => ['bool'],
'ncurses_nonl' => ['bool'],
'ncurses_noqiflush' => ['void'],
'ncurses_noraw' => ['bool'],
'ncurses_pair_content' => ['int', 'pair'=>'int', 'f'=>'int', 'b'=>'int'],
'ncurses_panel_above' => ['resource', 'panel'=>'resource'],
'ncurses_panel_below' => ['resource', 'panel'=>'resource'],
'ncurses_panel_window' => ['resource', 'panel'=>'resource'],
'ncurses_pnoutrefresh' => ['int', 'pad'=>'resource', 'pminrow'=>'int', 'pmincol'=>'int', 'sminrow'=>'int', 'smincol'=>'int', 'smaxrow'=>'int', 'smaxcol'=>'int'],
'ncurses_prefresh' => ['int', 'pad'=>'resource', 'pminrow'=>'int', 'pmincol'=>'int', 'sminrow'=>'int', 'smincol'=>'int', 'smaxrow'=>'int', 'smaxcol'=>'int'],
'ncurses_putp' => ['int', 'text'=>'string'],
'ncurses_qiflush' => ['void'],
'ncurses_raw' => ['bool'],
'ncurses_refresh' => ['int', 'ch'=>'int'],
'ncurses_replace_panel' => ['int', 'panel'=>'resource', 'window'=>'resource'],
'ncurses_reset_prog_mode' => ['int'],
'ncurses_reset_shell_mode' => ['int'],
'ncurses_resetty' => ['bool'],
'ncurses_savetty' => ['bool'],
'ncurses_scr_dump' => ['int', 'filename'=>'string'],
'ncurses_scr_init' => ['int', 'filename'=>'string'],
'ncurses_scr_restore' => ['int', 'filename'=>'string'],
'ncurses_scr_set' => ['int', 'filename'=>'string'],
'ncurses_scrl' => ['int', 'count'=>'int'],
'ncurses_show_panel' => ['int', 'panel'=>'resource'],
'ncurses_slk_attr' => ['int'],
'ncurses_slk_attroff' => ['int', 'intarg'=>'int'],
'ncurses_slk_attron' => ['int', 'intarg'=>'int'],
'ncurses_slk_attrset' => ['int', 'intarg'=>'int'],
'ncurses_slk_clear' => ['bool'],
'ncurses_slk_color' => ['int', 'intarg'=>'int'],
'ncurses_slk_init' => ['bool', 'format'=>'int'],
'ncurses_slk_noutrefresh' => ['bool'],
'ncurses_slk_refresh' => ['int'],
'ncurses_slk_restore' => ['int'],
'ncurses_slk_set' => ['bool', 'labelnr'=>'int', 'label'=>'string', 'format'=>'int'],
'ncurses_slk_touch' => ['int'],
'ncurses_standend' => ['int'],
'ncurses_standout' => ['int'],
'ncurses_start_color' => ['int'],
'ncurses_termattrs' => ['bool'],
'ncurses_termname' => ['string'],
'ncurses_timeout' => ['void', 'millisec'=>'int'],
'ncurses_top_panel' => ['int', 'panel'=>'resource'],
'ncurses_typeahead' => ['int', 'fd'=>'int'],
'ncurses_ungetch' => ['int', 'keycode'=>'int'],
'ncurses_ungetmouse' => ['bool', 'mevent'=>'array'],
'ncurses_update_panels' => ['void'],
'ncurses_use_default_colors' => ['bool'],
'ncurses_use_env' => ['void', 'flag'=>'bool'],
'ncurses_use_extended_names' => ['int', 'flag'=>'bool'],
'ncurses_vidattr' => ['int', 'intarg'=>'int'],
'ncurses_vline' => ['int', 'charattr'=>'int', 'n'=>'int'],
'ncurses_waddch' => ['int', 'window'=>'resource', 'ch'=>'int'],
'ncurses_waddstr' => ['int', 'window'=>'resource', 'str'=>'string', 'n='=>'int'],
'ncurses_wattroff' => ['int', 'window'=>'resource', 'attrs'=>'int'],
'ncurses_wattron' => ['int', 'window'=>'resource', 'attrs'=>'int'],
'ncurses_wattrset' => ['int', 'window'=>'resource', 'attrs'=>'int'],
'ncurses_wborder' => ['int', 'window'=>'resource', 'left'=>'int', 'right'=>'int', 'top'=>'int', 'bottom'=>'int', 'tl_corner'=>'int', 'tr_corner'=>'int', 'bl_corner'=>'int', 'br_corner'=>'int'],
'ncurses_wclear' => ['int', 'window'=>'resource'],
'ncurses_wcolor_set' => ['int', 'window'=>'resource', 'color_pair'=>'int'],
'ncurses_werase' => ['int', 'window'=>'resource'],
'ncurses_wgetch' => ['int', 'window'=>'resource'],
'ncurses_whline' => ['int', 'window'=>'resource', 'charattr'=>'int', 'n'=>'int'],
'ncurses_wmouse_trafo' => ['bool', 'window'=>'resource', 'y'=>'int', 'x'=>'int', 'toscreen'=>'bool'],
'ncurses_wmove' => ['int', 'window'=>'resource', 'y'=>'int', 'x'=>'int'],
'ncurses_wnoutrefresh' => ['int', 'window'=>'resource'],
'ncurses_wrefresh' => ['int', 'window'=>'resource'],
'ncurses_wstandend' => ['int', 'window'=>'resource'],
'ncurses_wstandout' => ['int', 'window'=>'resource'],
'ncurses_wvline' => ['int', 'window'=>'resource', 'charattr'=>'int', 'n'=>'int'],
'newrelic_add_custom_parameter' => ['bool', 'key'=>'string', 'value'=>''],
'newrelic_add_custom_tracer' => ['bool', 'function_name'=>'string'],
'newrelic_background_job' => ['void', 'flag='=>'bool'],
'newrelic_capture_params' => ['void', 'enable='=>'bool'],
'newrelic_custom_metric' => ['bool', 'metric_name'=>'string', 'value'=>'float'],
'newrelic_disable_autorum' => ['bool'],
'newrelic_end_of_transaction' => ['void'],
'newrelic_end_transaction' => ['bool', 'ignore='=>'bool'],
'newrelic_get_browser_timing_footer' => ['string', 'include_tags='=>'bool'],
'newrelic_get_browser_timing_header' => ['string', 'include_tags='=>'bool'],
'newrelic_ignore_apdex' => ['void'],
'newrelic_ignore_transaction' => ['void'],
'newrelic_name_transaction' => ['bool', 'name'=>'string'],
'newrelic_notice_error' => ['void', 'message'=>'string', 'exception='=>'exception'],
'newrelic_notice_error\'1' => ['void', 'unused_1'=>'string', 'message'=>'string', 'unused_2'=>'string', 'unused_3'=>'int', 'unused_4='=>''],
'newrelic_record_custom_event' => ['void', 'name'=>'string', 'attributes'=>'array'],
'newrelic_record_datastore_segment' => ['void', 'func'=>'callable', 'parameters'=>'array'],
'newrelic_set_appname' => ['bool', 'name'=>'string', 'license='=>'string', 'xmit='=>'bool'],
'newrelic_set_user_attributes' => ['bool', 'user'=>'string', 'account'=>'string', 'product'=>'string'],
'newrelic_start_transaction' => ['bool', 'appname'=>'string', 'license='=>'string'],
'newt_bell' => ['void'],
'newt_button' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string'],
'newt_button_bar' => ['resource', 'buttons'=>'array'],
'newt_centered_window' => ['int', 'width'=>'int', 'height'=>'int', 'title='=>'string'],
'newt_checkbox' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string', 'def_value'=>'string', 'seq='=>'string'],
'newt_checkbox_get_value' => ['string', 'checkbox'=>'resource'],
'newt_checkbox_set_flags' => ['void', 'checkbox'=>'resource', 'flags'=>'int', 'sense'=>'int'],
'newt_checkbox_set_value' => ['void', 'checkbox'=>'resource', 'value'=>'string'],
'newt_checkbox_tree' => ['resource', 'left'=>'int', 'top'=>'int', 'height'=>'int', 'flags='=>'int'],
'newt_checkbox_tree_add_item' => ['void', 'checkboxtree'=>'resource', 'text'=>'string', 'data'=>'mixed', 'flags'=>'int', 'index'=>'int', '...args='=>'int'],
'newt_checkbox_tree_find_item' => ['array', 'checkboxtree'=>'resource', 'data'=>'mixed'],
'newt_checkbox_tree_get_current' => ['mixed', 'checkboxtree'=>'resource'],
'newt_checkbox_tree_get_entry_value' => ['string', 'checkboxtree'=>'resource', 'data'=>'mixed'],
'newt_checkbox_tree_get_multi_selection' => ['array', 'checkboxtree'=>'resource', 'seqnum'=>'string'],
'newt_checkbox_tree_get_selection' => ['array', 'checkboxtree'=>'resource'],
'newt_checkbox_tree_multi' => ['resource', 'left'=>'int', 'top'=>'int', 'height'=>'int', 'seq'=>'string', 'flags='=>'int'],
'newt_checkbox_tree_set_current' => ['void', 'checkboxtree'=>'resource', 'data'=>'mixed'],
'newt_checkbox_tree_set_entry' => ['void', 'checkboxtree'=>'resource', 'data'=>'mixed', 'text'=>'string'],
'newt_checkbox_tree_set_entry_value' => ['void', 'checkboxtree'=>'resource', 'data'=>'mixed', 'value'=>'string'],
'newt_checkbox_tree_set_width' => ['void', 'checkbox_tree'=>'resource', 'width'=>'int'],
'newt_clear_key_buffer' => ['void'],
'newt_cls' => ['void'],
'newt_compact_button' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string'],
'newt_component_add_callback' => ['void', 'component'=>'resource', 'func_name'=>'mixed', 'data'=>'mixed'],
'newt_component_takes_focus' => ['void', 'component'=>'resource', 'takes_focus'=>'bool'],
'newt_create_grid' => ['resource', 'cols'=>'int', 'rows'=>'int'],
'newt_cursor_off' => ['void'],
'newt_cursor_on' => ['void'],
'newt_delay' => ['void', 'microseconds'=>'int'],
'newt_draw_form' => ['void', 'form'=>'resource'],
'newt_draw_root_text' => ['void', 'left'=>'int', 'top'=>'int', 'text'=>'string'],
'newt_entry' => ['resource', 'left'=>'int', 'top'=>'int', 'width'=>'int', 'init_value='=>'string', 'flags='=>'int'],
'newt_entry_get_value' => ['string', 'entry'=>'resource'],
'newt_entry_set' => ['void', 'entry'=>'resource', 'value'=>'string', 'cursor_at_end='=>'bool'],
'newt_entry_set_filter' => ['void', 'entry'=>'resource', 'filter'=>'callable', 'data'=>'mixed'],
'newt_entry_set_flags' => ['void', 'entry'=>'resource', 'flags'=>'int', 'sense'=>'int'],
'newt_finished' => ['int'],
'newt_form' => ['resource', 'vert_bar='=>'resource', 'help='=>'string', 'flags='=>'int'],
'newt_form_add_component' => ['void', 'form'=>'resource', 'component'=>'resource'],
'newt_form_add_components' => ['void', 'form'=>'resource', 'components'=>'array'],
'newt_form_add_hot_key' => ['void', 'form'=>'resource', 'key'=>'int'],
'newt_form_destroy' => ['void', 'form'=>'resource'],
'newt_form_get_current' => ['resource', 'form'=>'resource'],
'newt_form_run' => ['void', 'form'=>'resource', 'exit_struct'=>'array'],
'newt_form_set_background' => ['void', 'from'=>'resource', 'background'=>'int'],
'newt_form_set_height' => ['void', 'form'=>'resource', 'height'=>'int'],
'newt_form_set_size' => ['void', 'form'=>'resource'],
'newt_form_set_timer' => ['void', 'form'=>'resource', 'milliseconds'=>'int'],
'newt_form_set_width' => ['void', 'form'=>'resource', 'width'=>'int'],
'newt_form_watch_fd' => ['void', 'form'=>'resource', 'stream'=>'resource', 'flags='=>'int'],
'newt_get_screen_size' => ['void', 'cols'=>'int', 'rows'=>'int'],
'newt_grid_add_components_to_form' => ['void', 'grid'=>'resource', 'form'=>'resource', 'recurse'=>'bool'],
'newt_grid_basic_window' => ['resource', 'text'=>'resource', 'middle'=>'resource', 'buttons'=>'resource'],
'newt_grid_free' => ['void', 'grid'=>'resource', 'recurse'=>'bool'],
'newt_grid_get_size' => ['void', 'grid'=>'resource', 'width'=>'int', 'height'=>'int'],
'newt_grid_h_close_stacked' => ['resource', 'element1_type'=>'int', 'element1'=>'resource', '...args='=>'resource'],
'newt_grid_h_stacked' => ['resource', 'element1_type'=>'int', 'element1'=>'resource', '...args='=>'resource'],
'newt_grid_place' => ['void', 'grid'=>'resource', 'left'=>'int', 'top'=>'int'],
'newt_grid_set_field' => ['void', 'grid'=>'resource', 'col'=>'int', 'row'=>'int', 'type'=>'int', 'val'=>'resource', 'pad_left'=>'int', 'pad_top'=>'int', 'pad_right'=>'int', 'pad_bottom'=>'int', 'anchor'=>'int', 'flags='=>'int'],
'newt_grid_simple_window' => ['resource', 'text'=>'resource', 'middle'=>'resource', 'buttons'=>'resource'],
'newt_grid_v_close_stacked' => ['resource', 'element1_type'=>'int', 'element1'=>'resource', '...args='=>'resource'],
'newt_grid_v_stacked' => ['resource', 'element1_type'=>'int', 'element1'=>'resource', '...args='=>'resource'],
'newt_grid_wrapped_window' => ['void', 'grid'=>'resource', 'title'=>'string'],
'newt_grid_wrapped_window_at' => ['void', 'grid'=>'resource', 'title'=>'string', 'left'=>'int', 'top'=>'int'],
'newt_init' => ['int'],
'newt_label' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string'],
'newt_label_set_text' => ['void', 'label'=>'resource', 'text'=>'string'],
'newt_listbox' => ['resource', 'left'=>'int', 'top'=>'int', 'height'=>'int', 'flags='=>'int'],
'newt_listbox_append_entry' => ['void', 'listbox'=>'resource', 'text'=>'string', 'data'=>'mixed'],
'newt_listbox_clear' => ['void', 'listobx'=>'resource'],
'newt_listbox_clear_selection' => ['void', 'listbox'=>'resource'],
'newt_listbox_delete_entry' => ['void', 'listbox'=>'resource', 'key'=>'mixed'],
'newt_listbox_get_current' => ['string', 'listbox'=>'resource'],
'newt_listbox_get_selection' => ['array', 'listbox'=>'resource'],
'newt_listbox_insert_entry' => ['void', 'listbox'=>'resource', 'text'=>'string', 'data'=>'mixed', 'key'=>'mixed'],
'newt_listbox_item_count' => ['int', 'listbox'=>'resource'],
'newt_listbox_select_item' => ['void', 'listbox'=>'resource', 'key'=>'mixed', 'sense'=>'int'],
'newt_listbox_set_current' => ['void', 'listbox'=>'resource', 'num'=>'int'],
'newt_listbox_set_current_by_key' => ['void', 'listbox'=>'resource', 'key'=>'mixed'],
'newt_listbox_set_data' => ['void', 'listbox'=>'resource', 'num'=>'int', 'data'=>'mixed'],
'newt_listbox_set_entry' => ['void', 'listbox'=>'resource', 'num'=>'int', 'text'=>'string'],
'newt_listbox_set_width' => ['void', 'listbox'=>'resource', 'width'=>'int'],
'newt_listitem' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string', 'is_default'=>'bool', 'prev_item'=>'resource', 'data'=>'mixed', 'flags='=>'int'],
'newt_listitem_get_data' => ['mixed', 'item'=>'resource'],
'newt_listitem_set' => ['void', 'item'=>'resource', 'text'=>'string'],
'newt_open_window' => ['int', 'left'=>'int', 'top'=>'int', 'width'=>'int', 'height'=>'int', 'title='=>'string'],
'newt_pop_help_line' => ['void'],
'newt_pop_window' => ['void'],
'newt_push_help_line' => ['void', 'text='=>'string'],
'newt_radio_get_current' => ['resource', 'set_member'=>'resource'],
'newt_radiobutton' => ['resource', 'left'=>'int', 'top'=>'int', 'text'=>'string', 'is_default'=>'bool', 'prev_button='=>'resource'],
'newt_redraw_help_line' => ['void'],
'newt_reflow_text' => ['string', 'text'=>'string', 'width'=>'int', 'flex_down'=>'int', 'flex_up'=>'int', 'actual_width'=>'int', 'actual_height'=>'int'],
'newt_refresh' => ['void'],
'newt_resize_screen' => ['void', 'redraw='=>'bool'],
'newt_resume' => ['void'],
'newt_run_form' => ['resource', 'form'=>'resource'],
'newt_scale' => ['resource', 'left'=>'int', 'top'=>'int', 'width'=>'int', 'full_value'=>'int'],
'newt_scale_set' => ['void', 'scale'=>'resource', 'amount'=>'int'],
'newt_scrollbar_set' => ['void', 'scrollbar'=>'resource', 'where'=>'int', 'total'=>'int'],
'newt_set_help_callback' => ['void', 'function'=>'mixed'],
'newt_set_suspend_callback' => ['void', 'function'=>'callable', 'data'=>'mixed'],
'newt_suspend' => ['void'],
'newt_textbox' => ['resource', 'left'=>'int', 'top'=>'int', 'width'=>'int', 'height'=>'int', 'flags='=>'int'],
'newt_textbox_get_num_lines' => ['int', 'textbox'=>'resource'],
'newt_textbox_reflowed' => ['resource', 'left'=>'int', 'top'=>'int', '*text'=>'char', 'width'=>'int', 'flex_down'=>'int', 'flex_up'=>'int', 'flags='=>'int'],
'newt_textbox_set_height' => ['void', 'textbox'=>'resource', 'height'=>'int'],
'newt_textbox_set_text' => ['void', 'textbox'=>'resource', 'text'=>'string'],
'newt_vertical_scrollbar' => ['resource', 'left'=>'int', 'top'=>'int', 'height'=>'int', 'normal_colorset='=>'int', 'thumb_colorset='=>'int'],
'newt_wait_for_key' => ['void'],
'newt_win_choice' => ['int', 'title'=>'string', 'button1_text'=>'string', 'button2_text'=>'string', 'format'=>'string', 'args='=>'mixed', '...args='=>'mixed'],
'newt_win_entries' => ['int', 'title'=>'string', 'text'=>'string', 'suggested_width'=>'int', 'flex_down'=>'int', 'flex_up'=>'int', 'data_width'=>'int', 'items'=>'array', 'button1'=>'string', '...args='=>'string'],
'newt_win_menu' => ['int', 'title'=>'string', 'text'=>'string', 'suggestedwidth'=>'int', 'flexdown'=>'int', 'flexup'=>'int', 'maxlistheight'=>'int', 'items'=>'array', 'listitem'=>'int', 'button1='=>'string', '...args='=>'string'],
'newt_win_message' => ['void', 'title'=>'string', 'button_text'=>'string', 'format'=>'string', 'args='=>'mixed', '...args='=>'mixed'],
'newt_win_messagev' => ['void', 'title'=>'string', 'button_text'=>'string', 'format'=>'string', 'args'=>'array'],
'newt_win_ternary' => ['int', 'title'=>'string', 'button1_text'=>'string', 'button2_text'=>'string', 'button3_text'=>'string', 'format'=>'string', 'args='=>'mixed', '...args='=>'mixed'],
'next' => ['mixed', '&rw_array_arg'=>'array'],
'ngettext' => ['string', 'msgid1'=>'string', 'msgid2'=>'string', 'n'=>'int'],
'nl2br' => ['string', 'str'=>'string', 'is_xhtml='=>'bool'],
'nl_langinfo' => ['string', 'item'=>'int'],
'NoRewindIterator::__construct' => ['void', 'it'=>'iterator'],
'NoRewindIterator::current' => ['mixed'],
'NoRewindIterator::getInnerIterator' => ['iterator'],
'NoRewindIterator::key' => ['mixed'],
'NoRewindIterator::next' => ['void'],
'NoRewindIterator::rewind' => ['void'],
'NoRewindIterator::valid' => ['bool'],
'Normalizer::isNormalized' => ['bool', 'input'=>'string', 'form='=>'int'],
'Normalizer::normalize' => ['string', 'input'=>'string', 'form='=>'int'],
'normalizer_is_normalized' => ['bool', 'input'=>'string', 'form='=>'int'],
'normalizer_normalize' => ['string', 'input'=>'string', 'form='=>'int'],
'notes_body' => ['array', 'server'=>'string', 'mailbox'=>'string', 'msg_number'=>'int'],
'notes_copy_db' => ['bool', 'from_database_name'=>'string', 'to_database_name'=>'string'],
'notes_create_db' => ['bool', 'database_name'=>'string'],
'notes_create_note' => ['bool', 'database_name'=>'string', 'form_name'=>'string'],
'notes_drop_db' => ['bool', 'database_name'=>'string'],
'notes_find_note' => ['int', 'database_name'=>'string', 'name'=>'string', 'type='=>'string'],
'notes_header_info' => ['object', 'server'=>'string', 'mailbox'=>'string', 'msg_number'=>'int'],
'notes_list_msgs' => ['bool', 'db'=>'string'],
'notes_mark_read' => ['bool', 'database_name'=>'string', 'user_name'=>'string', 'note_id'=>'string'],
'notes_mark_unread' => ['bool', 'database_name'=>'string', 'user_name'=>'string', 'note_id'=>'string'],
'notes_nav_create' => ['bool', 'database_name'=>'string', 'name'=>'string'],
'notes_search' => ['array', 'database_name'=>'string', 'keywords'=>'string'],
'notes_unread' => ['array', 'database_name'=>'string', 'user_name'=>'string'],
'notes_version' => ['float', 'database_name'=>'string'],
'nsapi_request_headers' => ['array'],
'nsapi_response_headers' => ['array'],
'nsapi_virtual' => ['bool', 'uri'=>'string'],
'nthmac' => ['string', 'clent'=>'string', 'data'=>'string'],
'number_format' => ['string', 'number'=>'float', 'num_decimal_places='=>'int'],
'number_format\'1' => ['string', 'number'=>'float', 'num_decimal_places'=>'int', 'dec_separator'=>'string', 'thousands_separator'=>'string'],
'NumberFormatter::__construct' => ['void', 'locale'=>'string', 'style'=>'int', 'pattern='=>'string'],
'NumberFormatter::create' => ['NumberFormatter', 'locale'=>'string', 'style'=>'int', 'pattern='=>'string'],
'NumberFormatter::format' => ['string', 'num'=>'', 'type='=>'int'],
'NumberFormatter::formatCurrency' => ['string', 'num'=>'float', 'currency'=>'string'],
'NumberFormatter::getAttribute' => ['int', 'attr'=>'int'],
'NumberFormatter::getErrorCode' => ['int'],
'NumberFormatter::getErrorMessage' => ['string'],
'NumberFormatter::getLocale' => ['string', 'type='=>'int'],
'NumberFormatter::getPattern' => ['string'],
'NumberFormatter::getSymbol' => ['string', 'attr'=>'int'],
'NumberFormatter::getTextAttribute' => ['string', 'attr'=>'int'],
'NumberFormatter::parse' => ['float|false', 'str'=>'string', 'type='=>'int', '&rw_position='=>'int'],
'NumberFormatter::parseCurrency' => ['float', 'str'=>'string', '&w_currency'=>'string', '&rw_position='=>'int'],
'NumberFormatter::setAttribute' => ['bool', 'attr'=>'int', 'value'=>''],
'NumberFormatter::setPattern' => ['bool', 'pattern'=>'string'],
'NumberFormatter::setSymbol' => ['bool', 'attr'=>'int', 'symbol'=>'string'],
'NumberFormatter::setTextAttribute' => ['bool', 'attr'=>'int', 'value'=>'string'],
'numfmt_create' => ['NumberFormatter', 'locale'=>'string', 'style'=>'int', 'pattern='=>'string'],
'numfmt_format' => ['string', 'fmt'=>'numberformatter', 'value='=>'float', 'type='=>'int'],
'numfmt_format_currency' => ['string|false', 'fmt'=>'numberformatter', 'value'=>'float', 'currency'=>'string'],
'numfmt_get_attribute' => ['int', 'fmt'=>'numberformatter', 'attr'=>'int'],
'numfmt_get_error_code' => ['int', 'fmt'=>'numberformatter'],
'numfmt_get_error_message' => ['string', 'fmt'=>'numberformatter'],
'numfmt_get_locale' => ['string', 'fmt'=>'numberformatter', 'type='=>'int'],
'numfmt_get_pattern' => ['string', 'fmt'=>'numberformatter'],
'numfmt_get_symbol' => ['string', 'fmt'=>'numberformatter', 'attr'=>'int'],
'numfmt_get_text_attribute' => ['string', 'fmt'=>'numberformatter', 'attr'=>'int'],
'numfmt_parse' => ['float|false', 'fmt'=>'numberformatter', 'value'=>'string', 'type='=>'int', '&rw_position='=>'int'],
'numfmt_parse_currency' => ['float|false', 'fmt'=>'numberformatter', 'value'=>'string', '&w_currency'=>'string', '&rw_position='=>'int'],
'numfmt_set_attribute' => ['bool', 'fmt'=>'numberformatter', 'attr'=>'int', 'value'=>'int'],
'numfmt_set_pattern' => ['bool', 'fmt'=>'numberformatter', 'pattern'=>'string'],
'numfmt_set_symbol' => ['bool', 'fmt'=>'numberformatter', 'attr'=>'int', 'value'=>'string'],
'numfmt_set_text_attribute' => ['bool', 'fmt'=>'numberformatter', 'attr'=>'int', 'value'=>'string'],
'OAuth::__construct' => ['void', 'consumer_key'=>'string', 'consumer_secret'=>'string', 'signature_method='=>'string', 'auth_type='=>'int'],
'OAuth::__destruct' => [''],
'OAuth::disableDebug' => ['bool'],
'OAuth::disableRedirects' => ['bool'],
'OAuth::disableSSLChecks' => ['bool'],
'OAuth::enableDebug' => ['bool'],
'OAuth::enableRedirects' => ['bool'],
'OAuth::enableSSLChecks' => ['bool'],
'OAuth::fetch' => ['mixed', 'protected_resource_url'=>'string', 'extra_parameters='=>'array', 'http_method='=>'string', 'http_headers='=>'array'],
'OAuth::generateSignature' => ['string', 'http_method'=>'string', 'url'=>'string', 'extra_parameters='=>'mixed'],
'OAuth::getAccessToken' => ['array|false', 'access_token_url'=>'string', 'auth_session_handle='=>'string', 'verifier_token='=>'string'],
'OAuth::getCAPath' => ['array'],
'OAuth::getLastResponse' => ['string'],
'OAuth::getLastResponseHeaders' => ['string|false'],
'OAuth::getLastResponseInfo' => ['array'],
'OAuth::getRequestHeader' => ['string|false', 'http_method'=>'string', 'url'=>'string', 'extra_parameters='=>'mixed'],
'OAuth::getRequestToken' => ['array|false', 'request_token_url'=>'string', 'callback_url='=>'string'],
'OAuth::setAuthType' => ['bool', 'auth_type'=>'int'],
'OAuth::setCAPath' => ['mixed', 'ca_path='=>'string', 'ca_info='=>'string'],
'OAuth::setNonce' => ['mixed', 'nonce'=>'string'],
'OAuth::setRequestEngine' => ['void', 'reqengine'=>'int'],
'OAuth::setRSACertificate' => ['mixed', 'cert'=>'string'],
'OAuth::setSSLChecks' => ['bool', 'sslcheck'=>'int'],
'OAuth::setTimestamp' => ['mixed', 'timestamp'=>'string'],
'OAuth::setToken' => ['bool', 'token'=>'string', 'token_secret'=>'string'],
'OAuth::setVersion' => ['bool', 'version'=>'string'],
'oauth_get_sbs' => ['string', 'http_method'=>'string', 'uri'=>'string', 'request_parameters='=>'array'],
'oauth_urlencode' => ['string', 'uri'=>'string'],
'OAuthProvider::__construct' => ['void', 'params_array='=>'array'],
'OAuthProvider::addRequiredParameter' => ['bool', 'req_params'=>'string'],
'OAuthProvider::callconsumerHandler' => ['void'],
'OAuthProvider::callTimestampNonceHandler' => ['void'],
'OAuthProvider::calltokenHandler' => ['void'],
'OAuthProvider::checkOAuthRequest' => ['void', 'uri='=>'string', 'method='=>'string'],
'OAuthProvider::consumerHandler' => ['void', 'callback_function'=>'callable'],
'OAuthProvider::generateToken' => ['string', 'size'=>'int', 'strong='=>'bool'],
'OAuthProvider::is2LeggedEndpoint' => ['void', 'params_array'=>'mixed'],
'OAuthProvider::isRequestTokenEndpoint' => ['void', 'will_issue_request_token'=>'bool'],
'OAuthProvider::removeRequiredParameter' => ['bool', 'req_params'=>'string'],
'OAuthProvider::reportProblem' => ['string', 'oauthexception'=>'string', 'send_headers='=>'bool'],
'OAuthProvider::setParam' => ['bool', 'param_key'=>'string', 'param_val='=>'mixed'],
'OAuthProvider::setRequestTokenPath' => ['bool', 'path'=>'string'],
'OAuthProvider::timestampNonceHandler' => ['void', 'callback_function'=>'callable'],
'OAuthProvider::tokenHandler' => ['void', 'callback_function'=>'callable'],
'ob_clean' => ['bool'],
'ob_deflatehandler' => ['string', 'data'=>'string', 'mode'=>'int'],
'ob_end_clean' => ['bool'],
'ob_end_flush' => ['bool'],
'ob_etaghandler' => ['string', 'data'=>'string', 'mode'=>'int'],
'ob_flush' => ['bool'],
'ob_get_clean' => ['string|false'],
'ob_get_contents' => ['string|false'],
'ob_get_flush' => ['string|false'],
'ob_get_length' => ['int|false'],
'ob_get_level' => ['int'],
'ob_get_status' => ['false|array', 'full_status='=>'bool'],
'ob_gzhandler' => ['string|false', 'data'=>'string', 'flags'=>'int'],
'ob_iconv_handler' => ['string', 'contents'=>'string', 'status'=>'int'],
'ob_implicit_flush' => ['void', 'flag='=>'int'],
'ob_inflatehandler' => ['string', 'data'=>'string', 'mode'=>'int'],
'ob_list_handlers' => ['false|array'],
'ob_start' => ['bool', 'user_function='=>'string|array|?callable', 'chunk_size='=>'int', 'flags='=>'int'],
'ob_tidyhandler' => ['string', 'input'=>'string', 'mode='=>'int'],
'OCI-Collection::append' => ['bool', 'value'=>''],
'OCI-Collection::assign' => ['bool', 'from'=>'oci-collection'],
'OCI-Collection::assignElem' => ['bool', 'index'=>'int', 'value'=>''],
'OCI-Collection::free' => ['bool'],
'OCI-Collection::getElem' => ['', 'index'=>'int'],
'OCI-Collection::max' => ['int'],
'OCI-Collection::size' => ['int'],
'OCI-Collection::trim' => ['bool', 'num'=>'int'],
'OCI-Lob::append' => ['bool', 'lob_from'=>'oci-lob'],
'OCI-Lob::close' => ['bool'],
'OCI-Lob::eof' => ['bool'],
'OCI-Lob::erase' => ['int', 'offset='=>'int', 'length='=>'int'],
'OCI-Lob::export' => ['bool', 'filename'=>'string', 'start='=>'int', 'length='=>'int'],
'OCI-Lob::flush' => ['bool', 'flag='=>'int'],
'OCI-Lob::free' => ['bool'],
'OCI-Lob::getBuffering' => ['bool'],
'OCI-Lob::import' => ['bool', 'filename'=>'string'],
'OCI-Lob::load' => ['string'],
'OCI-Lob::read' => ['string', 'length'=>'int'],
'OCI-Lob::rewind' => ['bool'],
'OCI-Lob::save' => ['bool', 'data'=>'string', 'offset='=>'int'],
'OCI-Lob::seek' => ['bool', 'offset'=>'int', 'whence='=>'int'],
'OCI-Lob::setBuffering' => ['bool', 'on_off'=>'bool'],
'OCI-Lob::size' => ['int'],
'OCI-Lob::tell' => ['int'],
'OCI-Lob::truncate' => ['bool', 'length='=>'int'],
'OCI-Lob::write' => ['int', 'data'=>'string', 'length='=>'int'],
'OCI-Lob::writeTemporary' => ['bool', 'data'=>'string', 'lob_type='=>'int'],
'oci_bind_array_by_name' => ['bool', 'stmt'=>'resource', 'name'=>'string', '&rw_var'=>'array', 'max_table_length'=>'int', 'max_item_length='=>'int', 'type='=>'int'],
'oci_bind_by_name' => ['bool', 'stmt'=>'resource', 'name'=>'string', '&rw_var'=>'mixed', 'maxlength='=>'int', 'type='=>'int'],
'oci_cancel' => ['bool', 'stmt'=>'resource'],
'oci_client_version' => ['string'],
'oci_close' => ['bool', 'connection'=>'resource'],
'OCI_Collection::append' => ['bool', 'value'=>'mixed'],
'OCI_Collection::assign' => ['bool', 'from'=>'OCI_Collection'],
'OCI_Collection::assignelem' => ['bool', 'index'=>'int', 'value'=>'mixed'],
'OCI_Collection::free' => ['bool'],
'OCI_Collection::getelem' => ['mixed', 'index'=>'int'],
'OCI_Collection::max' => ['int'],
'OCI_Collection::size' => ['int'],
'OCI_Collection::trim' => ['bool', 'num'=>'int'],
'oci_collection_append' => ['bool', 'value'=>'string'],
'oci_collection_assign' => ['bool', 'from'=>'object'],
'oci_collection_element_assign' => ['bool', 'index'=>'int', 'val'=>'string'],
'oci_collection_element_get' => ['string', 'ndx'=>'int'],
'oci_collection_max' => ['int'],
'oci_collection_size' => ['int'],
'oci_collection_trim' => ['bool', 'num'=>'int'],
'oci_commit' => ['bool', 'connection'=>'resource'],
'oci_connect' => ['resource', 'user'=>'string', 'pass'=>'string', 'db='=>'string', 'charset='=>'string', 'session_mode='=>'int'],
'oci_define_by_name' => ['bool', 'stmt'=>'resource', 'name'=>'string', '&w_var'=>'mixed', 'type='=>'int'],
'oci_error' => ['array', 'stmt|connection|global='=>'resource'],
'oci_execute' => ['bool', 'stmt'=>'resource', 'mode='=>'int'],
'oci_fetch' => ['bool', 'stmt'=>'resource'],
'oci_fetch_all' => ['int', 'stmt'=>'resource', '&w_output'=>'array', 'skip='=>'int', 'maxrows='=>'int', 'flags='=>'int'],
'oci_fetch_array' => ['array', 'stmt'=>'resource', 'mode='=>'int'],
'oci_fetch_assoc' => ['array', 'stmt'=>'resource'],
'oci_fetch_object' => ['object', 'stmt'=>'resource'],
'oci_fetch_row' => ['array', 'stmt'=>'resource'],
'oci_field_is_null' => ['bool', 'stmt'=>'resource', 'col'=>'mixed'],
'oci_field_name' => ['string', 'stmt'=>'resource', 'col'=>'mixed'],
'oci_field_precision' => ['int', 'stmt'=>'resource', 'col'=>'mixed'],
'oci_field_scale' => ['int', 'stmt'=>'resource', 'col'=>'mixed'],
'oci_field_size' => ['int', 'stmt'=>'resource', 'col'=>'mixed'],
'oci_field_type' => ['mixed', 'stmt'=>'resource', 'col'=>'mixed'],
'oci_field_type_raw' => ['int', 'stmt'=>'resource', 'col'=>'mixed'],
'oci_free_collection' => ['bool'],
'oci_free_cursor' => ['bool', 'stmt'=>'resource'],
'oci_free_descriptor' => ['bool'],
'oci_free_statement' => ['bool', 'stmt'=>'resource'],
'oci_get_implicit' => ['bool', 'stmt'=>''],
'oci_get_implicit_resultset' => ['resource ', 'statement'=>'resource'],
'oci_internal_debug' => ['void', 'onoff'=>'bool'],
'OCI_Lob::append' => ['bool', 'lob_from'=>'OCI_Lob'],
'OCI_Lob::close' => ['bool'],
'OCI_Lob::eof' => ['bool'],
'OCI_Lob::erase' => ['int', 'offset='=>'int', 'length='=>'int'],
'OCI_Lob::export' => ['bool', 'filename'=>'string', 'start='=>'int', 'length='=>'int'],
'OCI_Lob::flush' => ['bool', 'flag='=>'int'],
'OCI_Lob::free' => ['bool'],
'OCI_Lob::getbuffering' => ['bool'],
'OCI_Lob::import' => ['bool', 'filename'=>'string'],
'OCI_Lob::load' => ['string'],
'OCI_Lob::read' => ['string', 'length'=>'int'],
'OCI_Lob::rewind' => ['bool'],
'OCI_Lob::save' => ['bool', 'data'=>'string', 'offset='=>'int'],
'OCI_Lob::savefile' => ['bool', 'filename'=>''],
'OCI_Lob::seek' => ['bool', 'offset'=>'int', 'whence='=>'int'],
'OCI_Lob::setbuffering' => ['bool', 'on_off'=>'bool'],
'OCI_Lob::size' => ['int'],
'OCI_Lob::tell' => ['int'],
'OCI_Lob::truncate' => ['bool', 'length='=>'int'],
'OCI_Lob::write' => ['int', 'data'=>'string', 'length='=>'int'],
'OCI_Lob::writeTemporary' => ['bool', 'data'=>'string', 'lob_type='=>'int'],
'OCI_Lob::writetofile' => ['bool', 'filename'=>'', 'start'=>'', 'length'=>''],
'oci_lob_append' => ['bool', 'lob'=>'object'],
'oci_lob_close' => ['bool'],
'oci_lob_copy' => ['bool', 'lob_to'=>'object', 'lob_from'=>'object', 'length='=>'int'],
'oci_lob_eof' => ['bool'],
'oci_lob_erase' => ['int', 'offset'=>'int', 'length'=>'int'],
'oci_lob_export' => ['bool', 'filename'=>'string', 'start'=>'int', 'length'=>'int'],
'oci_lob_flush' => ['bool', 'flag'=>'int'],
'oci_lob_import' => ['bool', 'filename'=>'string'],
'oci_lob_is_equal' => ['bool', 'lob1'=>'object', 'lob2'=>'object'],
'oci_lob_load' => ['string'],
'oci_lob_read' => ['string', 'length'=>'int'],
'oci_lob_rewind' => ['bool'],
'oci_lob_save' => ['bool', 'data'=>'string', 'offset'=>'int'],
'oci_lob_seek' => ['bool', 'offset'=>'int', 'whence'=>'int'],
'oci_lob_size' => ['int'],
'oci_lob_tell' => ['int'],
'oci_lob_truncate' => ['bool', 'length'=>'int'],
'oci_lob_write' => ['int', 'string'=>'string', 'length'=>'int'],
'oci_lob_write_temporary' => ['bool', 'var'=>'string', 'lob_type'=>'int'],
'oci_new_collection' => ['object', 'connection'=>'resource', 'tdo'=>'string', 'schema='=>'string'],
'oci_new_connect' => ['resource', 'user'=>'string', 'pass'=>'string', 'db='=>'string', 'charset='=>'string', 'session_mode='=>'int'],
'oci_new_cursor' => ['resource', 'connection'=>'resource'],
'oci_new_descriptor' => ['object', 'connection'=>'resource', 'type='=>'int'],
'oci_num_fields' => ['int', 'stmt'=>'resource'],
'oci_num_rows' => ['int', 'stmt'=>'resource'],
'oci_parse' => ['resource', 'connection'=>'resource', 'statement'=>'string'],
'oci_password_change' => ['bool', 'connection'=>'', 'username'=>'string', 'old_password'=>'string', 'new_password'=>'string'],
'oci_pconnect' => ['resource', 'user'=>'string', 'pass'=>'string', 'db='=>'string', 'charset='=>'string', 'session_mode='=>'int'],
'oci_register_taf_callback' => ['bool', 'connection'=>'resource', 'callback='=>'callable'],
'oci_result' => ['string', 'stmt'=>'resource', 'column'=>'mixed'],
'oci_rollback' => ['bool', 'connection'=>'resource'],
'oci_server_version' => ['string', 'connection'=>'resource'],
'oci_set_action' => ['bool', 'connection'=>'resource', 'value'=>'string'],
'oci_set_client_identifier' => ['bool', 'connection'=>'resource', 'value'=>'string'],
'oci_set_client_info' => ['bool', 'connection'=>'resource', 'value'=>'string'],
'oci_set_db_operation' => ['bool', 'connection'=>'', 'value'=>'string'],
'oci_set_edition' => ['bool', 'value'=>'string'],
'oci_set_module_name' => ['bool', 'connection'=>'resource', 'value'=>'string'],
'oci_set_prefetch' => ['bool', 'stmt'=>'resource', 'prefetch_rows'=>'int'],
'oci_statement_type' => ['string', 'stmt'=>'resource'],
'oci_unregister_taf_callback' => ['bool', 'connection'=>'resource'],
'ocifetchinto' => ['int', 'stmt'=>'', '&w_output'=>'array', 'mode='=>'int'],
'ocigetbufferinglob' => ['bool'],
'ocisetbufferinglob' => ['bool', 'flag'=>'bool'],
'octdec' => ['int', 'octal_number'=>'string'],
'odbc_autocommit' => ['mixed', 'connection_id'=>'resource', 'onoff='=>'bool'],
'odbc_binmode' => ['bool', 'result_id'=>'int', 'mode'=>'int'],
'odbc_close' => ['void', 'connection_id'=>'resource'],
'odbc_close_all' => ['void'],
'odbc_columnprivileges' => ['resource', 'connection_id'=>'resource', 'catalog'=>'string', 'schema'=>'string', 'table'=>'string', 'column'=>'string'],
'odbc_columns' => ['resource', 'connection_id'=>'resource', 'qualifier='=>'string', 'owner='=>'string', 'table_name='=>'string', 'column_name='=>'string'],
'odbc_commit' => ['bool', 'connection_id'=>'resource'],
'odbc_connect' => ['resource', 'dsn'=>'string', 'user'=>'string', 'password'=>'string', 'cursor_option='=>'int'],
'odbc_cursor' => ['string', 'result_id'=>'resource'],
'odbc_data_source' => ['array', 'connection_id'=>'resource', 'fetch_type'=>'int'],
'odbc_do' => ['resource', 'connection_id'=>'resource', 'query'=>'string', 'flags='=>'int'],
'odbc_error' => ['string', 'connection_id='=>'resource'],
'odbc_errormsg' => ['string', 'connection_id='=>'resource'],
'odbc_exec' => ['resource', 'connection_id'=>'resource', 'query'=>'string', 'flags='=>'int'],
'odbc_execute' => ['bool', 'result_id'=>'resource', 'parameters_array='=>'array'],
'odbc_fetch_array' => ['array', 'result'=>'resource', 'rownumber='=>'int'],
'odbc_fetch_into' => ['int', 'result_id'=>'resource', '&w_result_array'=>'array', 'rownumber='=>'int'],
'odbc_fetch_object' => ['object', 'result'=>'int', 'rownumber='=>'int'],
'odbc_fetch_row' => ['bool', 'result_id'=>'resource', 'row_number='=>'int'],
'odbc_field_len' => ['int', 'result_id'=>'resource', 'field_number'=>'int'],
'odbc_field_name' => ['string', 'result_id'=>'resource', 'field_number'=>'int'],
'odbc_field_num' => ['int', 'result_id'=>'resource', 'field_name'=>'string'],
'odbc_field_precision' => ['int', 'result_id'=>'resource', 'field_number'=>'int'],
'odbc_field_scale' => ['int', 'result_id'=>'resource', 'field_number'=>'int'],
'odbc_field_type' => ['string', 'result_id'=>'resource', 'field_number'=>'int'],
'odbc_foreignkeys' => ['resource', 'connection_id'=>'resource', 'pk_qualifier'=>'string', 'pk_owner'=>'string', 'pk_table'=>'string', 'fk_qualifier'=>'string', 'fk_owner'=>'string', 'fk_table'=>'string'],
'odbc_free_result' => ['bool', 'result_id'=>'resource'],
'odbc_gettypeinfo' => ['resource', 'connection_id'=>'resource', 'data_type='=>'int'],
'odbc_longreadlen' => ['bool', 'result_id'=>'int', 'length'=>'int'],
'odbc_next_result' => ['bool', 'result_id'=>'resource'],
'odbc_num_fields' => ['int', 'result_id'=>'resource'],
'odbc_num_rows' => ['int', 'result_id'=>'resource'],
'odbc_pconnect' => ['resource', 'dsn'=>'string', 'user'=>'string', 'password'=>'string', 'cursor_option='=>'int'],
'odbc_prepare' => ['resource', 'connection_id'=>'resource', 'query'=>'string'],
'odbc_primarykeys' => ['resource', 'connection_id'=>'resource', 'qualifier'=>'string', 'owner'=>'string', 'table'=>'string'],
'odbc_procedurecolumns' => ['resource', 'connection_id'=>'', 'qualifier'=>'string', 'owner'=>'string', 'proc'=>'string', 'column'=>'string'],
'odbc_procedures' => ['resource', 'connection_id'=>'', 'qualifier'=>'string', 'owner'=>'string', 'name'=>'string'],
'odbc_result' => ['mixed', 'result_id'=>'resource', 'field'=>'mixed'],
'odbc_result_all' => ['int', 'result_id'=>'resource', 'format='=>'string'],
'odbc_rollback' => ['bool', 'connection_id'=>'resource'],
'odbc_setoption' => ['bool', 'conn_id|result_id'=>'resource', 'which'=>'int', 'option'=>'int', 'value'=>'int'],
'odbc_specialcolumns' => ['resource', 'connection_id'=>'resource', 'type'=>'int', 'qualifier'=>'string', 'owner'=>'string', 'table'=>'string', 'scope'=>'int', 'nullable'=>'int'],
'odbc_statistics' => ['resource', 'connection_id'=>'resource', 'qualifier'=>'string', 'owner'=>'string', 'name'=>'string', 'unique'=>'int', 'accuracy'=>'int'],
'odbc_tableprivileges' => ['resource', 'connection_id'=>'resource', 'qualifier'=>'string', 'owner'=>'string', 'name'=>'string'],
'odbc_tables' => ['resource', 'connection_id'=>'resource', 'qualifier='=>'string', 'owner='=>'string', 'name='=>'string', 'table_types='=>'string'],
'opcache_compile_file' => ['bool', 'file'=>'string'],
'opcache_get_configuration' => ['array'],
'opcache_get_status' => ['array', 'get_scripts='=>'bool'],
'opcache_invalidate' => ['bool', 'script'=>'string', 'force='=>'bool'],
'opcache_is_script_cached' => ['bool', 'script'=>'string'],
'opcache_reset' => ['bool'],
'openal_buffer_create' => ['resource'],
'openal_buffer_data' => ['bool', 'buffer'=>'resource', 'format'=>'int', 'data'=>'string', 'freq'=>'int'],
'openal_buffer_destroy' => ['bool', 'buffer'=>'resource'],
'openal_buffer_get' => ['int', 'buffer'=>'resource', 'property'=>'int'],
'openal_buffer_loadwav' => ['bool', 'buffer'=>'resource', 'wavfile'=>'string'],
'openal_context_create' => ['resource', 'device'=>'resource'],
'openal_context_current' => ['bool', 'context'=>'resource'],
'openal_context_destroy' => ['bool', 'context'=>'resource'],
'openal_context_process' => ['bool', 'context'=>'resource'],
'openal_context_suspend' => ['bool', 'context'=>'resource'],
'openal_device_close' => ['bool', 'device'=>'resource'],
'openal_device_open' => ['resource', 'device_desc='=>'string'],
'openal_listener_get' => ['mixed', 'property'=>'int'],
'openal_listener_set' => ['bool', 'property'=>'int', 'setting'=>'mixed'],
'openal_source_create' => ['resource'],
'openal_source_destroy' => ['bool', 'source'=>'resource'],
'openal_source_get' => ['mixed', 'source'=>'resource', 'property'=>'int'],
'openal_source_pause' => ['bool', 'source'=>'resource'],
'openal_source_play' => ['bool', 'source'=>'resource'],
'openal_source_rewind' => ['bool', 'source'=>'resource'],
'openal_source_set' => ['bool', 'source'=>'resource', 'property'=>'int', 'setting'=>'mixed'],
'openal_source_stop' => ['bool', 'source'=>'resource'],
'openal_stream' => ['resource', 'source'=>'resource', 'format'=>'int', 'rate'=>'int'],
'opendir' => ['resource|false', 'path'=>'string', 'context='=>'resource'],
'openlog' => ['bool', 'ident'=>'string', 'option'=>'int', 'facility'=>'int'],
'openssl_cipher_iv_length' => ['int', 'method'=>'string'],
'openssl_csr_export' => ['bool', 'csr'=>'string|resource', '&w_out'=>'string', 'notext='=>'bool'],
'openssl_csr_export_to_file' => ['bool', 'csr'=>'string|resource', 'outfilename'=>'string', 'notext='=>'bool'],
'openssl_csr_get_public_key' => ['resource|false', 'csr'=>'string|resource', 'use_shortnames='=>'bool'],
'openssl_csr_get_subject' => ['array', 'csr'=>'string|resource', 'use_shortnames='=>'bool'],
'openssl_csr_new' => ['resource|false', 'dn'=>'array', '&w_privkey'=>'resource', 'configargs='=>'array', 'extraattribs='=>'array'],
'openssl_csr_sign' => ['resource', 'csr'=>'string|resource', 'x509'=>'string|resource', 'priv_key'=>'string|resource|array', 'days'=>'int', 'config_args='=>'array', 'serial='=>'int'],
'openssl_decrypt' => ['string|false', 'data'=>'string', 'method'=>'string', 'password'=>'string', 'options='=>'int', 'iv='=>'string', 'tag='=>'string', 'aad='=>'string'],
'openssl_dh_compute_key' => ['string', 'pub_key'=>'string', 'dh_key'=>'resource'],
'openssl_digest' => ['string', 'data'=>'string', 'method'=>'string', 'raw_output='=>'bool'],
'openssl_encrypt' => ['string|false', 'data'=>'string', 'method'=>'string', 'password'=>'string', 'options='=>'int', 'iv='=>'string', '&rw_tag='=>'string', 'aad='=>'string', 'tag_length='=>'int'],
'openssl_error_string' => ['string'],
'openssl_free_key' => ['void', 'key_identifier'=>'resource'],
'openssl_get_cert_locations' => ['array'],
'openssl_get_cipher_methods' => ['array', 'aliases='=>'bool'],
'openssl_get_curve_names' => ['array<int,string>'],
'openssl_get_md_methods' => ['array', 'aliases='=>'bool'],
'openssl_get_privatekey' => ['resource', 'key'=>'string', 'passphrase='=>'string'],
'openssl_get_publickey' => ['resource', 'cert'=>'resource|string'],
'openssl_open' => ['bool', 'data'=>'string', '&w_opendata'=>'string', 'ekey'=>'string', 'privkey'=>'string|array|resource', 'method='=>'string'],
'openssl_pbkdf2' => ['string', 'password'=>'string', 'salt'=>'string', 'key_length'=>'int', 'iterations'=>'int', 'digest_method'=>'string'],
'openssl_pkcs12_export' => ['bool', 'x509'=>'string|resource', '&w_out'=>'string', 'priv_key'=>'string|array|resource', 'pass'=>'string', 'args='=>'array'],
'openssl_pkcs12_export_to_file' => ['bool', 'x509'=>'string|resource', 'filename'=>'string', 'priv_key'=>'string|array|resource', 'pass'=>'string', 'args='=>'array'],
'openssl_pkcs12_read' => ['bool', 'pkcs12'=>'string', '&w_certs'=>'array', 'pass'=>'string'],
'openssl_pkcs7_decrypt' => ['bool', 'infilename'=>'string', 'outfilename'=>'string', 'recipcert'=>'string|resource', 'recipkey='=>'string|resource|array'],
'openssl_pkcs7_encrypt' => ['bool', 'infile'=>'string', 'outfile'=>'string', 'recipcerts'=>'string|resource|array', 'headers'=>'array', 'flags='=>'int', 'cipher='=>'int'],
'openssl_pkcs7_read' => ['bool', 'infilename'=>'string', 'certs'=>'array'],
'openssl_pkcs7_sign' => ['bool', 'infile'=>'string', 'outfile'=>'string', 'signcert'=>'string|resource', 'signkey'=>'string|resource|array', 'headers'=>'array', 'flags='=>'int', 'extracertsfilename='=>'string'],
'openssl_pkcs7_verify' => ['bool', 'filename'=>'string', 'flags'=>'int', 'signerscerts='=>'string', 'cainfo='=>'array', 'extracerts='=>'string', 'content='=>'string'],
'openssl_pkey_export' => ['bool', 'key'=>'resource', '&w_out'=>'string', 'passphrase='=>'string', 'config_args='=>'array'],
'openssl_pkey_export_to_file' => ['bool', 'key'=>'resource|string|array', 'outfilename'=>'string', 'passphrase='=>'string', 'config_args='=>'array'],
'openssl_pkey_free' => ['void', 'key'=>'resource'],
'openssl_pkey_get_details' => ['array|false', 'key'=>'resource'],
'openssl_pkey_get_private' => ['resource', 'key'=>'string', 'passphrase='=>'string'],
'openssl_pkey_get_public' => ['resource', 'cert'=>'resource|string'],
'openssl_pkey_new' => ['resource', 'configargs='=>'array'],
'openssl_private_decrypt' => ['bool', 'data'=>'string', '&w_decrypted'=>'string', 'key'=>'string|resource|array', 'padding='=>'int'],
'openssl_private_encrypt' => ['bool', 'data'=>'string', '&w_crypted'=>'string', 'key'=>'string|resource|array', 'padding='=>'int'],
'openssl_public_decrypt' => ['bool', 'data'=>'string', '&w_crypted'=>'string', 'key'=>'string|resource', 'padding='=>'int'],
'openssl_public_encrypt' => ['bool', 'data'=>'string', '&w_crypted'=>'string', 'key'=>'string|resource', 'padding='=>'int'],
'openssl_random_pseudo_bytes' => ['string', 'length'=>'int', '&w_returned_strong_result='=>'bool'],
'openssl_seal' => ['int', 'data'=>'string', '&w_sealdata'=>'string', '&rw_ekeys'=>'array', 'pubkeys'=>'array', 'method='=>'string'],
'openssl_sign' => ['bool', 'data'=>'string', '&w_signature'=>'string', 'key'=>'resource|string', 'method='=>'int|string'],
'openssl_spki_export' => ['string', 'spki'=>'string'],
'openssl_spki_export_challenge' => ['string', 'spki'=>'string'],
'openssl_spki_new' => ['string', 'privkey'=>'resource', 'challenge'=>'string', 'algorithm='=>'int'],
'openssl_spki_verify' => ['bool', 'spki'=>'string'],
'openssl_verify' => ['int', 'data'=>'string', 'signature'=>'string', 'pub_key_id'=>'resource|string', 'signature_alg='=>'int|string'],
'openssl_x509_check_private_key' => ['bool', 'cert'=>'string|resource', 'key'=>'string|resource|array'],
'openssl_x509_checkpurpose' => ['int', 'x509cert'=>'string|resource', 'purpose'=>'int', 'cainfo='=>'array', 'untrustedfile='=>'string'],
'openssl_x509_export' => ['bool', 'x509'=>'string|resource', '&w_out'=>'string', 'notext='=>'bool'],
'openssl_x509_export_to_file' => ['bool', 'x509'=>'string|resource', 'outfilename'=>'string', 'notext='=>'bool'],
'openssl_x509_fingerprint' => ['bool', 'x509'=>'string|resource', 'hash_algorithm='=>'string', 'raw_output='=>'bool'],
'openssl_x509_free' => ['void', 'x509'=>'resource'],
'openssl_x509_parse' => ['array|false', 'x509'=>'string|resource', 'shortnames='=>'bool'],
'openssl_x509_read' => ['resource', 'cert'=>'string|resource'],
'ord' => ['int', 'character'=>'string'],
'OuterIterator::getInnerIterator' => ['Iterator'],
'OutOfBoundsException::__clone' => ['void'],
'OutOfBoundsException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?OutOfBoundsException'],
'OutOfBoundsException::__toString' => ['string'],
'OutOfBoundsException::getCode' => ['int'],
'OutOfBoundsException::getFile' => ['string'],
'OutOfBoundsException::getLine' => ['int'],
'OutOfBoundsException::getMessage' => ['string'],
'OutOfBoundsException::getPrevious' => ['Throwable|OutOfBoundsException'],
'OutOfBoundsException::getTrace' => ['array'],
'OutOfBoundsException::getTraceAsString' => ['string'],
'OutOfRangeException::__clone' => ['void'],
'OutOfRangeException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?OutOfRangeException'],
'OutOfRangeException::__toString' => ['string'],
'OutOfRangeException::getCode' => ['int'],
'OutOfRangeException::getFile' => ['string'],
'OutOfRangeException::getLine' => ['int'],
'OutOfRangeException::getMessage' => ['string'],
'OutOfRangeException::getPrevious' => ['Throwable|OutOfRangeException'],
'OutOfRangeException::getTrace' => ['array'],
'OutOfRangeException::getTraceAsString' => ['string'],
'output_add_rewrite_var' => ['bool', 'name'=>'string', 'value'=>'string'],
'output_reset_rewrite_vars' => ['bool'],
'OverflowException::__clone' => ['void'],
'OverflowException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?OverflowException'],
'OverflowException::__toString' => ['string'],
'OverflowException::getCode' => ['int'],
'OverflowException::getFile' => ['string'],
'OverflowException::getLine' => ['int'],
'OverflowException::getMessage' => ['string'],
'OverflowException::getPrevious' => ['Throwable|OverflowException'],
'OverflowException::getTrace' => ['array'],
'OverflowException::getTraceAsString' => ['string'],
'overload' => ['', 'class_name'=>'string'],
'override_function' => ['bool', 'function_name'=>'string', 'function_args'=>'string', 'function_code'=>'string'],
'pack' => ['string', 'format'=>'string', '...args='=>'mixed'],
'ParentIterator::__construct' => ['void', 'it'=>'recursiveiterator'],
'ParentIterator::accept' => ['bool'],
'ParentIterator::getChildren' => ['ParentIterator'],
'ParentIterator::hasChildren' => ['bool'],
'ParentIterator::next' => ['void'],
'ParentIterator::rewind' => ['void'],
'ParentIterator::valid' => [''],
'Parle\Lexer::advance' => ['void'],
'Parle\Lexer::build' => ['void'],
'Parle\Lexer::callout' => ['void', 'id'=>'int', 'callback'=>'callable'],
'Parle\Lexer::consume' => ['void', 'data'=>'string'],
'Parle\Lexer::dump' => ['void'],
'Parle\Lexer::getToken' => ['Parle\Token'],
'Parle\Lexer::insertMacro' => ['void', 'name'=>'string', 'regex'=>'string'],
'Parle\Lexer::push' => ['void', 'regex'=>'string', 'id'=>'int'],
'Parle\Lexer::reset' => ['void', 'pos'=>'int'],
'Parle\Parser::advance' => ['void'],
'Parle\Parser::build' => ['void'],
'Parle\Parser::consume' => ['void', 'data'=>'string', 'lexer'=>'Parle\Lexer'],
'Parle\Parser::dump' => ['void'],
'Parle\Parser::errorInfo' => ['Parle\ErrorInfo'],
'Parle\Parser::left' => ['void', 'token'=>'string'],
'Parle\Parser::nonassoc' => ['void', 'token'=>'string'],
'Parle\Parser::precedence' => ['void', 'token'=>'string'],
'Parle\Parser::push' => ['int', 'name'=>'string', 'rule'=>'string'],
'Parle\Parser::reset' => ['void', 'tokenId'=>'int'],
'Parle\Parser::right' => ['void', 'token'=>'string'],
'Parle\Parser::sigil' => ['string', 'idx'=>'array'],
'Parle\Parser::token' => ['void', 'token'=>'string'],
'Parle\Parser::tokenId' => ['int', 'token'=>'string'],
'Parle\Parser::trace' => ['string'],
'Parle\Parser::validate' => ['bool', 'data'=>'string', 'lexer'=>'Parle\Lexer'],
'Parle\RLexer::advance' => ['void'],
'Parle\RLexer::build' => ['void'],
'Parle\RLexer::callout' => ['void', 'id'=>'int', 'callback'=>'callable'],
'Parle\RLexer::consume' => ['void', 'data'=>'string'],
'Parle\RLexer::dump' => ['void'],
'Parle\RLexer::getToken' => ['Parle\Token'],
'Parle\RLexer::push' => ['void', 'state'=>'string', 'regex'=>'string', 'newState'=>'string'],
'Parle\RLexer::pushState' => ['int', 'state'=>'string'],
'Parle\RLexer::reset' => ['void', 'pos'=>'int'],
'Parle\RParser::advance' => ['void'],
'Parle\RParser::build' => ['void'],
'Parle\RParser::consume' => ['void', 'data'=>'string', 'lexer'=>'Parle\Lexer'],
'Parle\RParser::dump' => ['void'],
'Parle\RParser::errorInfo' => ['Parle\ErrorInfo'],
'Parle\RParser::left' => ['void', 'token'=>'string'],
'Parle\RParser::nonassoc' => ['void', 'token'=>'string'],
'Parle\RParser::precedence' => ['void', 'token'=>'string'],
'Parle\RParser::push' => ['int', 'name'=>'string', 'rule'=>'string'],
'Parle\RParser::reset' => ['void', 'tokenId'=>'int'],
'Parle\RParser::right' => ['void', 'token'=>'string'],
'Parle\RParser::sigil' => ['string', 'idx'=>'array'],
'Parle\RParser::token' => ['void', 'token'=>'string'],
'Parle\RParser::tokenId' => ['int', 'token'=>'string'],
'Parle\RParser::trace' => ['string'],
'Parle\RParser::validate' => ['bool', 'data'=>'string', 'lexer'=>'Parle\Lexer'],
'Parle\Stack::pop' => ['void'],
'Parle\Stack::push' => ['void', 'item'=>''],
'parse_ini_file' => ['array|false', 'filename'=>'string', 'process_sections='=>'bool', 'scanner_mode='=>'int'],
'parse_ini_string' => ['array|false', 'ini_string'=>'string', 'process_sections='=>'bool', 'scanner_mode='=>'int'],
'parse_str' => ['void', 'encoded_string'=>'string', '&w_result='=>'array'],
'parse_url' => ['array{scheme?:string,host?:string,port?:int,user?:string,pass?:string,path?:string,query?:string,fragment?:string}|string|int|null', 'url'=>'string', 'url_component='=>'int'],
'ParseError::__clone' => ['void'],
'ParseError::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?ParseError'],
'ParseError::__toString' => ['string'],
'ParseError::getCode' => ['int'],
'ParseError::getFile' => ['string'],
'ParseError::getLine' => ['int'],
'ParseError::getMessage' => ['string'],
'ParseError::getPrevious' => ['Throwable|ParseError'],
'ParseError::getTrace' => ['array'],
'ParseError::getTraceAsString' => ['string'],
'parsekit_compile_file' => ['array', 'filename'=>'string', 'errors='=>'array', 'options='=>'int'],
'parsekit_compile_string' => ['array', 'phpcode'=>'string', 'errors='=>'array', 'options='=>'int'],
'parsekit_func_arginfo' => ['array', 'function'=>'mixed'],
'passthru' => ['void', 'command'=>'string', '&w_return_value='=>'int'],
'password_get_info' => ['array', 'hash'=>'string'],
'password_hash' => ['string', 'password'=>'string', 'algo'=>'int', 'options='=>'array'],
'password_make_salt' => ['bool', 'password'=>'string', 'hash'=>'string'],
'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'int', 'options='=>'array'],
'password_verify' => ['bool', 'password'=>'string', 'hash'=>'string'],
'pathinfo' => ['array|string', 'path'=>'string', 'options='=>'int'],
'pclose' => ['int', 'fp'=>'resource'],
'pcnlt_sigwaitinfo' => ['int', 'set'=>'array', '&w_siginfo'=>'array'],
'pcntl_alarm' => ['int', 'seconds'=>'int'],
'pcntl_async_signals' => ['bool', 'on='=>'bool'],
'pcntl_exec' => ['bool', 'path'=>'string', 'args='=>'array', 'envs='=>'array'],
'pcntl_fork' => ['int'],
'pcntl_get_last_error' => ['int'],
'pcntl_getpriority' => ['int', 'pid='=>'int', 'process_identifier='=>'int'],
'pcntl_setpriority' => ['bool', 'priority'=>'int', 'pid='=>'int', 'process_identifier='=>'int'],
'pcntl_signal' => ['bool', 'signo'=>'int', 'handle'=>'callable():void|callable(int):void|callable(int,array):void|int', 'restart_syscalls='=>'bool'],
'pcntl_signal_dispatch' => ['bool'],
'pcntl_signal_get_handler' => ['int|string', 'signo'=>'int'],
'pcntl_sigprocmask' => ['bool', 'how'=>'int', 'set'=>'array', '&w_oldset='=>'array'],
'pcntl_sigtimedwait' => ['int', 'set'=>'array', '&w_siginfo='=>'array', 'seconds='=>'int', 'nanoseconds='=>'int'],
'pcntl_sigwaitinfo' => ['int', 'set'=>'array', '&w_siginfo='=>'array'],
'pcntl_strerror' => ['string', 'errno'=>'int'],
'pcntl_wait' => ['int', '&w_status'=>'int', 'options='=>'int', '&w_rusage='=>'array'],
'pcntl_waitpid' => ['int', 'pid'=>'int', '&w_status'=>'int', 'options='=>'int', '&w_rusage='=>'array'],
'pcntl_wexitstatus' => ['int', 'status'=>'int'],
'pcntl_wifcontinued' => ['bool', 'status'=>'int'],
'pcntl_wifexited' => ['bool', 'status'=>'int'],
'pcntl_wifsignaled' => ['bool', 'status'=>'int'],
'pcntl_wifstopped' => ['bool', 'status'=>'int'],
'pcntl_wstopsig' => ['int', 'status'=>'int'],
'pcntl_wtermsig' => ['int', 'status'=>'int'],
'PDF_activate_item' => ['bool', 'pdfdoc'=>'resource', 'id'=>'int'],
'PDF_add_launchlink' => ['bool', 'pdfdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'filename'=>'string'],
'PDF_add_locallink' => ['bool', 'pdfdoc'=>'resource', 'lowerleftx'=>'float', 'lowerlefty'=>'float', 'upperrightx'=>'float', 'upperrighty'=>'float', 'page'=>'int', 'dest'=>'string'],
'PDF_add_nameddest' => ['bool', 'pdfdoc'=>'resource', 'name'=>'string', 'optlist'=>'string'],
'PDF_add_note' => ['bool', 'pdfdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'contents'=>'string', 'title'=>'string', 'icon'=>'string', 'open'=>'int'],
'PDF_add_pdflink' => ['bool', 'pdfdoc'=>'resource', 'bottom_left_x'=>'float', 'bottom_left_y'=>'float', 'up_right_x'=>'float', 'up_right_y'=>'float', 'filename'=>'string', 'page'=>'int', 'dest'=>'string'],
'PDF_add_table_cell' => ['int', 'pdfdoc'=>'resource', 'table'=>'int', 'column'=>'int', 'row'=>'int', 'text'=>'string', 'optlist'=>'string'],
'PDF_add_textflow' => ['int', 'pdfdoc'=>'resource', 'textflow'=>'int', 'text'=>'string', 'optlist'=>'string'],
'PDF_add_thumbnail' => ['bool', 'pdfdoc'=>'resource', 'image'=>'int'],
'PDF_add_weblink' => ['bool', 'pdfdoc'=>'resource', 'lowerleftx'=>'float', 'lowerlefty'=>'float', 'upperrightx'=>'float', 'upperrighty'=>'float', 'url'=>'string'],
'PDF_arc' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float', 'r'=>'float', 'alpha'=>'float', 'beta'=>'float'],
'PDF_arcn' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float', 'r'=>'float', 'alpha'=>'float', 'beta'=>'float'],
'PDF_attach_file' => ['bool', 'pdfdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'filename'=>'string', 'description'=>'string', 'author'=>'string', 'mimetype'=>'string', 'icon'=>'string'],
'PDF_begin_document' => ['int', 'pdfdoc'=>'resource', 'filename'=>'string', 'optlist'=>'string'],
'PDF_begin_font' => ['bool', 'pdfdoc'=>'resource', 'filename'=>'string', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'e'=>'float', 'f'=>'float', 'optlist'=>'string'],
'PDF_begin_glyph' => ['bool', 'pdfdoc'=>'resource', 'glyphname'=>'string', 'wx'=>'float', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float'],
'PDF_begin_item' => ['int', 'pdfdoc'=>'resource', 'tag'=>'string', 'optlist'=>'string'],
'PDF_begin_layer' => ['bool', 'pdfdoc'=>'resource', 'layer'=>'int'],
'PDF_begin_page' => ['bool', 'pdfdoc'=>'resource', 'width'=>'float', 'height'=>'float'],
'PDF_begin_page_ext' => ['bool', 'pdfdoc'=>'resource', 'width'=>'float', 'height'=>'float', 'optlist'=>'string'],
'PDF_begin_pattern' => ['int', 'pdfdoc'=>'resource', 'width'=>'float', 'height'=>'float', 'xstep'=>'float', 'ystep'=>'float', 'painttype'=>'int'],
'PDF_begin_template' => ['int', 'pdfdoc'=>'resource', 'width'=>'float', 'height'=>'float'],
'PDF_begin_template_ext' => ['int', 'pdfdoc'=>'resource', 'width'=>'float', 'height'=>'float', 'optlist'=>'string'],
'PDF_circle' => ['bool', 'pdfdoc'=>'resource', 'x'=>'float', 'y'=>'float', 'r'=>'float'],
'PDF_clip' => ['bool', 'p'=>'resource'],
'PDF_close' => ['bool', 'p'=>'resource'],
'PDF_close_image' => ['bool', 'p'=>'resource', 'image'=>'int'],
'PDF_close_pdi' => ['bool', 'p'=>'resource', 'doc'=>'int'],
'PDF_close_pdi_page' => ['bool', 'p'=>'resource', 'page'=>'int'],
'PDF_closepath' => ['bool', 'p'=>'resource'],
'PDF_closepath_fill_stroke' => ['bool', 'p'=>'resource'],
'PDF_closepath_stroke' => ['bool', 'p'=>'resource'],
'PDF_concat' => ['bool', 'p'=>'resource', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'e'=>'float', 'f'=>'float'],
'PDF_continue_text' => ['bool', 'p'=>'resource', 'text'=>'string'],
'PDF_create_3dview' => ['int', 'pdfdoc'=>'resource', 'username'=>'string', 'optlist'=>'string'],
'PDF_create_action' => ['int', 'pdfdoc'=>'resource', 'type'=>'string', 'optlist'=>'string'],
'PDF_create_annotation' => ['bool', 'pdfdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'type'=>'string', 'optlist'=>'string'],
'PDF_create_bookmark' => ['int', 'pdfdoc'=>'resource', 'text'=>'string', 'optlist'=>'string'],
'PDF_create_field' => ['bool', 'pdfdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'name'=>'string', 'type'=>'string', 'optlist'=>'string'],
'PDF_create_fieldgroup' => ['bool', 'pdfdoc'=>'resource', 'name'=>'string', 'optlist'=>'string'],
'PDF_create_gstate' => ['int', 'pdfdoc'=>'resource', 'optlist'=>'string'],
'PDF_create_pvf' => ['bool', 'pdfdoc'=>'resource', 'filename'=>'string', 'data'=>'string', 'optlist'=>'string'],
'PDF_create_textflow' => ['int', 'pdfdoc'=>'resource', 'text'=>'string', 'optlist'=>'string'],
'PDF_curveto' => ['bool', 'p'=>'resource', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'],
'PDF_define_layer' => ['int', 'pdfdoc'=>'resource', 'name'=>'string', 'optlist'=>'string'],
'PDF_delete' => ['bool', 'pdfdoc'=>'resource'],
'PDF_delete_pvf' => ['int', 'pdfdoc'=>'resource', 'filename'=>'string'],
'PDF_delete_table' => ['bool', 'pdfdoc'=>'resource', 'table'=>'int', 'optlist'=>'string'],
'PDF_delete_textflow' => ['bool', 'pdfdoc'=>'resource', 'textflow'=>'int'],
'PDF_encoding_set_char' => ['bool', 'pdfdoc'=>'resource', 'encoding'=>'string', 'slot'=>'int', 'glyphname'=>'string', 'uv'=>'int'],
'PDF_end_document' => ['bool', 'pdfdoc'=>'resource', 'optlist'=>'string'],
'PDF_end_font' => ['bool', 'pdfdoc'=>'resource'],
'PDF_end_glyph' => ['bool', 'pdfdoc'=>'resource'],
'PDF_end_item' => ['bool', 'pdfdoc'=>'resource', 'id'=>'int'],
'PDF_end_layer' => ['bool', 'pdfdoc'=>'resource'],
'PDF_end_page' => ['bool', 'p'=>'resource'],
'PDF_end_page_ext' => ['bool', 'pdfdoc'=>'resource', 'optlist'=>'string'],
'PDF_end_pattern' => ['bool', 'p'=>'resource'],
'PDF_end_template' => ['bool', 'p'=>'resource'],
'PDF_endpath' => ['bool', 'p'=>'resource'],
'PDF_fill' => ['bool', 'p'=>'resource'],
'PDF_fill_imageblock' => ['int', 'pdfdoc'=>'resource', 'page'=>'int', 'blockname'=>'string', 'image'=>'int', 'optlist'=>'string'],
'PDF_fill_pdfblock' => ['int', 'pdfdoc'=>'resource', 'page'=>'int', 'blockname'=>'string', 'contents'=>'int', 'optlist'=>'string'],
'PDF_fill_stroke' => ['bool', 'p'=>'resource'],
'PDF_fill_textblock' => ['int', 'pdfdoc'=>'resource', 'page'=>'int', 'blockname'=>'string', 'text'=>'string', 'optlist'=>'string'],
'PDF_findfont' => ['int', 'p'=>'resource', 'fontname'=>'string', 'encoding'=>'string', 'embed'=>'int'],
'PDF_fit_image' => ['bool', 'pdfdoc'=>'resource', 'image'=>'int', 'x'=>'float', 'y'=>'float', 'optlist'=>'string'],
'PDF_fit_pdi_page' => ['bool', 'pdfdoc'=>'resource', 'page'=>'int', 'x'=>'float', 'y'=>'float', 'optlist'=>'string'],
'PDF_fit_table' => ['string', 'pdfdoc'=>'resource', 'table'=>'int', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'optlist'=>'string'],
'PDF_fit_textflow' => ['string', 'pdfdoc'=>'resource', 'textflow'=>'int', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'optlist'=>'string'],
'PDF_fit_textline' => ['bool', 'pdfdoc'=>'resource', 'text'=>'string', 'x'=>'float', 'y'=>'float', 'optlist'=>'string'],
'PDF_get_apiname' => ['string', 'pdfdoc'=>'resource'],
'PDF_get_buffer' => ['string', 'p'=>'resource'],
'PDF_get_errmsg' => ['string', 'pdfdoc'=>'resource'],
'PDF_get_errnum' => ['int', 'pdfdoc'=>'resource'],
'PDF_get_majorversion' => ['int'],
'PDF_get_minorversion' => ['int'],
'PDF_get_parameter' => ['string', 'p'=>'resource', 'key'=>'string', 'modifier'=>'float'],
'PDF_get_pdi_parameter' => ['string', 'p'=>'resource', 'key'=>'string', 'doc'=>'int', 'page'=>'int', 'reserved'=>'int'],
'PDF_get_pdi_value' => ['float', 'p'=>'resource', 'key'=>'string', 'doc'=>'int', 'page'=>'int', 'reserved'=>'int'],
'PDF_get_value' => ['float', 'p'=>'resource', 'key'=>'string', 'modifier'=>'float'],
'PDF_info_font' => ['float', 'pdfdoc'=>'resource', 'font'=>'int', 'keyword'=>'string', 'optlist'=>'string'],
'PDF_info_matchbox' => ['float', 'pdfdoc'=>'resource', 'boxname'=>'string', 'num'=>'int', 'keyword'=>'string'],
'PDF_info_table' => ['float', 'pdfdoc'=>'resource', 'table'=>'int', 'keyword'=>'string'],
'PDF_info_textflow' => ['float', 'pdfdoc'=>'resource', 'textflow'=>'int', 'keyword'=>'string'],
'PDF_info_textline' => ['float', 'pdfdoc'=>'resource', 'text'=>'string', 'keyword'=>'string', 'optlist'=>'string'],
'PDF_initgraphics' => ['bool', 'p'=>'resource'],
'PDF_lineto' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float'],
'PDF_load_3ddata' => ['int', 'pdfdoc'=>'resource', 'filename'=>'string', 'optlist'=>'string'],
'PDF_load_font' => ['int', 'pdfdoc'=>'resource', 'fontname'=>'string', 'encoding'=>'string', 'optlist'=>'string'],
'PDF_load_iccprofile' => ['int', 'pdfdoc'=>'resource', 'profilename'=>'string', 'optlist'=>'string'],
'PDF_load_image' => ['int', 'pdfdoc'=>'resource', 'imagetype'=>'string', 'filename'=>'string', 'optlist'=>'string'],
'PDF_makespotcolor' => ['int', 'p'=>'resource', 'spotname'=>'string'],
'PDF_moveto' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float'],
'PDF_new' => ['resource'],
'PDF_open_ccitt' => ['int', 'pdfdoc'=>'resource', 'filename'=>'string', 'width'=>'int', 'height'=>'int', 'bitreverse'=>'int', 'k'=>'int', 'blackls1'=>'int'],
'PDF_open_file' => ['bool', 'p'=>'resource', 'filename'=>'string'],
'PDF_open_image' => ['int', 'p'=>'resource', 'imagetype'=>'string', 'source'=>'string', 'data'=>'string', 'length'=>'int', 'width'=>'int', 'height'=>'int', 'components'=>'int', 'bpc'=>'int', 'params'=>'string'],
'PDF_open_image_file' => ['int', 'p'=>'resource', 'imagetype'=>'string', 'filename'=>'string', 'stringparam'=>'string', 'intparam'=>'int'],
'PDF_open_memory_image' => ['int', 'p'=>'resource', 'image'=>'resource'],
'PDF_open_pdi' => ['int', 'pdfdoc'=>'resource', 'filename'=>'string', 'optlist'=>'string', 'len'=>'int'],
'PDF_open_pdi_document' => ['int', 'p'=>'resource', 'filename'=>'string', 'optlist'=>'string'],
'PDF_open_pdi_page' => ['int', 'p'=>'resource', 'doc'=>'int', 'pagenumber'=>'int', 'optlist'=>'string'],
'PDF_pcos_get_number' => ['float', 'p'=>'resource', 'doc'=>'int', 'path'=>'string'],
'PDF_pcos_get_stream' => ['string', 'p'=>'resource', 'doc'=>'int', 'optlist'=>'string', 'path'=>'string'],
'PDF_pcos_get_string' => ['string', 'p'=>'resource', 'doc'=>'int', 'path'=>'string'],
'PDF_place_image' => ['bool', 'pdfdoc'=>'resource', 'image'=>'int', 'x'=>'float', 'y'=>'float', 'scale'=>'float'],
'PDF_place_pdi_page' => ['bool', 'pdfdoc'=>'resource', 'page'=>'int', 'x'=>'float', 'y'=>'float', 'sx'=>'float', 'sy'=>'float'],
'PDF_process_pdi' => ['int', 'pdfdoc'=>'resource', 'doc'=>'int', 'page'=>'int', 'optlist'=>'string'],
'PDF_rect' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'],
'PDF_restore' => ['bool', 'p'=>'resource'],
'PDF_resume_page' => ['bool', 'pdfdoc'=>'resource', 'optlist'=>'string'],
'PDF_rotate' => ['bool', 'p'=>'resource', 'phi'=>'float'],
'PDF_save' => ['bool', 'p'=>'resource'],
'PDF_scale' => ['bool', 'p'=>'resource', 'sx'=>'float', 'sy'=>'float'],
'PDF_set_border_color' => ['bool', 'p'=>'resource', 'red'=>'float', 'green'=>'float', 'blue'=>'float'],
'PDF_set_border_dash' => ['bool', 'pdfdoc'=>'resource', 'black'=>'float', 'white'=>'float'],
'PDF_set_border_style' => ['bool', 'pdfdoc'=>'resource', 'style'=>'string', 'width'=>'float'],
'PDF_set_gstate' => ['bool', 'pdfdoc'=>'resource', 'gstate'=>'int'],
'PDF_set_info' => ['bool', 'p'=>'resource', 'key'=>'string', 'value'=>'string'],
'PDF_set_layer_dependency' => ['bool', 'pdfdoc'=>'resource', 'type'=>'string', 'optlist'=>'string'],
'PDF_set_parameter' => ['bool', 'p'=>'resource', 'key'=>'string', 'value'=>'string'],
'PDF_set_text_pos' => ['bool', 'p'=>'resource', 'x'=>'float', 'y'=>'float'],
'PDF_set_value' => ['bool', 'p'=>'resource', 'key'=>'string', 'value'=>'float'],
'PDF_setcolor' => ['bool', 'p'=>'resource', 'fstype'=>'string', 'colorspace'=>'string', 'c1'=>'float', 'c2'=>'float', 'c3'=>'float', 'c4'=>'float'],
'PDF_setdash' => ['bool', 'pdfdoc'=>'resource', 'b'=>'float', 'w'=>'float'],
'PDF_setdashpattern' => ['bool', 'pdfdoc'=>'resource', 'optlist'=>'string'],
'PDF_setflat' => ['bool', 'pdfdoc'=>'resource', 'flatness'=>'float'],
'PDF_setfont' => ['bool', 'pdfdoc'=>'resource', 'font'=>'int', 'fontsize'=>'float'],
'PDF_setgray' => ['bool', 'p'=>'resource', 'g'=>'float'],
'PDF_setgray_fill' => ['bool', 'p'=>'resource', 'g'=>'float'],
'PDF_setgray_stroke' => ['bool', 'p'=>'resource', 'g'=>'float'],
'PDF_setlinecap' => ['bool', 'p'=>'resource', 'linecap'=>'int'],
'PDF_setlinejoin' => ['bool', 'p'=>'resource', 'value'=>'int'],
'PDF_setlinewidth' => ['bool', 'p'=>'resource', 'width'=>'float'],
'PDF_setmatrix' => ['bool', 'p'=>'resource', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'e'=>'float', 'f'=>'float'],
'PDF_setmiterlimit' => ['bool', 'pdfdoc'=>'resource', 'miter'=>'float'],
'PDF_setrgbcolor' => ['bool', 'p'=>'resource', 'red'=>'float', 'green'=>'float', 'blue'=>'float'],
'PDF_setrgbcolor_fill' => ['bool', 'p'=>'resource', 'red'=>'float', 'green'=>'float', 'blue'=>'float'],
'PDF_setrgbcolor_stroke' => ['bool', 'p'=>'resource', 'red'=>'float', 'green'=>'float', 'blue'=>'float'],
'PDF_shading' => ['int', 'pdfdoc'=>'resource', 'shtype'=>'string', 'x0'=>'float', 'y0'=>'float', 'x1'=>'float', 'y1'=>'float', 'c1'=>'float', 'c2'=>'float', 'c3'=>'float', 'c4'=>'float', 'optlist'=>'string'],
'PDF_shading_pattern' => ['int', 'pdfdoc'=>'resource', 'shading'=>'int', 'optlist'=>'string'],
'PDF_shfill' => ['bool', 'pdfdoc'=>'resource', 'shading'=>'int'],
'PDF_show' => ['bool', 'pdfdoc'=>'resource', 'text'=>'string'],
'PDF_show_boxed' => ['int', 'p'=>'resource', 'text'=>'string', 'left'=>'float', 'top'=>'float', 'width'=>'float', 'height'=>'float', 'mode'=>'string', 'feature'=>'string'],
'PDF_show_xy' => ['bool', 'p'=>'resource', 'text'=>'string', 'x'=>'float', 'y'=>'float'],
'PDF_skew' => ['bool', 'p'=>'resource', 'alpha'=>'float', 'beta'=>'float'],
'PDF_stringwidth' => ['float', 'p'=>'resource', 'text'=>'string', 'font'=>'int', 'fontsize'=>'float'],
'PDF_stroke' => ['bool', 'p'=>'resource'],
'PDF_suspend_page' => ['bool', 'pdfdoc'=>'resource', 'optlist'=>'string'],
'PDF_translate' => ['bool', 'p'=>'resource', 'tx'=>'float', 'ty'=>'float'],
'PDF_utf16_to_utf8' => ['string', 'pdfdoc'=>'resource', 'utf16string'=>'string'],
'PDF_utf32_to_utf16' => ['string', 'pdfdoc'=>'resource', 'utf32string'=>'string', 'ordering'=>'string'],
'PDF_utf8_to_utf16' => ['string', 'pdfdoc'=>'resource', 'utf8string'=>'string', 'ordering'=>'string'],
'PDO::__construct' => ['void', 'dsn'=>'string', 'username='=>'string', 'passwd='=>'string', 'options='=>'array'],
'PDO::__sleep' => ['int'],
'PDO::__wakeup' => ['void'],
'PDO::beginTransaction' => ['bool'],
'PDO::commit' => ['bool'],
'PDO::cubrid_schema' => ['array', 'schema_type'=>'int', 'table_name='=>'string', 'col_name='=>'string'],
'PDO::errorCode' => ['string'],
'PDO::errorInfo' => ['array'],
'PDO::exec' => ['int', 'query'=>'string'],
'PDO::getAttribute' => ['', 'attribute'=>'int'],
'PDO::getAvailableDrivers' => ['array'],
'PDO::inTransaction' => ['bool'],
'PDO::lastInsertId' => ['string', 'seqname='=>'string'],
'PDO::pgsqlCopyFromArray' => ['bool', 'table_name'=>'string', 'rows'=>'array', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'],
'PDO::pgsqlCopyFromFile' => ['bool', 'table_name'=>'string', 'filename'=>'string', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'],
'PDO::pgsqlCopyToArray' => ['array', 'table_name'=>'string', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'],
'PDO::pgsqlCopyToFile' => ['bool', 'table_name'=>'string', 'filename'=>'string', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'],
'PDO::pgsqlGetNotify' => ['array', 'result_type'=>'int', 'ms_timeout'=>'int'],
'PDO::pgsqlGetPid' => ['int'],
'PDO::pgsqlLOBCreate' => ['string'],
'PDO::pgsqlLOBOpen' => ['resource', 'oid'=>'string', 'mode='=>'string'],
'PDO::pgsqlLOBUnlink' => ['bool', 'oid'=>'string'],
'PDO::prepare' => ['PDOStatement', 'statement'=>'string', 'options='=>'array'],
'PDO::query' => ['PDOStatement', 'sql'=>'string'],
'PDO::query\'1' => ['PDOStatement', 'sql'=>'string', 'fetch_column'=>'int', 'colno'=>'int'],
'PDO::query\'2' => ['PDOStatement', 'sql'=>'string', 'fetch_class'=>'int', 'classname'=>'string', 'ctorargs'=>'array'],
'PDO::query\'3' => ['PDOStatement', 'sql'=>'string', 'fetch_into'=>'int', 'object'=>'object'],
'PDO::quote' => ['string', 'string'=>'string', 'paramtype='=>'int'],
'PDO::rollBack' => ['bool'],
'PDO::setAttribute' => ['bool', 'attribute'=>'int', 'value'=>''],
'PDO::sqliteCreateAggregate' => ['bool', 'function_name'=>'string', 'step_func'=>'callable', 'finalize_func'=>'callable', 'num_args='=>'int'],
'PDO::sqliteCreateCollation' => ['bool', 'name'=>'string', 'callback'=>'callable'],
'PDO::sqliteCreateFunction' => ['bool', 'function_name'=>'string', 'callback'=>'callable', 'num_args='=>'int'],
'pdo_drivers' => ['array'],
'PDOException::getCode' => [''],
'PDOException::getFile' => [''],
'PDOException::getLine' => [''],
'PDOException::getMessage' => [''],
'PDOException::getPrevious' => [''],
'PDOException::getTrace' => [''],
'PDOException::getTraceAsString' => [''],
'PDOStatement::__sleep' => ['int'],
'PDOStatement::__wakeup' => ['void'],
'PDOStatement::bindColumn' => ['bool', 'column'=>'mixed', '&rw_param'=>'mixed', 'type='=>'int', 'maxlen='=>'int', 'driverdata='=>'mixed'],
'PDOStatement::bindParam' => ['bool', 'paramno'=>'mixed', '&rw_param'=>'mixed', 'type='=>'int', 'maxlen='=>'int', 'driverdata='=>'mixed'],
'PDOStatement::bindValue' => ['bool', 'paramno'=>'mixed', 'param'=>'mixed', 'type='=>'int'],
'PDOStatement::closeCursor' => ['bool'],
'PDOStatement::columnCount' => ['int'],
'PDOStatement::debugDumpParams' => ['void'],
'PDOStatement::errorCode' => ['string'],
'PDOStatement::errorInfo' => ['array'],
'PDOStatement::execute' => ['bool', 'bound_input_params='=>'?array'],
'PDOStatement::fetch' => ['mixed', 'how='=>'int', 'orientation='=>'int', 'offset='=>'int'],
'PDOStatement::fetchAll' => ['array', 'how='=>'int', 'fetch_argument='=>'int|string|callable', 'ctor_args='=>'?array'],
'PDOStatement::fetchColumn' => ['string', 'column_number='=>'int'],
'PDOStatement::fetchObject' => ['mixed', 'class_name='=>'string', 'ctor_args='=>'?array'],
'PDOStatement::getAttribute' => ['mixed', 'attribute'=>'int'],
'PDOStatement::getColumnMeta' => ['array', 'column'=>'int'],
'PDOStatement::nextRowset' => ['bool'],
'PDOStatement::rowCount' => ['int'],
'PDOStatement::setAttribute' => ['bool', 'attribute'=>'int', 'value'=>'mixed'],
'PDOStatement::setFetchMode' => ['bool', 'mode'=>'int'],
'PDOStatement::setFetchMode\'1' => ['bool', 'fetch_column'=>'int', 'colno'=>'int'],
'PDOStatement::setFetchMode\'2' => ['bool', 'fetch_class'=>'int', 'classname'=>'string', 'ctorargs'=>'array'],
'PDOStatement::setFetchMode\'3' => ['bool', 'fetch_into'=>'int', 'object'=>'object'],
'pfsockopen' => ['resource', 'hostname'=>'string', 'port='=>'int', '&w_errno='=>'int', '&w_errstr='=>'string', 'timeout='=>'float'],
'pg_affected_rows' => ['int', 'result'=>'resource'],
'pg_cancel_query' => ['bool', 'connection'=>'resource'],
'pg_client_encoding' => ['string', 'connection='=>'resource'],
'pg_close' => ['bool', 'connection='=>'resource'],
'pg_connect' => ['resource', 'connection_string'=>'string', 'connect_type='=>'int'],
'pg_connect_poll' => ['resource', 'connection'=>'resource'],
'pg_connection_busy' => ['bool', 'connection'=>'resource'],
'pg_connection_reset' => ['bool', 'connection'=>'resource'],
'pg_connection_status' => ['int', 'connection'=>'resource'],
'pg_consume_input' => ['bool', 'connection'=>'resource'],
'pg_convert' => ['array', 'db'=>'resource', 'table'=>'string', 'values'=>'array', 'options='=>'int'],
'pg_copy_from' => ['bool', 'connection'=>'resource', 'table_name'=>'string', 'rows'=>'array', 'delimiter='=>'string', 'null_as='=>'string'],
'pg_copy_to' => ['array', 'connection'=>'resource', 'table_name'=>'string', 'delimiter='=>'string', 'null_as='=>'string'],
'pg_dbname' => ['string', 'connection='=>'resource'],
'pg_delete' => ['mixed', 'db'=>'resource', 'table'=>'string', 'ids'=>'array', 'options='=>'int'],
'pg_end_copy' => ['bool', 'connection='=>'resource'],
'pg_escape_bytea' => ['string', 'connection='=>'resource', 'data='=>'string'],
'pg_escape_bytea\'1' => ['string', 'data'=>'string'],
'pg_escape_identifier' => ['string', 'connection'=>'resource', 'data'=>'string'],
'pg_escape_identifier\'1' => ['string', 'data'=>'string'],
'pg_escape_literal' => ['string', 'connection'=>'resource', 'data'=>'string'],
'pg_escape_literal\'1' => ['string', 'data'=>'string'],
'pg_escape_string' => ['string', 'connection='=>'resource', 'data='=>'string'],
'pg_escape_string\'1' => ['string', 'data'=>'string'],
'pg_execute' => ['resource', 'connection='=>'resource', 'stmtname='=>'string', 'params='=>'array'],
'pg_execute\'1' => ['resource', 'stmtname'=>'string', 'params'=>'array'],
'pg_fetch_all' => ['array', 'result'=>'resource', 'result_type='=>'int'],
'pg_fetch_all_columns' => ['array', 'result'=>'resource', 'column_number='=>'int'],
'pg_fetch_array' => ['array', 'result'=>'resource', 'row='=>'int', 'result_type='=>'int'],
'pg_fetch_assoc' => ['array', 'result'=>'resource', 'row='=>'int'],
'pg_fetch_object' => ['object', 'result'=>'', 'row='=>'int', 'result_type='=>'int'],
'pg_fetch_object\'1' => ['object', 'result'=>'', 'row='=>'int', 'class_name='=>'string', 'ctor_params='=>'array'],
'pg_fetch_result' => ['', 'result'=>'', 'field_name'=>'string|int'],
'pg_fetch_result\'1' => ['', 'result'=>'', 'row_number'=>'int', 'field_name'=>'string|int'],
'pg_fetch_row' => ['array', 'result'=>'resource', 'row='=>'int', 'result_type='=>'int'],
'pg_field_is_null' => ['int', 'result'=>'', 'field_name_or_number'=>'string|int'],
'pg_field_is_null\'1' => ['int', 'result'=>'', 'row'=>'int', 'field_name_or_number'=>'string|int'],
'pg_field_name' => ['string', 'result'=>'resource', 'field_number'=>'int'],
'pg_field_num' => ['int', 'result'=>'resource', 'field_name'=>'string'],
'pg_field_prtlen' => ['int', 'result'=>'', 'field_name_or_number'=>''],
'pg_field_prtlen\'1' => ['int', 'result'=>'', 'row'=>'int', 'field_name_or_number'=>'string|int'],
'pg_field_size' => ['int', 'result'=>'resource', 'field_number'=>'int'],
'pg_field_table' => ['mixed', 'result'=>'resource', 'field_number'=>'int', 'oid_only='=>'bool'],
'pg_field_type' => ['string', 'result'=>'resource', 'field_number'=>'int'],
'pg_field_type_oid' => ['string', 'result'=>'resource', 'field_number'=>'int'],
'pg_flush' => ['mixed', 'connection'=>'resource'],
'pg_free_result' => ['bool', 'result'=>'resource'],
'pg_get_notify' => ['array', 'connection'=>'resource', 'result_type='=>'int'],
'pg_get_pid' => ['int|false', 'connection'=>'resource'],
'pg_get_result' => ['resource|false', 'connection='=>'resource'],
'pg_host' => ['string', 'connection='=>'resource'],
'pg_insert' => ['mixed', 'db'=>'resource', 'table'=>'string', 'values'=>'array', 'options='=>'int'],
'pg_last_error' => ['string', 'connection='=>'resource', 'operation='=>'int'],
'pg_last_notice' => ['string', 'connection'=>'resource'],
'pg_last_oid' => ['string', 'result'=>'resource'],
'pg_lo_close' => ['bool', 'large_object'=>'resource'],
'pg_lo_create' => ['int', 'connection='=>'resource', 'large_object_oid='=>''],
'pg_lo_export' => ['bool', 'connection'=>'resource', 'oid'=>'int', 'filename'=>'string'],
'pg_lo_export\'1' => ['bool', 'oid'=>'int', 'pathname'=>'string'],
'pg_lo_import' => ['int', 'connection'=>'resource', 'pathname'=>'string', 'oid'=>''],
'pg_lo_import\'1' => ['int', 'pathname'=>'string', 'oid'=>''],
'pg_lo_open' => ['resource', 'connection'=>'resource', 'oid'=>'int', 'mode'=>'string'],
'pg_lo_read' => ['string', 'large_object'=>'resource', 'len='=>'int'],
'pg_lo_read_all' => ['int', 'large_object'=>'resource'],
'pg_lo_seek' => ['bool', 'large_object'=>'resource', 'offset'=>'int', 'whence='=>'int'],
'pg_lo_tell' => ['int', 'large_object'=>'resource'],
'pg_lo_truncate' => ['bool', 'large_object'=>'resource', 'size'=>'int'],
'pg_lo_unlink' => ['bool', 'connection'=>'resource', 'oid'=>'int'],
'pg_lo_write' => ['int', 'large_object'=>'resource', 'data'=>'string', 'len='=>'int'],
'pg_meta_data' => ['array', 'db'=>'resource', 'table'=>'string', 'extended='=>'bool'],
'pg_num_fields' => ['int', 'result'=>'resource'],
'pg_num_rows' => ['int', 'result'=>'resource'],
'pg_options' => ['string', 'connection='=>'resource'],
'pg_parameter_status' => ['string|false', 'connection='=>'resource', 'param_name='=>'string'],
'pg_parameter_status\'1' => ['string|false', 'param_name'=>'string'],
'pg_pconnect' => ['resource', 'connection_string'=>'string', 'host='=>'string', 'port='=>'string|int', 'options='=>'string', 'tty='=>'string', 'database='=>'string'],
'pg_ping' => ['bool', 'connection='=>'resource'],
'pg_port' => ['int', 'connection='=>'resource'],
'pg_prepare' => ['resource', 'connection='=>'resource', 'stmtname='=>'string', 'query='=>'string'],
'pg_prepare\'1' => ['resource', 'stmtname'=>'string', 'query'=>'string'],
'pg_put_line' => ['bool', 'connection='=>'resource', 'data='=>'string'],
'pg_put_line\'1' => ['bool', 'data'=>'string'],
'pg_query' => ['resource', 'connection='=>'resource', 'query='=>'string'],
'pg_query\'1' => ['resource', 'query'=>'string'],
'pg_query_params' => ['resource', 'connection='=>'resource', 'query='=>'string', 'params='=>'array'],
'pg_query_params\'1' => ['resource', 'query'=>'string', 'params'=>'array'],
'pg_result_error' => ['string|false', 'result'=>'resource'],
'pg_result_error_field' => ['string|?false', 'result'=>'resource', 'fieldcode'=>'int'],
'pg_result_seek' => ['bool', 'result'=>'resource', 'offset'=>'int'],
'pg_result_status' => ['mixed', 'result'=>'resource', 'result_type='=>'int'],
'pg_select' => ['mixed', 'db'=>'resource', 'table'=>'string', 'ids'=>'array', 'options='=>'int', 'result_type='=>'int'],
'pg_send_execute' => ['bool', 'connection'=>'resource', 'stmtname'=>'string', 'params'=>'array'],
'pg_send_prepare' => ['bool', 'connection'=>'resource', 'stmtname'=>'string', 'query'=>'string'],
'pg_send_query' => ['bool', 'connection'=>'resource', 'query'=>'string'],
'pg_send_query_params' => ['bool', 'connection'=>'resource', 'query'=>'string', 'params'=>'array'],
'pg_set_client_encoding' => ['int', 'connection='=>'resource', 'encoding='=>'string'],
'pg_set_client_encoding\'1' => ['int', 'connection'=>'resource', 'encoding'=>'string'],
'pg_set_error_verbosity' => ['int', 'connection='=>'resource', 'verbosity='=>'int'],
'pg_set_error_verbosity\'1' => ['int', 'verbosity'=>'int'],
'pg_socket' => ['resource', 'connection'=>'resource'],
'pg_trace' => ['bool', 'filename'=>'string', 'mode='=>'string', 'connection='=>'resource'],
'pg_transaction_status' => ['int', 'connection'=>'resource'],
'pg_tty' => ['string', 'connection='=>'resource'],
'pg_tty\'1' => ['string'],
'pg_unescape_bytea' => ['string', 'data'=>'string'],
'pg_untrace' => ['bool', 'connection='=>'resource'],
'pg_untrace\'1' => ['bool'],
'pg_update' => ['mixed', 'db'=>'resource', 'table'=>'string', 'fields'=>'array', 'ids'=>'array', 'options='=>'int'],
'pg_version' => ['array', 'connection='=>'resource'],
'Phar::__construct' => ['void', 'fname'=>'string', 'flags='=>'int', 'alias='=>'string'],
'Phar::addEmptyDir' => ['', 'dirname'=>'string'],
'Phar::addFile' => ['', 'file'=>'string', 'localname='=>'string'],
'Phar::addFromString' => ['', 'localname'=>'string', 'contents'=>'string'],
'Phar::apiVersion' => ['string'],
'Phar::buildFromDirectory' => ['array', 'base_dir'=>'string', 'regex='=>'string'],
'Phar::buildFromIterator' => ['array', 'iter'=>'iterator', 'base_directory='=>'string'],
'Phar::canCompress' => ['bool', 'method='=>'int'],
'Phar::canWrite' => ['bool'],
'Phar::compress' => ['object', 'compression'=>'int', 'extension='=>'string'],
'Phar::compressAllFilesBZIP2' => ['bool'],
'Phar::compressAllFilesGZ' => ['bool'],
'Phar::compressFiles' => ['', 'compression'=>'int'],
'Phar::convertToData' => ['PharData', 'format='=>'int', 'compression='=>'int', 'extension='=>'string'],
'Phar::convertToExecutable' => ['Phar', 'format='=>'int', 'compression='=>'int', 'extension='=>'string'],
'Phar::copy' => ['bool', 'oldfile'=>'string', 'newfile'=>'string'],
'Phar::count' => ['int'],
'Phar::createDefaultStub' => ['string', 'indexfile='=>'string', 'webindexfile='=>'string'],
'Phar::decompress' => ['object', 'extension='=>'string'],
'Phar::decompressFiles' => ['bool'],
'Phar::delete' => ['bool', 'entry'=>'string'],
'Phar::delMetadata' => ['bool'],
'Phar::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string|array', 'overwrite='=>'bool'],
'Phar::getAlias' => ['string'],
'Phar::getMetadata' => ['mixed'],
'Phar::getModified' => ['bool'],
'Phar::getPath' => ['string'],
'Phar::getSignature' => ['array'],
'Phar::getStub' => ['string'],
'Phar::getSupportedCompression' => ['array'],
'Phar::getSupportedSignatures' => ['array'],
'Phar::getVersion' => ['string'],
'Phar::hasMetadata' => ['bool'],
'Phar::interceptFileFuncs' => [''],
'Phar::isBuffering' => ['bool'],
'Phar::isCompressed' => [''],
'Phar::isFileFormat' => ['bool', 'format'=>'int'],
'Phar::isValidPharFilename' => ['bool', 'filename'=>'string', 'executable='=>'bool'],
'Phar::isWritable' => ['bool'],
'Phar::loadPhar' => ['bool', 'filename'=>'string', 'alias='=>'string'],
'Phar::mapPhar' => ['bool', 'alias='=>'string', 'dataoffset='=>'int'],
'Phar::mount' => ['void', 'pharpath'=>'string', 'externalpath'=>'string'],
'Phar::mungServer' => ['', 'munglist'=>'array'],
'Phar::offsetExists' => ['bool', 'offset'=>'string'],
'Phar::offsetGet' => ['int', 'offset'=>'string'],
'Phar::offsetSet' => ['', 'offset'=>'string', 'value'=>'string'],
'Phar::offsetUnset' => ['bool', 'offset'=>'string'],
'Phar::running' => ['string', 'retphar='=>'bool'],
'Phar::setAlias' => ['bool', 'alias'=>'string'],
'Phar::setDefaultStub' => ['bool', 'index='=>'string', 'webindex='=>'string'],
'Phar::setMetadata' => ['', 'metadata'=>''],
'Phar::setSignatureAlgorithm' => ['', 'sigtype'=>'int', 'privatekey='=>'string'],
'Phar::setStub' => ['bool', 'stub'=>'string'],
'Phar::startBuffering' => [''],
'Phar::stopBuffering' => [''],
'Phar::uncompressAllFiles' => ['bool'],
'Phar::unlinkArchive' => ['bool', 'archive'=>'string'],
'Phar::webPhar' => ['', 'alias='=>'string', 'index='=>'string', 'f404='=>'string', 'mimetypes='=>'array', 'rewrites='=>'array'],
'PharData::__construct' => ['void', 'fname'=>'string', 'flags='=>'int', 'alias='=>'string', 'format='=>'int'],
'PharData::addEmptyDir' => ['bool', 'dirname'=>'string'],
'PharData::addFile' => ['', 'file'=>'string', 'localname='=>'string'],
'PharData::addFromString' => ['bool', 'localname'=>'string', 'contents'=>'string'],
'PharData::buildFromDirectory' => ['array', 'base_dir'=>'string', 'regex='=>'string'],
'PharData::buildFromIterator' => ['array', 'iter'=>'iterator', 'base_directory='=>'string'],
'PharData::compress' => ['object', 'compression'=>'int', 'extension='=>'string'],
'PharData::compressFiles' => ['bool', 'compression'=>'int'],
'PharData::convertToData' => ['PharData', 'format='=>'int', 'compression='=>'int', 'extension='=>'string'],
'PharData::convertToExecutable' => ['Phar', 'format='=>'int', 'compression='=>'int', 'extension='=>'string'],
'PharData::copy' => ['bool', 'oldfile'=>'string', 'newfile'=>'string'],
'PharData::decompress' => ['object', 'extension='=>'string'],
'PharData::decompressFiles' => ['bool'],
'PharData::delete' => ['bool', 'entry'=>'string'],
'PharData::delMetadata' => ['bool'],
'PharData::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string|array', 'overwrite='=>'bool'],
'PharData::isWritable' => ['bool'],
'PharData::offsetSet' => ['', 'offset'=>'string', 'value'=>'string'],
'PharData::offsetUnset' => ['bool', 'offset'=>'string'],
'PharData::setAlias' => ['bool', 'alias'=>'string'],
'PharData::setDefaultStub' => ['bool', 'index='=>'string', 'webindex='=>'string'],
'PharData::setStub' => ['bool', 'stub'=>'string'],
'PharFileInfo::__construct' => ['void', 'entry'=>'string'],
'PharFileInfo::chmod' => ['void', 'permissions'=>'int'],
'PharFileInfo::compress' => ['bool', 'compression'=>'int'],
'PharFileInfo::decompress' => ['bool'],
'PharFileInfo::delMetadata' => ['bool'],
'PharFileInfo::getCompressedSize' => ['int'],
'PharFileInfo::getContent' => ['string'],
'PharFileInfo::getCRC32' => ['int'],
'PharFileInfo::getMetadata' => ['mixed'],
'PharFileInfo::getPharFlags' => ['int'],
'PharFileInfo::hasMetadata' => ['bool'],
'PharFileInfo::isCompressed' => ['bool', 'compression_type='=>'int'],
'PharFileInfo::isCompressedBZIP2' => ['bool'],
'PharFileInfo::isCompressedGZ' => ['bool'],
'PharFileInfo::isCRCChecked' => ['bool'],
'PharFileInfo::setCompressedBZIP2' => ['bool'],
'PharFileInfo::setCompressedGZ' => ['bool'],
'PharFileInfo::setMetadata' => ['void', 'metadata'=>'mixed'],
'PharFileInfo::setUncompressed' => ['bool'],
'phdfs::__construct' => ['void', 'ip'=>'string', 'port'=>'string'],
'phdfs::__destruct' => [''],
'phdfs::connect' => ['bool'],
'phdfs::copy' => ['bool', 'source_file'=>'string', 'destination_file'=>'string'],
'phdfs::create_directory' => ['bool', 'path'=>'string'],
'phdfs::delete' => ['bool', 'path'=>'string'],
'phdfs::disconnect' => ['bool'],
'phdfs::exists' => ['bool', 'path'=>'string'],
'phdfs::file_info' => ['array', 'path'=>'string'],
'phdfs::list_directory' => ['array', 'path'=>'string'],
'phdfs::read' => ['string', 'path'=>'string', 'length='=>'string'],
'phdfs::rename' => ['bool', 'old_path'=>'string', 'new_path'=>'string'],
'phdfs::tell' => ['int', 'path'=>'string'],
'phdfs::write' => ['bool', 'path'=>'string', 'buffer'=>'string', 'mode='=>'string'],
'php_check_syntax' => ['bool', 'filename'=>'string', 'error_message='=>'string'],
'php_ini_loaded_file' => ['string'],
'php_ini_scanned_files' => ['string'],
'php_logo_guid' => ['string'],
'php_sapi_name' => ['string'],
'php_strip_whitespace' => ['string', 'file_name'=>'string'],
'php_uname' => ['string', 'mode='=>'string'],
'php_user_filter::filter' => ['int', 'in'=>'resource', 'out'=>'resource', '&rw_consumed'=>'int', 'closing'=>'bool'],
'php_user_filter::onClose' => ['void'],
'php_user_filter::onCreate' => ['bool'],
'phpcredits' => ['bool', 'flag='=>'int'],
'phpdbg_break' => [''],
'phpdbg_break_file' => ['', 'file'=>'string', 'line'=>'int'],
'phpdbg_break_function' => ['', 'function'=>'string'],
'phpdbg_break_method' => ['', 'class'=>'string', 'method'=>'string'],
'phpdbg_clear' => [''],
'phpdbg_color' => ['', 'element'=>'int', 'color'=>'string'],
'phpdbg_end_oplog' => [''],
'phpdbg_prompt' => ['', 'prompt'=>'string'],
'phpdbg_start_oplog' => [''],
'phpinfo' => ['bool', 'what='=>'int'],
'phpversion' => ['string|false', 'extension='=>'string'],
'pht\AtomicInteger::__construct' => ['void', 'value='=>'int'],
'pht\AtomicInteger::dec' => ['void'],
'pht\AtomicInteger::get' => ['int'],
'pht\AtomicInteger::inc' => ['void'],
'pht\AtomicInteger::lock' => ['void'],
'pht\AtomicInteger::set' => ['void', 'value'=>'int'],
'pht\AtomicInteger::unlock' => ['void'],
'pht\HashTable::lock' => ['void'],
'pht\HashTable::size' => ['int'],
'pht\HashTable::unlock' => ['void'],
'pht\Queue::front' => ['mixed'],
'pht\Queue::lock' => ['void'],
'pht\Queue::pop' => ['mixed'],
'pht\Queue::push' => ['void', 'value'=>'mixed'],
'pht\Queue::size' => ['int'],
'pht\Queue::unlock' => ['void'],
'pht\Runnable::run' => ['void'],
'pht\Vector::__construct' => ['void', 'size='=>'int', 'value='=>'mixed'],
'pht\Vector::deleteAt' => ['void', 'offset'=>'int'],
'pht\Vector::insertAt' => ['void', 'value'=>'mixed', 'offset'=>'int'],
'pht\Vector::lock' => ['void'],
'pht\Vector::pop' => ['mixed'],
'pht\Vector::push' => ['void', 'value'=>'mixed'],
'pht\Vector::resize' => ['void', 'size'=>'int', 'value='=>'mixed'],
'pht\Vector::shift' => ['mixed'],
'pht\Vector::size' => ['int'],
'pht\Vector::unlock' => ['void'],
'pht\Vector::unshift' => ['void', 'value'=>'mixed'],
'pht\Vector::updateAt' => ['void', 'value'=>'mixed', 'offset'=>'int'],
'pi' => ['float'],
'png2wbmp' => ['bool', 'pngname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'],
'pointObj::__construct' => ['void'],
'pointObj::distanceToLine' => ['float', 'p1'=>'pointObj', 'p2'=>'pointObj'],
'pointObj::distanceToPoint' => ['float', 'poPoint'=>'pointObj'],
'pointObj::distanceToShape' => ['float', 'shape'=>'shapeObj'],
'pointObj::draw' => ['int', 'map'=>'MapObj', 'layer'=>'layerObj', 'img'=>'imageObj', 'class_index'=>'int', 'text'=>'string'],
'pointObj::ms_newPointObj' => ['pointObj'],
'pointObj::project' => ['int', 'in'=>'projectionObj', 'out'=>'projectionObj'],
'pointObj::setXY' => ['int', 'x'=>'float', 'y'=>'float', 'm'=>'float'],
'pointObj::setXYZ' => ['int', 'x'=>'float', 'y'=>'float', 'z'=>'float', 'm'=>'float'],
'Pool::__construct' => ['void', 'size'=>'int', 'class'=>'string', 'ctor='=>'array'],
'Pool::collect' => ['void', 'collector'=>'Callable'],
'Pool::resize' => ['void', 'size'=>'int'],
'Pool::shutdown' => ['void'],
'Pool::submit' => ['int', 'task'=>'Threaded'],
'Pool::submitTo' => ['int', 'worker'=>'int', 'task'=>'Threaded'],
'popen' => ['resource', 'command'=>'string', 'mode'=>'string'],
'pos' => ['mixed', 'array_arg'=>'array'],
'posix_access' => ['bool', 'file'=>'string', 'mode='=>'int'],
'posix_ctermid' => ['string'],
'posix_errno' => ['int'],
'posix_get_last_error' => ['int'],
'posix_getcwd' => ['string'],
'posix_getegid' => ['int'],
'posix_geteuid' => ['int'],
'posix_getgid' => ['int'],
'posix_getgrgid' => ['array', 'gid'=>'int'],
'posix_getgrnam' => ['array|false', 'groupname'=>'string'],
'posix_getgroups' => ['array'],
'posix_getlogin' => ['string'],
'posix_getpgid' => ['int', 'pid'=>'int'],
'posix_getpgrp' => ['int'],
'posix_getpid' => ['int'],
'posix_getppid' => ['int'],
'posix_getpwnam' => ['array|false', 'groupname'=>'string'],
'posix_getpwuid' => ['array', 'uid'=>'int'],
'posix_getrlimit' => ['array'],
'posix_getsid' => ['int', 'pid'=>'int'],
'posix_getuid' => ['int'],
'posix_initgroups' => ['bool', 'name'=>'string', 'base_group_id'=>'int'],
'posix_isatty' => ['bool', 'fd'=>'resource|int'],
'posix_kill' => ['bool', 'pid'=>'int', 'sig'=>'int'],
'posix_mkfifo' => ['bool', 'pathname'=>'string', 'mode'=>'int'],
'posix_mknod' => ['bool', 'pathname'=>'string', 'mode'=>'int', 'major='=>'int', 'minor='=>'int'],
'posix_setegid' => ['bool', 'uid'=>'int'],
'posix_seteuid' => ['bool', 'uid'=>'int'],
'posix_setgid' => ['bool', 'uid'=>'int'],
'posix_setpgid' => ['bool', 'pid'=>'int', 'pgid'=>'int'],
'posix_setrlimit' => ['bool', 'resource'=>'int', 'softlimit'=>'int', 'hardlimit'=>'int'],
'posix_setsid' => ['int'],
'posix_setuid' => ['bool', 'uid'=>'int'],
'posix_strerror' => ['string', 'errno'=>'int'],
'posix_times' => ['array'],
'posix_ttyname' => ['string|false', 'fd'=>'resource|int'],
'posix_uname' => ['array'],
'pow' => ['float', 'base'=>'float', 'exponent'=>'float'],
'preg_filter' => ['mixed', 'regex'=>'mixed', 'replace'=>'mixed', 'subject'=>'mixed', 'limit='=>'int', '&w_count='=>'int'],
'preg_grep' => ['array', 'regex'=>'string', 'input'=>'array', 'flags='=>'int'],
'preg_last_error' => ['int'],
'preg_match' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_match_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_quote' => ['string', 'str'=>'string', 'delim_char='=>'string'],
'preg_replace' => ['string|array', 'regex'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'],
'preg_replace_callback' => ['string|array', 'regex'=>'string|array', 'callback'=>'callable(array):string', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'],
'preg_replace_callback_array' => ['string|array', 'pattern'=>'array<string,callable(array):string>', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'],
'preg_split' => ['array', 'pattern'=>'string', 'subject'=>'string', 'limit='=>'?int', 'flags='=>'int'],
'prev' => ['mixed', '&rw_array_arg'=>'array'],
'print' => ['int', 'arg'=>'string'],
'print_r' => ['string|true', 'var'=>'mixed', 'return='=>'bool'],
'printf' => ['int', 'format'=>'string', '...args='=>'string|int|float'],
'proc_close' => ['int', 'process'=>'resource'],
'proc_get_status' => ['array', 'process'=>'resource'],
'proc_nice' => ['bool', 'priority'=>'int'],
'proc_open' => ['resource|false', 'command'=>'string', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'],
'proc_terminate' => ['bool', 'process'=>'resource', 'signal='=>'int'],
'projectionObj::__construct' => ['void', 'projectionString'=>'string'],
'projectionObj::getUnits' => ['int'],
'projectionObj::ms_newProjectionObj' => ['projectionObj', 'projectionString'=>'string'],
'property_exists' => ['bool', 'object_or_class'=>'object|string', 'property_name'=>'string'],
'ps_add_bookmark' => ['int', 'psdoc'=>'resource', 'text'=>'string', 'parent='=>'int', 'open='=>'int'],
'ps_add_launchlink' => ['bool', 'psdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'filename'=>'string'],
'ps_add_locallink' => ['bool', 'psdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'page'=>'int', 'dest'=>'string'],
'ps_add_note' => ['bool', 'psdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'contents'=>'string', 'title'=>'string', 'icon'=>'string', 'open'=>'int'],
'ps_add_pdflink' => ['bool', 'psdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'filename'=>'string', 'page'=>'int', 'dest'=>'string'],
'ps_add_weblink' => ['bool', 'psdoc'=>'resource', 'llx'=>'float', 'lly'=>'float', 'urx'=>'float', 'ury'=>'float', 'url'=>'string'],
'ps_arc' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'alpha'=>'float', 'beta'=>'float'],
'ps_arcn' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float', 'radius'=>'float', 'alpha'=>'float', 'beta'=>'float'],
'ps_begin_page' => ['bool', 'psdoc'=>'resource', 'width'=>'float', 'height'=>'float'],
'ps_begin_pattern' => ['int', 'psdoc'=>'resource', 'width'=>'float', 'height'=>'float', 'xstep'=>'float', 'ystep'=>'float', 'painttype'=>'int'],
'ps_begin_template' => ['int', 'psdoc'=>'resource', 'width'=>'float', 'height'=>'float'],
'ps_circle' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float', 'radius'=>'float'],
'ps_clip' => ['bool', 'psdoc'=>'resource'],
'ps_close' => ['bool', 'psdoc'=>'resource'],
'ps_close_image' => ['void', 'psdoc'=>'resource', 'imageid'=>'int'],
'ps_closepath' => ['bool', 'psdoc'=>'resource'],
'ps_closepath_stroke' => ['bool', 'psdoc'=>'resource'],
'ps_continue_text' => ['bool', 'psdoc'=>'resource', 'text'=>'string'],
'ps_curveto' => ['bool', 'psdoc'=>'resource', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'],
'ps_delete' => ['bool', 'psdoc'=>'resource'],
'ps_end_page' => ['bool', 'psdoc'=>'resource'],
'ps_end_pattern' => ['bool', 'psdoc'=>'resource'],
'ps_end_template' => ['bool', 'psdoc'=>'resource'],
'ps_fill' => ['bool', 'psdoc'=>'resource'],
'ps_fill_stroke' => ['bool', 'psdoc'=>'resource'],
'ps_findfont' => ['int', 'psdoc'=>'resource', 'fontname'=>'string', 'encoding'=>'string', 'embed='=>'bool'],
'ps_get_buffer' => ['string', 'psdoc'=>'resource'],
'ps_get_parameter' => ['string', 'psdoc'=>'resource', 'name'=>'string', 'modifier='=>'float'],
'ps_get_value' => ['float', 'psdoc'=>'resource', 'name'=>'string', 'modifier='=>'float'],
'ps_hyphenate' => ['array', 'psdoc'=>'resource', 'text'=>'string'],
'ps_include_file' => ['bool', 'psdoc'=>'resource', 'file'=>'string'],
'ps_lineto' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float'],
'ps_makespotcolor' => ['int', 'psdoc'=>'resource', 'name'=>'string', 'reserved='=>'int'],
'ps_moveto' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float'],
'ps_new' => ['resource'],
'ps_open_file' => ['bool', 'psdoc'=>'resource', 'filename='=>'string'],
'ps_open_image' => ['int', 'psdoc'=>'resource', 'type'=>'string', 'source'=>'string', 'data'=>'string', 'length'=>'int', 'width'=>'int', 'height'=>'int', 'components'=>'int', 'bpc'=>'int', 'params'=>'string'],
'ps_open_image_file' => ['int', 'psdoc'=>'resource', 'type'=>'string', 'filename'=>'string', 'stringparam='=>'string', 'intparam='=>'int'],
'ps_open_memory_image' => ['int', 'psdoc'=>'resource', 'gd'=>'int'],
'ps_place_image' => ['bool', 'psdoc'=>'resource', 'imageid'=>'int', 'x'=>'float', 'y'=>'float', 'scale'=>'float'],
'ps_rect' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float'],
'ps_restore' => ['bool', 'psdoc'=>'resource'],
'ps_rotate' => ['bool', 'psdoc'=>'resource', 'rot'=>'float'],
'ps_save' => ['bool', 'psdoc'=>'resource'],
'ps_scale' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float'],
'ps_set_border_color' => ['bool', 'psdoc'=>'resource', 'red'=>'float', 'green'=>'float', 'blue'=>'float'],
'ps_set_border_dash' => ['bool', 'psdoc'=>'resource', 'black'=>'float', 'white'=>'float'],
'ps_set_border_style' => ['bool', 'psdoc'=>'resource', 'style'=>'string', 'width'=>'float'],
'ps_set_info' => ['bool', 'p'=>'resource', 'key'=>'string', 'val'=>'string'],
'ps_set_parameter' => ['bool', 'psdoc'=>'resource', 'name'=>'string', 'value'=>'string'],
'ps_set_text_pos' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float'],
'ps_set_value' => ['bool', 'psdoc'=>'resource', 'name'=>'string', 'value'=>'float'],
'ps_setcolor' => ['bool', 'psdoc'=>'resource', 'type'=>'string', 'colorspace'=>'string', 'c1'=>'float', 'c2'=>'float', 'c3'=>'float', 'c4'=>'float'],
'ps_setdash' => ['bool', 'psdoc'=>'resource', 'on'=>'float', 'off'=>'float'],
'ps_setflat' => ['bool', 'psdoc'=>'resource', 'value'=>'float'],
'ps_setfont' => ['bool', 'psdoc'=>'resource', 'fontid'=>'int', 'size'=>'float'],
'ps_setgray' => ['bool', 'psdoc'=>'resource', 'gray'=>'float'],
'ps_setlinecap' => ['bool', 'psdoc'=>'resource', 'type'=>'int'],
'ps_setlinejoin' => ['bool', 'psdoc'=>'resource', 'type'=>'int'],
'ps_setlinewidth' => ['bool', 'psdoc'=>'resource', 'width'=>'float'],
'ps_setmiterlimit' => ['bool', 'psdoc'=>'resource', 'value'=>'float'],
'ps_setoverprintmode' => ['bool', 'psdoc'=>'resource', 'mode'=>'int'],
'ps_setpolydash' => ['bool', 'psdoc'=>'resource', 'arr'=>'float'],
'ps_shading' => ['int', 'psdoc'=>'resource', 'type'=>'string', 'x0'=>'float', 'y0'=>'float', 'x1'=>'float', 'y1'=>'float', 'c1'=>'float', 'c2'=>'float', 'c3'=>'float', 'c4'=>'float', 'optlist'=>'string'],
'ps_shading_pattern' => ['int', 'psdoc'=>'resource', 'shadingid'=>'int', 'optlist'=>'string'],
'ps_shfill' => ['bool', 'psdoc'=>'resource', 'shadingid'=>'int'],
'ps_show' => ['bool', 'psdoc'=>'resource', 'text'=>'string'],
'ps_show2' => ['bool', 'psdoc'=>'resource', 'text'=>'string', 'len'=>'int'],
'ps_show_boxed' => ['int', 'psdoc'=>'resource', 'text'=>'string', 'left'=>'float', 'bottom'=>'float', 'width'=>'float', 'height'=>'float', 'hmode'=>'string', 'feature='=>'string'],
'ps_show_xy' => ['bool', 'psdoc'=>'resource', 'text'=>'string', 'x'=>'float', 'y'=>'float'],
'ps_show_xy2' => ['bool', 'psdoc'=>'resource', 'text'=>'string', 'len'=>'int', 'xcoor'=>'float', 'ycoor'=>'float'],
'ps_string_geometry' => ['array', 'psdoc'=>'resource', 'text'=>'string', 'fontid='=>'int', 'size='=>'float'],
'ps_stringwidth' => ['float', 'psdoc'=>'resource', 'text'=>'string', 'fontid='=>'int', 'size='=>'float'],
'ps_stroke' => ['bool', 'psdoc'=>'resource'],
'ps_symbol' => ['bool', 'psdoc'=>'resource', 'ord'=>'int'],
'ps_symbol_name' => ['string', 'psdoc'=>'resource', 'ord'=>'int', 'fontid='=>'int'],
'ps_symbol_width' => ['float', 'psdoc'=>'resource', 'ord'=>'int', 'fontid='=>'int', 'size='=>'float'],
'ps_translate' => ['bool', 'psdoc'=>'resource', 'x'=>'float', 'y'=>'float'],
'pspell_add_to_personal' => ['bool', 'pspell'=>'int', 'word'=>'string'],
'pspell_add_to_session' => ['bool', 'pspell'=>'int', 'word'=>'string'],
'pspell_check' => ['bool', 'pspell'=>'int', 'word'=>'string'],
'pspell_clear_session' => ['bool', 'pspell'=>'int'],
'pspell_config_create' => ['int', 'language'=>'string', 'spelling='=>'string', 'jargon='=>'string', 'encoding='=>'string'],
'pspell_config_data_dir' => ['bool', 'conf'=>'int', 'directory'=>'string'],
'pspell_config_dict_dir' => ['bool', 'conf'=>'int', 'directory'=>'string'],
'pspell_config_ignore' => ['bool', 'conf'=>'int', 'ignore'=>'int'],
'pspell_config_mode' => ['bool', 'conf'=>'int', 'mode'=>'int'],
'pspell_config_personal' => ['bool', 'conf'=>'int', 'personal'=>'string'],
'pspell_config_repl' => ['bool', 'conf'=>'int', 'repl'=>'string'],
'pspell_config_runtogether' => ['bool', 'conf'=>'int', 'runtogether'=>'bool'],
'pspell_config_save_repl' => ['bool', 'conf'=>'int', 'save'=>'bool'],
'pspell_new' => ['int', 'language'=>'string', 'spelling='=>'string', 'jargon='=>'string', 'encoding='=>'string', 'mode='=>'int'],
'pspell_new_config' => ['int', 'config'=>'int'],
'pspell_new_personal' => ['int', 'personal'=>'string', 'language'=>'string', 'spelling='=>'string', 'jargon='=>'string', 'encoding='=>'string', 'mode='=>'int'],
'pspell_save_wordlist' => ['bool', 'pspell'=>'int'],
'pspell_store_replacement' => ['bool', 'pspell'=>'int', 'misspell'=>'string', 'correct'=>'string'],
'pspell_suggest' => ['array', 'pspell'=>'int', 'word'=>'string'],
'putenv' => ['bool', 'setting'=>'string'],
'px_close' => ['bool', 'pxdoc'=>'resource'],
'px_create_fp' => ['bool', 'pxdoc'=>'resource', 'file'=>'resource', 'fielddesc'=>'array'],
'px_date2string' => ['string', 'pxdoc'=>'resource', 'value'=>'int', 'format'=>'string'],
'px_delete' => ['bool', 'pxdoc'=>'resource'],
'px_delete_record' => ['bool', 'pxdoc'=>'resource', 'num'=>'int'],
'px_get_field' => ['array', 'pxdoc'=>'resource', 'fieldno'=>'int'],
'px_get_info' => ['array', 'pxdoc'=>'resource'],
'px_get_parameter' => ['string', 'pxdoc'=>'resource', 'name'=>'string'],
'px_get_record' => ['array', 'pxdoc'=>'resource', 'num'=>'int', 'mode='=>'int'],
'px_get_schema' => ['array', 'pxdoc'=>'resource', 'mode='=>'int'],
'px_get_value' => ['float', 'pxdoc'=>'resource', 'name'=>'string'],
'px_insert_record' => ['int', 'pxdoc'=>'resource', 'data'=>'array'],
'px_new' => ['resource'],
'px_numfields' => ['int', 'pxdoc'=>'resource'],
'px_numrecords' => ['int', 'pxdoc'=>'resource'],
'px_open_fp' => ['bool', 'pxdoc'=>'resource', 'file'=>'resource'],
'px_put_record' => ['bool', 'pxdoc'=>'resource', 'record'=>'array', 'recpos='=>'int'],
'px_retrieve_record' => ['array', 'pxdoc'=>'resource', 'num'=>'int', 'mode='=>'int'],
'px_set_blob_file' => ['bool', 'pxdoc'=>'resource', 'filename'=>'string'],
'px_set_parameter' => ['bool', 'pxdoc'=>'resource', 'name'=>'string', 'value'=>'string'],
'px_set_tablename' => ['void', 'pxdoc'=>'resource', 'name'=>'string'],
'px_set_targetencoding' => ['bool', 'pxdoc'=>'resource', 'encoding'=>'string'],
'px_set_value' => ['bool', 'pxdoc'=>'resource', 'name'=>'string', 'value'=>'float'],
'px_timestamp2string' => ['string', 'pxdoc'=>'resource', 'value'=>'float', 'format'=>'string'],
'px_update_record' => ['bool', 'pxdoc'=>'resource', 'data'=>'array', 'num'=>'int'],
'qdom_error' => ['string'],
'qdom_tree' => ['QDomDocument', 'doc'=>'string'],
'querymapObj::convertToString' => ['string'],
'querymapObj::free' => ['void'],
'querymapObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'querymapObj::updateFromString' => ['int', 'snippet'=>'string'],
'QuickHashIntHash::__construct' => ['void', 'size'=>'int', 'options='=>'int'],
'QuickHashIntHash::add' => ['bool', 'key'=>'int', 'value='=>'int'],
'QuickHashIntHash::delete' => ['bool', 'key'=>'int'],
'QuickHashIntHash::exists' => ['bool', 'key'=>'int'],
'QuickHashIntHash::get' => ['int', 'key'=>'int'],
'QuickHashIntHash::getSize' => ['int'],
'QuickHashIntHash::loadFromFile' => ['QuickHashIntHash', 'filename'=>'string', 'options='=>'int'],
'QuickHashIntHash::loadFromString' => ['QuickHashIntHash', 'contents'=>'string', 'options='=>'int'],
'QuickHashIntHash::saveToFile' => ['void', 'filename'=>'string'],
'QuickHashIntHash::saveToString' => ['string'],
'QuickHashIntHash::set' => ['bool', 'key'=>'int', 'value'=>'int'],
'QuickHashIntHash::update' => ['bool', 'key'=>'int', 'value'=>'int'],
'QuickHashIntSet::__construct' => ['void', 'size'=>'int', 'options='=>'int'],
'QuickHashIntSet::add' => ['bool', 'key'=>'int'],
'QuickHashIntSet::delete' => ['bool', 'key'=>'int'],
'QuickHashIntSet::exists' => ['bool', 'key'=>'int'],
'QuickHashIntSet::getSize' => ['int'],
'QuickHashIntSet::loadFromFile' => ['QuickHashIntSet', 'filename'=>'string', 'size='=>'int', 'options='=>'int'],
'QuickHashIntSet::loadFromString' => ['QuickHashIntSet', 'contents'=>'string', 'size='=>'int', 'options='=>'int'],
'QuickHashIntSet::saveToFile' => ['void', 'filename'=>'string'],
'QuickHashIntSet::saveToString' => ['string'],
'QuickHashIntStringHash::__construct' => ['void', 'size'=>'int', 'options='=>'int'],
'QuickHashIntStringHash::add' => ['bool', 'key'=>'int', 'value'=>'string'],
'QuickHashIntStringHash::delete' => ['bool', 'key'=>'int'],
'QuickHashIntStringHash::exists' => ['bool', 'key'=>'int'],
'QuickHashIntStringHash::get' => ['mixed', 'key'=>'int'],
'QuickHashIntStringHash::getSize' => ['int'],
'QuickHashIntStringHash::loadFromFile' => ['QuickHashIntStringHash', 'filename'=>'string', 'size='=>'int', 'options='=>'int'],
'QuickHashIntStringHash::loadFromString' => ['QuickHashIntStringHash', 'contents'=>'string', 'size='=>'int', 'options='=>'int'],
'QuickHashIntStringHash::saveToFile' => ['void', 'filename'=>'string'],
'QuickHashIntStringHash::saveToString' => ['string'],
'QuickHashIntStringHash::set' => ['int', 'key'=>'int', 'value'=>'string'],
'QuickHashIntStringHash::update' => ['bool', 'key'=>'int', 'value'=>'string'],
'QuickHashStringIntHash::__construct' => ['void', 'size'=>'int', 'options='=>'int'],
'QuickHashStringIntHash::add' => ['bool', 'key'=>'string', 'value'=>'int'],
'QuickHashStringIntHash::delete' => ['bool', 'key'=>'string'],
'QuickHashStringIntHash::exists' => ['bool', 'key'=>'string'],
'QuickHashStringIntHash::get' => ['mixed', 'key'=>'string'],
'QuickHashStringIntHash::getSize' => ['int'],
'QuickHashStringIntHash::loadFromFile' => ['QuickHashStringIntHash', 'filename'=>'string', 'size='=>'int', 'options='=>'int'],
'QuickHashStringIntHash::loadFromString' => ['QuickHashStringIntHash', 'contents'=>'string', 'size='=>'int', 'options='=>'int'],
'QuickHashStringIntHash::saveToFile' => ['void', 'filename'=>'string'],
'QuickHashStringIntHash::saveToString' => ['string'],
'QuickHashStringIntHash::set' => ['int', 'key'=>'string', 'value'=>'int'],
'QuickHashStringIntHash::update' => ['bool', 'key'=>'string', 'value'=>'int'],
'quoted_printable_decode' => ['string', 'str'=>'string'],
'quoted_printable_encode' => ['string', 'str'=>'string'],
'quotemeta' => ['string', 'str'=>'string'],
'rad2deg' => ['float', 'number'=>'float'],
'radius_acct_open' => ['resource'],
'radius_add_server' => ['bool', 'radius_handle'=>'resource', 'hostname'=>'string', 'port'=>'int', 'secret'=>'string', 'timeout'=>'int', 'max_tries'=>'int'],
'radius_auth_open' => ['resource'],
'radius_close' => ['bool', 'radius_handle'=>'resource'],
'radius_config' => ['bool', 'radius_handle'=>'resource', 'file'=>'string'],
'radius_create_request' => ['bool', 'radius_handle'=>'resource', 'type'=>'int'],
'radius_cvt_addr' => ['string', 'data'=>'string'],
'radius_cvt_int' => ['int', 'data'=>'string'],
'radius_cvt_string' => ['string', 'data'=>'string'],
'radius_demangle' => ['string', 'radius_handle'=>'resource', 'mangled'=>'string'],
'radius_demangle_mppe_key' => ['string', 'radius_handle'=>'resource', 'mangled'=>'string'],
'radius_get_attr' => ['mixed', 'radius_handle'=>'resource'],
'radius_get_tagged_attr_data' => ['string', 'data'=>'string'],
'radius_get_tagged_attr_tag' => ['int', 'data'=>'string'],
'radius_get_vendor_attr' => ['array', 'data'=>'string'],
'radius_put_addr' => ['bool', 'radius_handle'=>'resource', 'type'=>'int', 'addr'=>'string'],
'radius_put_attr' => ['bool', 'radius_handle'=>'resource', 'type'=>'int', 'value'=>'string'],
'radius_put_int' => ['bool', 'radius_handle'=>'resource', 'type'=>'int', 'value'=>'int'],
'radius_put_string' => ['bool', 'radius_handle'=>'resource', 'type'=>'int', 'value'=>'string'],
'radius_put_vendor_addr' => ['bool', 'radius_handle'=>'resource', 'vendor'=>'int', 'type'=>'int', 'addr'=>'string'],
'radius_put_vendor_attr' => ['bool', 'radius_handle'=>'resource', 'vendor'=>'int', 'type'=>'int', 'value'=>'string'],
'radius_put_vendor_int' => ['bool', 'radius_handle'=>'resource', 'vendor'=>'int', 'type'=>'int', 'value'=>'int'],
'radius_put_vendor_string' => ['bool', 'radius_handle'=>'resource', 'vendor'=>'int', 'type'=>'int', 'value'=>'string'],
'radius_request_authenticator' => ['string', 'radius_handle'=>'resource'],
'radius_salt_encrypt_attr' => ['string', 'radius_handle'=>'resource', 'data'=>'string'],
'radius_send_request' => ['int', 'radius_handle'=>'resource'],
'radius_server_secret' => ['string', 'radius_handle'=>'resource'],
'radius_strerror' => ['string', 'radius_handle'=>'resource'],
'rand' => ['int', 'min'=>'int', 'max'=>'int'],
'rand\'1' => ['int'],
'random_bytes' => ['string', 'length'=>'int'],
'random_int' => ['int', 'min'=>'int', 'max'=>'int'],
'range' => ['array', 'low'=>'mixed', 'high'=>'mixed', 'step='=>'int|float'],
'RangeException::__clone' => ['void'],
'RangeException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?RangeException'],
'RangeException::__toString' => ['string'],
'RangeException::getCode' => ['int'],
'RangeException::getFile' => ['string'],
'RangeException::getLine' => ['int'],
'RangeException::getMessage' => ['string'],
'RangeException::getPrevious' => ['Throwable|RangeException'],
'RangeException::getTrace' => ['array'],
'RangeException::getTraceAsString' => ['string'],
'rar_allow_broken_set' => ['bool', 'rarfile'=>'RarArchive', 'allow_broken'=>'bool'],
'rar_broken_is' => ['bool', 'rarfile'=>'rararchive'],
'rar_close' => ['bool', 'rarfile'=>'rararchive'],
'rar_comment_get' => ['string', 'rarfile'=>'rararchive'],
'rar_entry_get' => ['RarEntry', 'entryname'=>'string', 'rarfile'=>'rararchive'],
'rar_list' => ['RarArchive', 'rarfile'=>'rararchive'],
'rar_open' => ['RarArchive', 'filename'=>'string', 'password='=>'string', 'volume_callback='=>'callable'],
'rar_solid_is' => ['bool', 'rarfile'=>'rararchive'],
'rar_wrapper_cache_stats' => ['string'],
'RarArchive::__toString' => ['string'],
'RarArchive::close' => ['bool', 'rarfile'=>'rararchive'],
'RarArchive::getComment' => ['string', 'rarfile'=>'rararchive'],
'RarArchive::getEntries' => ['RarArchive', 'rarfile'=>'rararchive'],
'RarArchive::getEntry' => ['RarEntry', 'entryname'=>'string', 'rarfile'=>'rararchive'],
'RarArchive::isBroken' => ['bool', 'rarfile'=>'rararchive'],
'RarArchive::isSolid' => ['bool', 'rarfile'=>'rararchive'],
'RarArchive::open' => ['RarArchive', 'filename'=>'string', 'password='=>'string', 'volume_callback='=>'callable'],
'RarArchive::setAllowBroken' => ['bool', 'allow_broken'=>'bool', 'rarfile'=>'rararchive'],
'RarEntry::__toString' => ['string'],
'RarEntry::extract' => ['bool', 'dir'=>'string', 'filepath='=>'string', 'password='=>'string', 'extended_data='=>'bool'],
'RarEntry::getAttr' => ['int'],
'RarEntry::getCrc' => ['string'],
'RarEntry::getFileTime' => ['string'],
'RarEntry::getHostOs' => ['int'],
'RarEntry::getMethod' => ['int'],
'RarEntry::getName' => ['string'],
'RarEntry::getPackedSize' => ['int'],
'RarEntry::getStream' => ['resource', 'password='=>'string'],
'RarEntry::getUnpackedSize' => ['int'],
'RarEntry::getVersion' => ['int'],
'RarEntry::isDirectory' => ['bool'],
'RarEntry::isEncrypted' => ['bool'],
'RarException::getCode' => ['int'],
'RarException::getFile' => ['string'],
'RarException::getLine' => ['int'],
'RarException::getMessage' => ['string'],
'RarException::getPrevious' => ['Exception|Throwable'],
'RarException::getTrace' => ['array'],
'RarException::getTraceAsString' => ['string'],
'RarException::isUsingExceptions' => ['bool'],
'RarException::setUsingExceptions' => ['RarEntry', 'using_exceptions'=>'bool'],
'rawurldecode' => ['string', 'str'=>'string'],
'rawurlencode' => ['string', 'str'=>'string'],
'read_exif_data' => ['array', 'filename'=>'string', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'],
'readdir' => ['string', 'dir_handle='=>'resource'],
'readfile' => ['int', 'filename'=>'string', 'use_include_path='=>'bool', 'context='=>'resource'],
'readgzfile' => ['int', 'filename'=>'string', 'use_include_path='=>'int'],
'readline' => ['string', 'prompt='=>'?string'],
'readline_add_history' => ['bool', 'prompt'=>'string'],
'readline_callback_handler_install' => ['bool', 'prompt'=>'string', 'callback'=>'callable'],
'readline_callback_handler_remove' => ['bool'],
'readline_callback_read_char' => ['void'],
'readline_clear_history' => ['bool'],
'readline_completion_function' => ['bool', 'funcname'=>'callable'],
'readline_info' => ['mixed', 'varname='=>'string', 'newvalue='=>'string'],
'readline_list_history' => ['array'],
'readline_on_new_line' => ['void'],
'readline_read_history' => ['bool', 'filename='=>'string'],
'readline_redisplay' => ['void'],
'readline_write_history' => ['bool', 'filename='=>'string'],
'readlink' => ['string', 'filename'=>'string'],
'realpath' => ['string|false', 'path'=>'string'],
'realpath_cache_get' => ['array'],
'realpath_cache_size' => ['int'],
'recode' => ['string', 'request'=>'string', 'str'=>'string'],
'recode_file' => ['bool', 'request'=>'string', 'input'=>'resource', 'output'=>'resource'],
'recode_string' => ['string', 'request'=>'string', 'str'=>'string'],
'rectObj::__construct' => ['void'],
'rectObj::draw' => ['int', 'map'=>'MapObj', 'layer'=>'layerObj', 'img'=>'imageObj', 'class_index'=>'int', 'text'=>'string'],
'rectObj::fit' => ['float', 'width'=>'int', 'height'=>'int'],
'rectObj::ms_newRectObj' => ['rectObj'],
'rectObj::project' => ['int', 'in'=>'projectionObj', 'out'=>'projectionObj'],
'rectObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'rectObj::setextent' => ['void', 'minx'=>'float', 'miny'=>'float', 'maxx'=>'float', 'maxy'=>'float'],
'RecursiveArrayIterator::getChildren' => ['RecursiveArrayIterator'],
'RecursiveArrayIterator::hasChildren' => ['bool'],
'RecursiveCachingIterator::__construct' => ['void', 'it'=>'Iterator', 'flags'=>''],
'RecursiveCachingIterator::getChildren' => ['RecursiveCachingIterator'],
'RecursiveCachingIterator::hasChildren' => ['bool'],
'RecursiveCallbackFilterIterator::__construct' => ['void', 'it'=>'recursiveiterator', 'func'=>'callable'],
'RecursiveCallbackFilterIterator::getChildren' => ['RecursiveCallbackFilterIterator'],
'RecursiveCallbackFilterIterator::hasChildren' => ['void'],
'RecursiveDirectoryIterator::__construct' => ['void', 'path'=>'string', 'flags='=>'int'],
'RecursiveDirectoryIterator::getChildren' => ['object'],
'RecursiveDirectoryIterator::getSubPath' => ['string'],
'RecursiveDirectoryIterator::getSubPathname' => ['string'],
'RecursiveDirectoryIterator::hasChildren' => ['bool', 'allow_links='=>'bool'],
'RecursiveDirectoryIterator::key' => ['string'],
'RecursiveDirectoryIterator::next' => ['void'],
'RecursiveDirectoryIterator::rewind' => ['void'],
'RecursiveFilterIterator::__construct' => ['void', 'it'=>'recursiveiterator'],
'RecursiveFilterIterator::getChildren' => ['RecursiveFilterIterator'],
'RecursiveFilterIterator::hasChildren' => ['bool'],
'RecursiveIterator::getChildren' => ['RecursiveIterator'],
'RecursiveIterator::hasChildren' => ['bool'],
'RecursiveIteratorIterator::__construct' => ['void', 'it'=>'RecursiveIterator|IteratorAggregate', 'mode='=>'int', 'flags='=>'int'],
'RecursiveIteratorIterator::beginChildren' => ['void'],
'RecursiveIteratorIterator::beginIteration' => ['RecursiveIterator'],
'RecursiveIteratorIterator::callGetChildren' => ['RecursiveIterator'],
'RecursiveIteratorIterator::callHasChildren' => ['bool'],
'RecursiveIteratorIterator::current' => ['mixed'],
'RecursiveIteratorIterator::endChildren' => ['void'],
'RecursiveIteratorIterator::endIteration' => ['RecursiveIterator'],
'RecursiveIteratorIterator::getDepth' => ['int'],
'RecursiveIteratorIterator::getInnerIterator' => ['RecursiveIterator'],
'RecursiveIteratorIterator::getMaxDepth' => ['int|false'],
'RecursiveIteratorIterator::getSubIterator' => ['RecursiveIterator', 'level='=>'int'],
'RecursiveIteratorIterator::key' => ['mixed'],
'RecursiveIteratorIterator::next' => ['void'],
'RecursiveIteratorIterator::nextElement' => ['void'],
'RecursiveIteratorIterator::rewind' => ['void'],
'RecursiveIteratorIterator::setMaxDepth' => ['void', 'max_depth='=>'int'],
'RecursiveIteratorIterator::valid' => ['bool'],
'RecursiveRegexIterator::__construct' => ['void', 'it'=>'recursiveiterator', 'regex='=>'string', 'mode='=>'int', 'flags='=>'int', 'preg_flags='=>'int'],
'RecursiveRegexIterator::getChildren' => ['RecursiveRegexIterator'],
'RecursiveRegexIterator::hasChildren' => ['bool'],
'RecursiveTreeIterator::__construct' => ['void', 'it'=>'recursiveiterator|iteratoraggregate', 'flags'=>'int', 'cit_flags'=>'int', 'mode'=>'int'],
'RecursiveTreeIterator::beginChildren' => ['void'],
'RecursiveTreeIterator::beginIteration' => ['RecursiveIterator'],
'RecursiveTreeIterator::callGetChildren' => ['RecursiveIterator'],
'RecursiveTreeIterator::callHasChildren' => ['bool'],
'RecursiveTreeIterator::current' => ['string'],
'RecursiveTreeIterator::endChildren' => ['void'],
'RecursiveTreeIterator::endIteration' => ['void'],
'RecursiveTreeIterator::getEntry' => ['string'],
'RecursiveTreeIterator::getPostfix' => ['string'],
'RecursiveTreeIterator::getPrefix' => ['string'],
'RecursiveTreeIterator::key' => ['string'],
'RecursiveTreeIterator::next' => ['void'],
'RecursiveTreeIterator::nextElement' => ['void'],
'RecursiveTreeIterator::rewind' => ['void'],
'RecursiveTreeIterator::setPostfix' => ['void', 'prefix'=>'string'],
'RecursiveTreeIterator::setPrefixPart' => ['void', 'part'=>'int', 'prefix'=>'string'],
'RecursiveTreeIterator::valid' => ['bool'],
'Redis::__construct' => ['void'],
'Redis::_prefix' => ['string', 'value'=>'mixed'],
'Redis::_serialize' => ['mixed', 'value'=>'mixed'],
'Redis::_unserialize' => ['mixed', 'value'=>'string'],
'Redis::append' => ['int', 'key'=>'string', 'value'=>'string'],
'Redis::auth' => ['bool', 'password'=>'string'],
'Redis::bgRewriteAOF' => ['bool'],
'Redis::bgSave' => ['bool'],
'Redis::bitCount' => ['int', 'key'=>'string'],
'Redis::bitOp' => ['int', 'operation'=>'string', '...args'=>'string'],
'Redis::bitpos' => ['int', 'key'=>'string', 'bit'=>'int', 'start='=>'int', 'end='=>'int'],
'Redis::blPop' => ['array', 'keys'=>'array', 'timeout'=>'int'],
'Redis::brPop' => ['array', 'keys'=>'array', 'timeout'=>'int'],
'Redis::brpoplpush' => ['string', 'srcKey'=>'string', 'dstKey'=>'string', 'timeout'=>'int'],
'Redis::clearLastError' => ['bool'],
'Redis::client' => ['mixed', 'command'=>'string', 'arg='=>'string'],
'Redis::close' => ['void'],
'Redis::config' => ['string', 'key'=>'string', 'value='=>'string'],
'Redis::connect' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'float', 'retry_interval='=>'?int'],
'Redis::dbSize' => ['int'],
'Redis::decr' => ['int', 'key'=>'string'],
'Redis::decrBy' => ['int', 'key'=>'string', 'value'=>'int'],
'Redis::decrByFloat' => ['float', 'key'=>'string', 'value'=>'float'],
'Redis::del' => ['bool', 'key'=>'string', '...args'=>'string'],
'Redis::del\'1' => ['bool', 'key'=>'string[]'],
'Redis::delete' => ['bool', 'key'=>'string', '...args'=>'string'],
'Redis::delete\'1' => ['bool', 'key'=>'string[]'],
'Redis::discard' => [''],
'Redis::dump' => ['string|false', 'key'=>'string'],
'Redis::echo' => ['string', 'message'=>'string'],
'Redis::eval' => ['mixed', 'script'=>'', 'args='=>'', 'numKeys='=>''],
'Redis::evalSha' => ['mixed', 'scriptSha'=>'string', 'args='=>'array', 'numKeys='=>'int'],
'Redis::evaluate' => ['mixed', 'script'=>'string', 'args='=>'array', 'numKeys='=>'int'],
'Redis::evaluateSha' => ['', 'scriptSha'=>'string', 'args='=>'array', 'numKeys='=>'int'],
'Redis::exec' => ['array'],
'Redis::exists' => ['bool', 'key'=>'string'],
'Redis::expire' => ['bool', 'key'=>'string', 'ttl'=>'int'],
'Redis::expireAt' => ['bool', 'key'=>'string', 'expiry'=>'int'],
'Redis::flushAll' => ['bool'],
'Redis::flushDb' => ['bool'],
'Redis::get' => ['string|false', 'key'=>'string'],
'Redis::getBit' => ['int', 'key'=>'string', 'offset'=>'int'],
'Redis::getKeys' => ['array<int,string>', 'pattern'=>'string'],
'Redis::getLastError' => ['null|string'],
'Redis::getMode' => ['int'],
'Redis::getMultiple' => ['array', 'keys'=>'string[]'],
'Redis::getOption' => ['int', 'name'=>'int'],
'Redis::getRange' => ['int', 'key'=>'string', 'start'=>'int', 'end'=>'int'],
'Redis::getSet' => ['string', 'key'=>'string', 'string'=>'string'],
'Redis::hDel' => ['bool|int', 'key'=>'string', 'hashKey1'=>'string', 'hashKey2='=>'string', 'hashKeyN='=>'string'],
'Redis::hExists' => ['bool', 'key'=>'string', 'hashKey'=>'string'],
'Redis::hGet' => ['string', 'key'=>'string', 'hashKey'=>'string'],
'Redis::hGetAll' => ['array', 'key'=>'string'],
'Redis::hIncrBy' => ['int', 'key'=>'string', 'hashKey'=>'string', 'value'=>'int'],
'Redis::hIncrByFloat' => ['float', 'key'=>'string', 'field'=>'string', 'increment'=>'float'],
'Redis::hKeys' => ['array', 'key'=>'string'],
'Redis::hLen' => ['int', 'key'=>'string'],
'Redis::hMGet' => ['array', 'key'=>'string', 'hashKeys'=>'array'],
'Redis::hMset' => ['bool', 'key'=>'string', 'hashKeys'=>'array'],
'Redis::hScan' => ['array', 'key'=>'string', 'iterator'=>'int', 'pattern='=>'string', 'count='=>'int'],
'Redis::hSet' => ['bool|int', 'key'=>'string', 'hashKey'=>'string', 'value'=>'string'],
'Redis::hSetNx' => ['bool', 'key'=>'string', 'hashKey'=>'string', 'value'=>'string'],
'Redis::hVals' => ['array', 'key'=>'string'],
'Redis::incr' => ['int', 'key'=>'string'],
'Redis::incrBy' => ['int', 'key'=>'string', 'value'=>'int'],
'Redis::incrByFloat' => ['float', 'key'=>'string'],
'Redis::info' => ['array', 'option='=>'string'],
'Redis::keys' => ['array<int,string>', 'pattern'=>'string'],
'Redis::lastSave' => ['int'],
'Redis::lGet' => ['', 'key'=>'string', 'index'=>'int'],
'Redis::lGetRange' => ['', 'key'=>'string', 'start'=>'int', 'end'=>'int'],
'Redis::lIndex' => ['string', 'key'=>'string', 'index'=>'int'],
'Redis::lInsert' => ['int', 'key'=>'string', 'position'=>'int', 'pivot'=>'string', 'value'=>'string'],
'Redis::listTrim' => ['', 'key'=>'string', 'start'=>'int', 'stop'=>'int'],
'Redis::lLen' => ['int', 'key'=>'string'],
'Redis::lPop' => ['string', 'key'=>'string'],
'Redis::lPush' => ['bool|int', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'],
'Redis::lPushx' => ['int', 'key'=>'string', 'value'=>'string'],
'Redis::lRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int'],
'Redis::lRem' => ['int', 'key'=>'string', 'value'=>'string', 'count'=>'int'],
'Redis::lRemove' => ['', 'key'=>'string', 'value'=>'string', 'count'=>'int'],
'Redis::lSet' => ['bool', 'key'=>'string', 'index'=>'int', 'value'=>'string'],
'Redis::lSize' => ['', 'key'=>'string'],
'Redis::lTrim' => ['array', 'key'=>'string', 'start'=>'int', 'stop'=>'int'],
'Redis::mGet' => ['array', 'keys'=>'string[]'],
'Redis::migrate' => ['bool', 'host'=>'string', 'port'=>'int', 'db'=>'int', 'timeout'=>'int', 'copy='=>'bool', 'replace='=>'bool'],
'Redis::move' => ['bool', 'key'=>'string', 'dbindex'=>'int'],
'Redis::mSet' => ['bool', 'pairs'=>'array'],
'Redis::mSetNx' => ['bool', 'pairs'=>'array'],
'Redis::multi' => ['Redis', 'mode='=>'int'],
'Redis::object' => ['string|long|false', 'info'=>'string', 'key'=>'string'],
'Redis::open' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'float', 'retry_interval='=>'?int'],
'Redis::pconnect' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'float', 'persistent_id='=>'string', 'retry_interval='=>'?int'],
'Redis::persist' => ['bool', 'key'=>'string'],
'Redis::pExpire' => ['bool', 'key'=>'string', 'ttl'=>'int'],
'Redis::pexpireAt' => ['bool', 'key'=>'string', 'expiry'=>'int'],
'Redis::pfAdd' => ['bool', 'key'=>'string', 'elements'=>'array'],
'Redis::pfCount' => ['int', 'key'=>'array|string'],
'Redis::pfMerge' => ['bool', 'destkey'=>'string', 'sourcekeys'=>'array'],
'Redis::ping' => ['string'],
'Redis::popen' => ['bool', 'host'=>'string', 'port='=>'int', 'timeout='=>'float', 'persistent_id='=>'string', 'retry_interval='=>'?int'],
'Redis::psetex' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'],
'Redis::psubscribe' => ['', 'patterns'=>'array', 'callback'=>'array|string'],
'Redis::pttl' => ['int', 'key'=>'string'],
'Redis::publish' => ['int', 'channel'=>'string', 'message'=>'string'],
'Redis::pubsub' => ['array|int', 'keyword'=>'string', 'argument'=>'array|string'],
'Redis::randomKey' => ['string'],
'Redis::rawCommand' => ['mixed', 'command'=>'string', 'arguments'=>'mixed'],
'Redis::rename' => ['bool', 'srckey'=>'string', 'dstkey'=>'string'],
'Redis::renameKey' => ['bool', 'srckey'=>'string', 'dstkey'=>'string'],
'Redis::renameNx' => ['bool', 'srckey'=>'string', 'dstkey'=>'string'],
'Redis::resetStat' => ['bool'],
'Redis::restore' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'],
'Redis::rPop' => ['string', 'key'=>'string'],
'Redis::rpoplpush' => ['string', 'srcKey'=>'string', 'dstKey'=>'string'],
'Redis::rPush' => ['bool|int', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'],
'Redis::rPushx' => ['int', 'key'=>'string', 'value'=>'string'],
'Redis::sAdd' => ['int', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'],
'Redis::sAddArray' => ['bool', 'key'=>'string', 'values'=>'array'],
'Redis::save' => ['bool'],
'Redis::scan' => ['array<int,string>|false', '&rw_iterator'=>'?int', 'pattern='=>'?string', 'count='=>'?int'],
'Redis::sCard' => ['int', 'key'=>'string'],
'Redis::sContains' => ['', 'key'=>'string', 'value'=>'string'],
'Redis::script' => ['mixed', 'command'=>'string', 'script'=>'string'],
'Redis::sDiff' => ['array', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'Redis::sDiffStore' => ['int', 'dstKey'=>'string', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'Redis::select' => ['bool', 'dbindex'=>'int'],
'Redis::set' => ['bool', 'key'=>'string', 'value'=>'string', 'options='=>'array'],
'Redis::set\'1' => ['bool', 'key'=>'string', 'value'=>'string', 'timeout='=>'int'],
'Redis::setBit' => ['int', 'key'=>'string', 'offset'=>'int', 'value'=>'int'],
'Redis::setEx' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'],
'Redis::setNx' => ['bool', 'key'=>'string', 'value'=>'string'],
'Redis::setOption' => ['bool', 'name'=>'int', 'value'=>'string'],
'Redis::setRange' => ['int', 'key'=>'string', 'offset'=>'int', 'end'=>'int'],
'Redis::setTimeout' => ['', 'key'=>'string', 'ttl'=>'int'],
'Redis::sGetMembers' => ['', 'key'=>'string'],
'Redis::sInter' => ['array', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'Redis::sInterStore' => ['int', 'dstKey'=>'string', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'Redis::sIsMember' => ['bool', 'key'=>'string', 'value'=>'string'],
'Redis::slave' => ['bool', 'host'=>'string', 'port'=>'int'],
'Redis::slave\'1' => ['bool', 'host'=>'string', 'port'=>'int'],
'Redis::slaveof' => ['bool', 'host='=>'string', 'port='=>'int'],
'Redis::slowLog' => ['mixed', 'operation'=>'string', 'length='=>'int'],
'Redis::sMembers' => ['array', 'key'=>'string'],
'Redis::sMove' => ['bool', 'srcKey'=>'string', 'dstKey'=>'string', 'member'=>'string'],
'Redis::sort' => ['array|int', 'options='=>'?array'],
'Redis::sPop' => ['string', 'key'=>'string'],
'Redis::sRandMember' => ['array|string', 'key'=>'string', 'count='=>'int'],
'Redis::sRem' => ['int', 'key'=>'string', 'member1'=>'string', 'member2='=>'string', 'memberN='=>'string'],
'Redis::sRemove' => ['', 'key'=>'string', 'member1'=>'string', 'member2='=>'string', 'memberN='=>'string'],
'Redis::sScan' => ['array|bool', 'key'=>'string', 'iterator'=>'int', 'pattern='=>'string', 'count='=>'int'],
'Redis::strLen' => ['int', 'key'=>'string'],
'Redis::subscribe' => ['', 'channels'=>'array', 'callback'=>'string'],
'Redis::substr' => ['', 'key'=>'string', 'start'=>'int', 'end'=>'int'],
'Redis::sUnion' => ['array', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'Redis::sUnionStore' => ['int', 'dstKey'=>'string', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'Redis::time' => ['array'],
'Redis::ttl' => ['array|int', 'options='=>'?array'],
'Redis::type' => ['int', 'key'=>'string'],
'Redis::unwatch' => [''],
'Redis::wait' => ['int', 'numSlaves'=>'int', 'timeout'=>'int'],
'Redis::watch' => ['void', 'key'=>'string'],
'Redis::zAdd' => ['int', 'key'=>'string', 'score1'=>'float', 'value1'=>'string', 'score2='=>'float', 'value2='=>'string', 'scoreN='=>'float', 'valueN='=>'string'],
'Redis::zCard' => ['int', 'key'=>'string'],
'Redis::zCount' => ['int', 'key'=>'string', 'start'=>'string', 'end'=>'string'],
'Redis::zDelete' => ['int', 'key'=>'string', 'member1'=>'string', 'member2='=>'string', 'memberN='=>'string'],
'Redis::zDeleteRangeByRank' => ['', 'key'=>'string', 'start'=>'int', 'end'=>'int'],
'Redis::zDeleteRangeByScore' => ['', 'key'=>'string', 'start'=>'float', 'end'=>'float'],
'Redis::zIncrBy' => ['float', 'key'=>'string', 'value'=>'float', 'member'=>'string'],
'Redis::zInter' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'],
'Redis::zRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'withscores='=>'bool'],
'Redis::zRangeByLex' => ['array', 'key'=>'string', 'min'=>'int', 'max'=>'int', 'offset='=>'int', 'limit='=>'int'],
'Redis::zRangeByScore' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'options='=>'array'],
'Redis::zRank' => ['int', 'key'=>'string', 'member'=>'string'],
'Redis::zRem' => ['int', 'key'=>'string', 'member1'=>'string', 'member2='=>'string', 'memberN='=>'string'],
'Redis::zRemRangeByRank' => ['int', 'key'=>'string', 'start'=>'int', 'end'=>'int'],
'Redis::zRemRangeByScore' => ['int', 'key'=>'string', 'start'=>'float|string', 'end'=>'float|string'],
'Redis::zRevRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'withscore='=>'bool'],
'Redis::zRevRangeByLex' => ['array', 'key'=>'string', 'min'=>'int', 'max'=>'int', 'offset='=>'int', 'limit='=>'int'],
'Redis::zRevRangeByScore' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'options='=>'array'],
'Redis::zRevRank' => ['int', 'key'=>'string', 'member'=>'string'],
'Redis::zScan' => ['array|bool', 'key'=>'string', 'iterator'=>'int', 'pattern='=>'string', 'count='=>'int'],
'Redis::zScore' => ['float', 'key'=>'string', 'member'=>'string'],
'Redis::zSize' => ['', 'key'=>'string'],
'Redis::zUnion' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'],
'RedisArray::__construct' => ['void', 'name='=>'string', 'hosts='=>'?array', 'opts='=>'?array'],
'RedisArray::_function' => ['string'],
'RedisArray::_hosts' => ['array'],
'RedisArray::_rehash' => [''],
'RedisArray::_target' => ['string', 'key'=>'string'],
'RedisCluster::__construct' => ['void', 'name'=>'string', 'seeds'=>'array', 'timeout='=>'float', 'readTimeout='=>'float', 'persistent='=>'bool|false'],
'RedisCluster::_masters' => ['array'],
'RedisCluster::_prefix' => ['string', 'value'=>'mixed'],
'RedisCluster::_serialize' => ['mixed', 'value'=>'mixed'],
'RedisCluster::_unserialize' => ['mixed', 'value'=>'string'],
'RedisCluster::append' => ['int', 'key'=>'string', 'value'=>'string'],
'RedisCluster::bgrewriteaof' => ['bool', 'nodeParams'=>'string'],
'RedisCluster::bgsave' => ['bool', 'nodeParams'=>'string'],
'RedisCluster::bitCount' => ['int', 'key'=>'string'],
'RedisCluster::bitOp' => ['int', 'operation'=>'string', 'retKey'=>'string', 'key1'=>'string', 'key2'=>'string', 'key3='=>'string'],
'RedisCluster::bitpos' => ['int', 'key'=>'string', 'bit'=>'int', 'start='=>'int', 'end='=>'int'],
'RedisCluster::blPop' => ['array', 'keys'=>'array', 'timeout'=>'int'],
'RedisCluster::brPop' => ['array', 'keys'=>'array', 'timeout'=>'int'],
'RedisCluster::brpoplpush' => ['string', 'srcKey'=>'string', 'dstKey'=>'string', 'timeout'=>'int'],
'RedisCluster::clearLastError' => ['bool'],
'RedisCluster::client' => ['', 'nodeParams'=>'string', 'subCmd'=>'', 'args'=>''],
'RedisCluster::close' => [''],
'RedisCluster::cluster' => ['mixed', 'nodeParams'=>'string', 'command'=>'string', 'arguments'=>'mixed'],
'RedisCluster::command' => ['mixed'],
'RedisCluster::config' => ['array', 'nodeParams'=>'string', 'operation'=>'string', 'key'=>'string', 'value'=>'string'],
'RedisCluster::dbSize' => ['int', 'nodeParams'=>'string'],
'RedisCluster::decr' => ['int', 'key'=>'string'],
'RedisCluster::decrBy' => ['int', 'key'=>'string', 'value'=>'int'],
'RedisCluster::del' => ['int', 'key1'=>'int', 'key2='=>'string', 'key3='=>'string'],
'RedisCluster::discard' => [''],
'RedisCluster::dump' => ['string', 'key'=>'string'],
'RedisCluster::echo' => ['mixed', 'nodeParams'=>'string', 'msg'=>'string'],
'RedisCluster::eval' => ['mixed', 'script'=>'', 'args='=>'', 'numKeys='=>''],
'RedisCluster::evalSha' => ['mixed', 'scriptSha'=>'string', 'args='=>'array', 'numKeys='=>'int'],
'RedisCluster::exec' => ['array|void'],
'RedisCluster::exists' => ['bool', 'key'=>'string'],
'RedisCluster::expire' => ['bool', 'key'=>'string', 'ttl'=>'int'],
'RedisCluster::expireAt' => ['bool', 'key'=>'string', 'timestamp'=>'int'],
'RedisCluster::flushAll' => ['bool', 'nodeParams'=>'string'],
'RedisCluster::flushDB' => ['bool', 'nodeParams'=>'string'],
'RedisCluster::geoAdd' => ['', 'key'=>'string', 'longitude'=>'float', 'latitude'=>'float', 'member'=>'string'],
'RedisCluster::geoDist' => ['', 'key'=>'string', 'member1'=>'string', 'member2'=>'string', 'unit='=>'string'],
'RedisCluster::geohash' => ['', 'key'=>'', 'member1'=>'', 'member2='=>'mixed', 'memberN='=>'mixed'],
'RedisCluster::geopos' => ['', 'key'=>'', 'member1'=>'', 'member2='=>'mixed', 'memberN='=>'mixed'],
'RedisCluster::geoRadius' => ['', 'key'=>'string', 'longitude'=>'float', 'latitude'=>'float', 'radius'=>'float', 'radiusUnit'=>'string', 'options'=>'array'],
'RedisCluster::geoRadiusByMember' => ['', 'key'=>'string', 'member'=>'string', 'radius'=>'float', 'radiusUnit'=>'string', 'options'=>'array'],
'RedisCluster::get' => ['bool|string', 'key'=>'string'],
'RedisCluster::getBit' => ['int', 'key'=>'string', 'offset'=>'int'],
'RedisCluster::getLastError' => ['string'],
'RedisCluster::getMode' => ['int'],
'RedisCluster::getOption' => ['int', 'name'=>'string'],
'RedisCluster::getRange' => ['string', 'key'=>'string', 'start'=>'int', 'end'=>'int'],
'RedisCluster::getSet' => ['string', 'key'=>'string', 'value'=>'string'],
'RedisCluster::hDel' => ['int', 'key'=>'string', 'hashKey1'=>'string', 'hashKey2='=>'string', 'hashKeyN='=>'string'],
'RedisCluster::hExists' => ['bool', 'key'=>'string', 'hashKey'=>'string'],
'RedisCluster::hGet' => ['string', 'key'=>'string', 'hashKey'=>'string'],
'RedisCluster::hGetAll' => ['array', 'key'=>'string'],
'RedisCluster::hIncrBy' => ['int', 'key'=>'string', 'hashKey'=>'string', 'value'=>'int'],
'RedisCluster::hIncrByFloat' => ['float', 'key'=>'string', 'field'=>'string', 'increment'=>'float'],
'RedisCluster::hKeys' => ['array', 'key'=>'string'],
'RedisCluster::hLen' => ['int', 'key'=>'string'],
'RedisCluster::hMGet' => ['array', 'key'=>'string', 'hashKeys'=>'array'],
'RedisCluster::hMSet' => ['bool', 'key'=>'string', 'hashKeys'=>'array'],
'RedisCluster::hScan' => ['array', 'key'=>'string', '&iterator'=>'int', 'pattern='=>'string', 'count='=>'int'],
'RedisCluster::hSet' => ['int', 'key'=>'string', 'hashKey'=>'string', 'value'=>'string'],
'RedisCluster::hSetNx' => ['bool', 'key'=>'string', 'hashKey'=>'string', 'value'=>'string'],
'RedisCluster::hVals' => ['array', 'key'=>'string'],
'RedisCluster::incr' => ['int', 'key'=>'string'],
'RedisCluster::incrBy' => ['int', 'key'=>'string', 'value'=>'int'],
'RedisCluster::incrByFloat' => ['float', 'key'=>'string', 'increment'=>'float'],
'RedisCluster::info' => ['string', 'option='=>'string'],
'RedisCluster::keys' => ['array', 'pattern'=>'string'],
'RedisCluster::lastSave' => ['int', 'nodeParams'=>'string'],
'RedisCluster::lGet' => ['', 'key'=>'string', 'index'=>'int'],
'RedisCluster::lIndex' => ['string', 'key'=>'string', 'index'=>'int'],
'RedisCluster::lInsert' => ['int', 'key'=>'string', 'position'=>'int', 'pivot'=>'string', 'value'=>'string'],
'RedisCluster::lLen' => ['int', 'key'=>'string'],
'RedisCluster::lPop' => ['string', 'key'=>'string'],
'RedisCluster::lPush' => ['int', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'],
'RedisCluster::lPushx' => ['int', 'key'=>'string', 'value'=>'string'],
'RedisCluster::lRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int'],
'RedisCluster::lRem' => ['int', 'key'=>'string', 'value'=>'string', 'count'=>'int'],
'RedisCluster::lSet' => ['bool', 'key'=>'string', 'index'=>'int', 'value'=>'string'],
'RedisCluster::lTrim' => ['array', 'key'=>'string', 'start'=>'int', 'stop'=>'int'],
'RedisCluster::mget' => ['array', 'array'=>'array'],
'RedisCluster::mset' => ['bool', 'array'=>'array'],
'RedisCluster::msetnx' => ['int', 'array'=>'array'],
'RedisCluster::multi' => ['Redis', 'mode='=>'int'],
'RedisCluster::object' => ['string', 'string='=>'string', 'key='=>'string'],
'RedisCluster::persist' => ['bool', 'key'=>'string'],
'RedisCluster::pExpire' => ['bool', 'key'=>'string', 'ttl'=>'int'],
'RedisCluster::pExpireAt' => ['bool', 'key'=>'string', 'timestamp'=>'int'],
'RedisCluster::pfAdd' => ['bool', 'key'=>'string', 'elements'=>'array'],
'RedisCluster::pfCount' => ['int', 'key'=>'string'],
'RedisCluster::pfMerge' => ['bool', 'destKey'=>'string', 'sourceKeys'=>'array'],
'RedisCluster::ping' => ['string', 'nodeParams'=>'string'],
'RedisCluster::psetex' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'],
'RedisCluster::psubscribe' => ['mixed', 'patterns'=>'array', 'callback'=>'string'],
'RedisCluster::pttl' => ['int', 'key'=>'string'],
'RedisCluster::publish' => ['int', 'channel'=>'string', 'message'=>'string'],
'RedisCluster::pubsub' => ['array', 'nodeParams'=>'string', 'keyword'=>'string', 'argument'=>'string'],
'RedisCluster::punSubscribe' => ['', 'channels'=>'', 'callback'=>''],
'RedisCluster::randomKey' => ['string', 'nodeParams'=>'string'],
'RedisCluster::rawCommand' => ['mixed', 'nodeParams'=>'string', 'command'=>'string', 'arguments'=>'mixed'],
'RedisCluster::rename' => ['bool', 'srcKey'=>'string', 'dstKey'=>'string'],
'RedisCluster::renameNx' => ['bool', 'srcKey'=>'string', 'dstKey'=>'string'],
'RedisCluster::restore' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'],
'RedisCluster::role' => ['array', 'nodeParams'=>'string'],
'RedisCluster::rPop' => ['string', 'key'=>'string'],
'RedisCluster::rpoplpush' => ['string', 'srcKey'=>'string', 'dstKey'=>'string'],
'RedisCluster::rPush' => ['int', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'],
'RedisCluster::rPushx' => ['int', 'key'=>'string', 'value'=>'string'],
'RedisCluster::sAdd' => ['int', 'key'=>'string', 'value1'=>'string', 'value2='=>'string', 'valueN='=>'string'],
'RedisCluster::sAddArray' => ['int', 'key'=>'string', 'valueArray'=>'array'],
'RedisCluster::save' => ['bool', 'nodeParams'=>'string'],
'RedisCluster::scan' => ['array', '&iterator'=>'int', 'pattern='=>'string', 'count='=>'int'],
'RedisCluster::sCard' => ['int', 'key'=>'string'],
'RedisCluster::script' => ['mixed', 'nodeParams'=>'string', 'command'=>'string', 'script'=>'string'],
'RedisCluster::sDiff' => ['array', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'RedisCluster::sDiffStore' => ['int', 'dstKey'=>'string', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'RedisCluster::set' => ['bool', 'key'=>'string', 'value'=>'string', 'timeout='=>'array|int'],
'RedisCluster::setBit' => ['int', 'key'=>'string', 'offset'=>'int', 'value'=>'bool|int'],
'RedisCluster::setex' => ['bool', 'key'=>'string', 'ttl'=>'int', 'value'=>'string'],
'RedisCluster::setnx' => ['bool', 'key'=>'string', 'value'=>'string'],
'RedisCluster::setOption' => ['bool', 'name'=>'string', 'value'=>'string'],
'RedisCluster::setRange' => ['string', 'key'=>'string', 'offset'=>'int', 'value'=>'string'],
'RedisCluster::sInter' => ['array', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'RedisCluster::sInterStore' => ['int', 'dstKey'=>'string', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'RedisCluster::sIsMember' => ['bool', 'key'=>'string', 'value'=>'string'],
'RedisCluster::slowLog' => ['', 'nodeParams'=>'string', 'command'=>'string', 'argument'=>'mixed'],
'RedisCluster::sMembers' => ['array', 'key'=>'string'],
'RedisCluster::sMove' => ['bool', 'srcKey'=>'string', 'dstKey'=>'string', 'member'=>'string'],
'RedisCluster::sort' => ['array', 'key'=>'string', 'option='=>'array'],
'RedisCluster::sPop' => ['string', 'key'=>'string'],
'RedisCluster::sRandMember' => ['array|string', 'key'=>'string', 'count='=>'int'],
'RedisCluster::sRem' => ['int', 'key'=>'string', 'member1'=>'string', 'member2='=>'string', 'memberN='=>'string'],
'RedisCluster::sScan' => ['array', 'key'=>'string', '&iterator'=>'int', 'pattern='=>'null', 'count='=>'int'],
'RedisCluster::strlen' => ['int', 'key'=>'string'],
'RedisCluster::subscribe' => ['mixed', 'channels'=>'array', 'callback'=>'string'],
'RedisCluster::sUnion' => ['array', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'RedisCluster::sUnionStore' => ['int', 'dstKey'=>'string', 'key1'=>'string', 'key2'=>'string', 'keyN='=>'string'],
'RedisCluster::time' => ['array', 'nodeParams'=>'string'],
'RedisCluster::ttl' => ['int', 'key'=>'string'],
'RedisCluster::type' => ['int', 'key'=>'string'],
'RedisCluster::unSubscribe' => ['', 'channels'=>'', 'callback'=>''],
'RedisCluster::unwatch' => [''],
'RedisCluster::watch' => ['void', 'key'=>'string'],
'RedisCluster::zAdd' => ['int', 'key'=>'string', 'score1'=>'float', 'value1'=>'string', 'score2='=>'float', 'value2='=>'string', 'scoreN='=>'float', 'valueN='=>'string'],
'RedisCluster::zCard' => ['int', 'key'=>'string'],
'RedisCluster::zCount' => ['int', 'key'=>'string', 'start'=>'string', 'end'=>'string'],
'RedisCluster::zIncrBy' => ['float', 'key'=>'string', 'value'=>'float', 'member'=>'string'],
'RedisCluster::zInterStore' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'],
'RedisCluster::zLexCount' => ['int', 'key'=>'string', 'min'=>'int', 'max'=>'int'],
'RedisCluster::zRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'withscores='=>'bool'],
'RedisCluster::zRangeByLex' => ['array', 'key'=>'string', 'min'=>'int', 'max'=>'int', 'offset='=>'int', 'limit='=>'int'],
'RedisCluster::zRangeByScore' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'options='=>'array'],
'RedisCluster::zRank' => ['int', 'key'=>'string', 'member'=>'string'],
'RedisCluster::zRem' => ['int', 'key'=>'string', 'member1'=>'string', 'member2='=>'string', 'memberN='=>'string'],
'RedisCluster::zRemRangeByLex' => ['array', 'key'=>'string', 'min'=>'int', 'max'=>'int'],
'RedisCluster::zRemRangeByRank' => ['int', 'key'=>'string', 'start'=>'int', 'end'=>'int'],
'RedisCluster::zRemRangeByScore' => ['int', 'key'=>'string', 'start'=>'float|string', 'end'=>'float|string'],
'RedisCluster::zRevRange' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'withscore='=>'bool'],
'RedisCluster::zRevRangeByLex' => ['array', 'key'=>'string', 'min'=>'int', 'max'=>'int', 'offset='=>'int', 'limit='=>'int'],
'RedisCluster::zRevRangeByScore' => ['array', 'key'=>'string', 'start'=>'int', 'end'=>'int', 'options='=>'array'],
'RedisCluster::zRevRank' => ['int', 'key'=>'string', 'member'=>'string'],
'RedisCluster::zScan' => ['array', 'key'=>'string', '&iterator'=>'int', 'pattern='=>'string', 'count='=>'int'],
'RedisCluster::zScore' => ['float', 'key'=>'string', 'member'=>'string'],
'RedisCluster::zUnionStore' => ['int', 'Output'=>'string', 'ZSetKeys'=>'array', 'Weights='=>'?array', 'aggregateFunction='=>'string'],
'Reflection::export' => ['string|null', 'r'=>'reflector', 'return='=>'bool'],
'Reflection::getModifierNames' => ['array', 'modifiers'=>'int'],
'ReflectionClass::__clone' => ['void'],
'ReflectionClass::__construct' => ['void', 'argument'=>''],
'ReflectionClass::__toString' => ['string'],
'ReflectionClass::export' => ['string|null', 'argument'=>'string|object', 'return='=>'bool'],
'ReflectionClass::getConstant' => ['mixed', 'name'=>'string'],
'ReflectionClass::getConstants' => ['array<string,mixed>'],
'ReflectionClass::getConstructor' => ['ReflectionMethod'],
'ReflectionClass::getDefaultProperties' => ['array'],
'ReflectionClass::getDocComment' => ['string|false'],
'ReflectionClass::getEndLine' => ['int'],
'ReflectionClass::getExtension' => ['ReflectionExtension|null'],
'ReflectionClass::getExtensionName' => ['string|false'],
'ReflectionClass::getFileName' => ['string|false'],
'ReflectionClass::getInterfaceNames' => ['array'],
'ReflectionClass::getInterfaces' => ['array'],
'ReflectionClass::getMethod' => ['ReflectionMethod', 'name'=>'string'],
'ReflectionClass::getMethods' => ['array', 'filter='=>'int'],
'ReflectionClass::getModifiers' => ['int'],
'ReflectionClass::getName' => ['string'],
'ReflectionClass::getNamespaceName' => ['string'],
'ReflectionClass::getParentClass' => ['ReflectionClass|false'],
'ReflectionClass::getProperties' => ['array', 'filter='=>'int'],
'ReflectionClass::getProperty' => ['ReflectionProperty', 'name'=>'string'],
'ReflectionClass::getReflectionConstant' => ['ReflectionClassConstant', 'name'=>'string'],
'ReflectionClass::getReflectionConstants' => ['array<int,ReflectionClassConstant>'],
'ReflectionClass::getShortName' => ['string'],
'ReflectionClass::getStartLine' => ['int'],
'ReflectionClass::getStaticProperties' => ['array'],
'ReflectionClass::getStaticPropertyValue' => ['mixed', 'name'=>'string', 'default='=>'mixed'],
'ReflectionClass::getTraitAliases' => ['array<string,string>'],
'ReflectionClass::getTraitNames' => ['array<int,string>'],
'ReflectionClass::getTraits' => ['array<string,ReflectionClass>'],
'ReflectionClass::hasConstant' => ['bool', 'name'=>'string'],
'ReflectionClass::hasMethod' => ['bool', 'name'=>'string'],
'ReflectionClass::hasProperty' => ['bool', 'name'=>'string'],
'ReflectionClass::implementsInterface' => ['bool', 'interface_name'=>'string|reflectionclass'],
'ReflectionClass::inNamespace' => ['bool'],
'ReflectionClass::isAbstract' => ['bool'],
'ReflectionClass::isAnonymous' => ['bool'],
'ReflectionClass::isCloneable' => ['bool'],
'ReflectionClass::isFinal' => ['bool'],
'ReflectionClass::isInstance' => ['bool', 'object'=>'object'],
'ReflectionClass::isInstantiable' => ['bool'],
'ReflectionClass::isInterface' => ['bool'],
'ReflectionClass::isInternal' => ['bool'],
'ReflectionClass::isIterable' => ['bool'],
'ReflectionClass::isIterateable' => ['bool'],
'ReflectionClass::isSubclassOf' => ['bool', 'class'=>'string|reflectionclass'],
'ReflectionClass::isTrait' => ['bool'],
'ReflectionClass::isUserDefined' => ['bool'],
'ReflectionClass::newInstance' => ['object', 'args='=>'mixed', '...args='=>'mixed'],
'ReflectionClass::newInstanceArgs' => ['object', 'args='=>'array'],
'ReflectionClass::newInstanceWithoutConstructor' => ['object'],
'ReflectionClass::setStaticPropertyValue' => ['void', 'name'=>'string', 'value'=>'string'],
'ReflectionClassConstant::__construct' => ['void', 'class'=>'mixed', 'name'=>'string'],
'ReflectionClassConstant::__toString' => ['string'],
'ReflectionClassConstant::export' => ['string', 'class'=>'mixed', 'name'=>'string', 'return='=>'bool'],
'ReflectionClassConstant::getDeclaringClass' => ['ReflectionClass'],
'ReflectionClassConstant::getDocComment' => ['string'],
'ReflectionClassConstant::getModifiers' => ['int'],
'ReflectionClassConstant::getName' => ['string'],
'ReflectionClassConstant::getValue' => ['mixed'],
'ReflectionClassConstant::isPrivate' => ['bool'],
'ReflectionClassConstant::isProtected' => ['bool'],
'ReflectionClassConstant::isPublic' => ['bool'],
'ReflectionExtension::__clone' => ['void'],
'ReflectionExtension::__construct' => ['void', 'name'=>'string'],
'ReflectionExtension::__toString' => ['string'],
'ReflectionExtension::export' => ['string|null', 'name'=>'string', 'return='=>'bool'],
'ReflectionExtension::getClasses' => ['array<string,ReflectionClass>'],
'ReflectionExtension::getClassNames' => ['array<int,string>'],
'ReflectionExtension::getConstants' => ['array<string,mixed>'],
'ReflectionExtension::getDependencies' => ['array<string,string>'],
'ReflectionExtension::getFunctions' => ['array<string,ReflectionFunction>'],
'ReflectionExtension::getINIEntries' => ['array<string,mixed>'],
'ReflectionExtension::getName' => ['string'],
'ReflectionExtension::getVersion' => ['string'],
'ReflectionExtension::info' => ['void'],
'ReflectionExtension::isPersistent' => ['void'],
'ReflectionExtension::isTemporary' => ['bool'],
'ReflectionFunction::__construct' => ['void', 'name'=>'string|Closure'],
'ReflectionFunction::__toString' => ['string'],
'ReflectionFunction::export' => ['string|null', 'name'=>'string', 'return='=>'bool'],
'ReflectionFunction::getClosure' => ['?Closure'],
'ReflectionFunction::getClosureScopeClass' => ['ReflectionClass'],
'ReflectionFunction::getClosureThis' => ['bool'],
'ReflectionFunction::getDocComment' => ['string|false'],
'ReflectionFunction::getEndLine' => ['int'],
'ReflectionFunction::getExtension' => ['ReflectionExtension|null'],
'ReflectionFunction::getExtensionName' => ['string|false'],
'ReflectionFunction::getFileName' => ['string|false'],
'ReflectionFunction::getName' => ['string'],
'ReflectionFunction::getNamespaceName' => ['string'],
'ReflectionFunction::getNumberOfParameters' => ['int'],
'ReflectionFunction::getNumberOfRequiredParameters' => ['int'],
'ReflectionFunction::getParameters' => ['array<int,ReflectionParameter>'],
'ReflectionFunction::getReturnType' => ['?ReflectionType'],
'ReflectionFunction::getShortName' => ['string'],
'ReflectionFunction::getStartLine' => ['int'],
'ReflectionFunction::getStaticVariables' => ['array'],
'ReflectionFunction::inNamespace' => ['bool'],
'ReflectionFunction::invoke' => ['mixed', '...args='=>'mixed'],
'ReflectionFunction::invokeArgs' => ['mixed', 'args'=>'array'],
'ReflectionFunction::isClosure' => ['bool'],
'ReflectionFunction::isDeprecated' => ['bool'],
'ReflectionFunction::isDisabled' => ['bool'],
'ReflectionFunction::isGenerator' => ['bool'],
'ReflectionFunction::isInternal' => ['bool'],
'ReflectionFunction::isUserDefined' => ['bool'],
'ReflectionFunction::isVariadic' => ['bool'],
'ReflectionFunction::returnsReference' => ['bool'],
'ReflectionFunctionAbstract::__clone' => ['void'],
'ReflectionFunctionAbstract::__toString' => ['string'],
'ReflectionFunctionAbstract::getClosureScopeClass' => ['ReflectionClass'],
'ReflectionFunctionAbstract::getClosureThis' => ['object'],
'ReflectionFunctionAbstract::getDocComment' => ['string|false'],
'ReflectionFunctionAbstract::getEndLine' => ['int'],
'ReflectionFunctionAbstract::getExtension' => ['ReflectionExtension'],
'ReflectionFunctionAbstract::getExtensionName' => ['string'],
'ReflectionFunctionAbstract::getFileName' => ['string|false'],
'ReflectionFunctionAbstract::getName' => ['string'],
'ReflectionFunctionAbstract::getNamespaceName' => ['string'],
'ReflectionFunctionAbstract::getNumberOfParameters' => ['int'],
'ReflectionFunctionAbstract::getNumberOfRequiredParameters' => ['int'],
'ReflectionFunctionAbstract::getParameters' => ['array<int,ReflectionParameter>'],
'ReflectionFunctionAbstract::getReturnType' => ['?ReflectionType'],
'ReflectionFunctionAbstract::getShortName' => ['string'],
'ReflectionFunctionAbstract::getStartLine' => ['int'],
'ReflectionFunctionAbstract::getStaticVariables' => ['array'],
'ReflectionFunctionAbstract::hasReturnType' => ['bool'],
'ReflectionFunctionAbstract::inNamespace' => ['bool'],
'ReflectionFunctionAbstract::isClosure' => ['bool'],
'ReflectionFunctionAbstract::isDeprecated' => ['bool'],
'ReflectionFunctionAbstract::isGenerator' => ['bool'],
'ReflectionFunctionAbstract::isInternal' => ['bool'],
'ReflectionFunctionAbstract::isUserDefined' => ['bool'],
'ReflectionFunctionAbstract::isVariadic' => ['bool'],
'ReflectionFunctionAbstract::returnsReference' => ['bool'],
'ReflectionGenerator::__construct' => ['void', 'generator'=>'object'],
'ReflectionGenerator::getExecutingFile' => ['string'],
'ReflectionGenerator::getExecutingGenerator' => ['Generator'],
'ReflectionGenerator::getExecutingLine' => ['int'],
'ReflectionGenerator::getFunction' => ['ReflectionFunctionAbstract'],
'ReflectionGenerator::getThis' => ['object'],
'ReflectionGenerator::getTrace' => ['array', 'options'=>'int'],
'ReflectionMethod::__construct' => ['void', 'class'=>'string|object', 'name'=>'string'],
'ReflectionMethod::__construct\'1' => ['void', 'class_method'=>'string'],
'ReflectionMethod::__toString' => ['string'],
'ReflectionMethod::export' => ['string|null', 'class'=>'string', 'name'=>'string', 'return='=>'bool'],
'ReflectionMethod::getClosure' => ['?Closure', 'object'=>'?object'],
'ReflectionMethod::getDeclaringClass' => ['ReflectionClass'],
'ReflectionMethod::getModifiers' => ['int'],
'ReflectionMethod::getPrototype' => ['ReflectionMethod'],
'ReflectionMethod::invoke' => ['mixed', 'object'=>'?object', '...args='=>'mixed'],
'ReflectionMethod::invokeArgs' => ['mixed', 'object'=>'?object', 'args'=>'array'],
'ReflectionMethod::isAbstract' => ['bool'],
'ReflectionMethod::isConstructor' => ['bool'],
'ReflectionMethod::isDestructor' => ['bool'],
'ReflectionMethod::isFinal' => ['bool'],
'ReflectionMethod::isPrivate' => ['bool'],
'ReflectionMethod::isProtected' => ['bool'],
'ReflectionMethod::isPublic' => ['bool'],
'ReflectionMethod::isStatic' => ['bool'],
'ReflectionMethod::setAccessible' => ['void', 'visible'=>'bool'],
'ReflectionNamedType::getName' => ['string'],
'ReflectionObject::__construct' => ['void', 'argument'=>'object'],
'ReflectionObject::export' => ['string|null', 'argument'=>'object', 'return='=>'bool'],
'ReflectionParameter::__clone' => ['void'],
'ReflectionParameter::__construct' => ['void', 'function'=>'', 'parameter'=>''],
'ReflectionParameter::__toString' => ['string'],
'ReflectionParameter::allowsNull' => ['bool'],
'ReflectionParameter::canBePassedByValue' => ['bool'],
'ReflectionParameter::export' => ['string|null', 'function'=>'string', 'parameter'=>'string', 'return='=>'bool'],
'ReflectionParameter::getClass' => ['ReflectionClass|null'],
'ReflectionParameter::getDeclaringClass' => ['ReflectionClass|null'],
'ReflectionParameter::getDeclaringFunction' => ['ReflectionFunctionAbstract'],
'ReflectionParameter::getDefaultValue' => ['bool'],
'ReflectionParameter::getDefaultValueConstantName' => ['string'],
'ReflectionParameter::getName' => ['string'],
'ReflectionParameter::getPosition' => ['int'],
'ReflectionParameter::getType' => ['ReflectionType'],
'ReflectionParameter::hasType' => ['bool'],
'ReflectionParameter::isArray' => ['bool'],
'ReflectionParameter::isCallable' => ['bool'],
'ReflectionParameter::isDefaultValueAvailable' => ['bool'],
'ReflectionParameter::isDefaultValueConstant' => ['bool'],
'ReflectionParameter::isOptional' => ['bool'],
'ReflectionParameter::isPassedByReference' => ['bool'],
'ReflectionParameter::isVariadic' => ['bool'],
'ReflectionProperty::__clone' => ['void'],
'ReflectionProperty::__construct' => ['void', 'class'=>'', 'name'=>'string'],
'ReflectionProperty::__toString' => ['string'],
'ReflectionProperty::export' => ['string|null', 'class'=>'mixed', 'name'=>'string', 'return='=>'bool'],
'ReflectionProperty::getDeclaringClass' => ['ReflectionClass'],
'ReflectionProperty::getDocComment' => ['string|false'],
'ReflectionProperty::getModifiers' => ['int'],
'ReflectionProperty::getName' => ['string'],
'ReflectionProperty::getValue' => ['mixed', 'object='=>'object'],
'ReflectionProperty::isDefault' => ['bool'],
'ReflectionProperty::isPrivate' => ['bool'],
'ReflectionProperty::isProtected' => ['bool'],
'ReflectionProperty::isPublic' => ['bool'],
'ReflectionProperty::isStatic' => ['bool'],
'ReflectionProperty::setAccessible' => ['void', 'visible'=>'bool'],
'ReflectionProperty::setValue' => ['void', 'object'=>'object', 'value'=>''],
'ReflectionProperty::setValue\'1' => ['void', 'value'=>''],
'ReflectionType::__toString' => ['string'],
'ReflectionType::allowsNull' => ['bool'],
'ReflectionType::getName' => ['string'],
'ReflectionType::isBuiltin' => ['bool'],
'ReflectionZendExtension::__clone' => ['void'],
'ReflectionZendExtension::__construct' => ['void', 'name'=>'string'],
'ReflectionZendExtension::__toString' => ['string'],
'ReflectionZendExtension::export' => ['string|null', 'name'=>'string', 'return='=>'bool'],
'ReflectionZendExtension::getAuthor' => ['string'],
'ReflectionZendExtension::getCopyright' => ['string'],
'ReflectionZendExtension::getName' => ['string'],
'ReflectionZendExtension::getURL' => ['string'],
'ReflectionZendExtension::getVersion' => ['string'],
'Reflector::__toString' => ['string'],
'Reflector::export' => ['?string'],
'RegexIterator::__construct' => ['void', 'it'=>'iterator', 'regex'=>'string', 'mode='=>'int', 'flags='=>'int', 'preg_flags='=>'int'],
'RegexIterator::accept' => ['bool'],
'RegexIterator::getFlags' => ['int'],
'RegexIterator::getMode' => ['int'],
'RegexIterator::getPregFlags' => ['int'],
'RegexIterator::getRegex' => ['string'],
'RegexIterator::setFlags' => ['bool', 'new_flags'=>'int'],
'RegexIterator::setMode' => ['bool', 'new_mode'=>'int'],
'RegexIterator::setPregFlags' => ['bool', 'new_flags'=>'int'],
'register_event_handler' => ['bool', 'event_handler_func'=>'event_handler_func', 'handler_register_name'=>'handler_register_name', 'event_type_mask'=>'event_type_mask'],
'register_shutdown_function' => ['void', 'function'=>'callable():void', '...parameter='=>'mixed'],
'register_tick_function' => ['bool', 'function'=>'callable():void', '...args='=>'mixed'],
'rename' => ['bool', 'old_name'=>'string', 'new_name'=>'string', 'context='=>'resource'],
'rename_function' => ['bool', 'original_name'=>'string', 'new_name'=>'string'],
'reset' => ['mixed', '&rw_array_arg'=>'array'],
'ResourceBundle::__construct' => ['void', 'locale'=>'string', 'bundlename'=>'string', 'fallback='=>'bool'],
'ResourceBundle::count' => ['int'],
'ResourceBundle::create' => ['?ResourceBundle', 'locale'=>'string', 'bundlename'=>'string', 'fallback='=>'bool'],
'ResourceBundle::get' => ['', 'index'=>'string|int'],
'ResourceBundle::getErrorCode' => ['int'],
'ResourceBundle::getErrorMessage' => ['string'],
'ResourceBundle::getLocales' => ['array', 'bundlename'=>'string'],
'resourcebundle_count' => ['int', 'r'=>'resourcebundle'],
'resourcebundle_create' => ['?ResourceBundle', 'locale'=>'string', 'bundlename'=>'string', 'fallback='=>'bool'],
'resourcebundle_get' => ['', 'r'=>'resourcebundle', 'index'=>'string|int'],
'resourcebundle_get_error_code' => ['int', 'r'=>'resourcebundle'],
'resourcebundle_get_error_message' => ['string', 'r'=>'resourcebundle'],
'resourcebundle_locales' => ['array', 'bundlename'=>'string'],
'restore_error_handler' => ['bool'],
'restore_exception_handler' => ['bool'],
'restore_include_path' => ['void'],
'rewind' => ['bool', 'fp'=>'resource'],
'rewinddir' => ['void', 'dir_handle='=>'resource'],
'rmdir' => ['bool', 'dirname'=>'string', 'context='=>'resource'],
'round' => ['float', 'number'=>'float', 'precision='=>'int', 'mode='=>'int'],
'rpm_close' => ['bool', 'rpmr'=>'resource'],
'rpm_get_tag' => ['mixed', 'rpmr'=>'resource', 'tagnum'=>'int'],
'rpm_is_valid' => ['bool', 'filename'=>'string'],
'rpm_open' => ['resource', 'filename'=>'string'],
'rpm_version' => ['string'],
'rrd_create' => ['bool', 'filename'=>'string', 'options'=>'array'],
'rrd_error' => ['string'],
'rrd_fetch' => ['array', 'filename'=>'string', 'options'=>'array'],
'rrd_first' => ['int', 'file'=>'string', 'raaindex='=>'int'],
'rrd_graph' => ['array', 'filename'=>'string', 'options'=>'array'],
'rrd_info' => ['array', 'filename'=>'string'],
'rrd_last' => ['int', 'filename'=>'string'],
'rrd_lastupdate' => ['array', 'filename'=>'string'],
'rrd_restore' => ['bool', 'xml_file'=>'string', 'rrd_file'=>'string', 'options='=>'array'],
'rrd_tune' => ['bool', 'filename'=>'string', 'options'=>'array'],
'rrd_update' => ['bool', 'filename'=>'string', 'options'=>'array'],
'rrd_version' => ['string'],
'rrd_xport' => ['array', 'options'=>'array'],
'rrdc_disconnect' => ['void'],
'RRDCreator::__construct' => ['void', 'path'=>'string', 'starttime='=>'string', 'step='=>'int'],
'RRDCreator::addArchive' => ['void', 'description'=>'string'],
'RRDCreator::addDataSource' => ['void', 'description'=>'string'],
'RRDCreator::save' => ['bool'],
'RRDGraph::__construct' => ['void', 'path'=>'string'],
'RRDGraph::save' => ['array'],
'RRDGraph::saveVerbose' => ['array'],
'RRDGraph::setOptions' => ['void', 'options'=>'array'],
'RRDUpdater::__construct' => ['void', 'path'=>'string'],
'RRDUpdater::update' => ['bool', 'values'=>'array', 'time='=>'string'],
'rsort' => ['bool', '&rw_array_arg'=>'array', 'sort_flags='=>'int'],
'rtrim' => ['string', 'str'=>'string', 'character_mask='=>'string'],
'runkit_class_adopt' => ['bool', 'classname'=>'string', 'parentname'=>'string'],
'runkit_class_emancipate' => ['bool', 'classname'=>'string'],
'runkit_constant_add' => ['bool', 'constname'=>'string', 'value'=>'mixed'],
'runkit_constant_redefine' => ['bool', 'constname'=>'string', 'newvalue'=>'mixed'],
'runkit_constant_remove' => ['bool', 'constname'=>'string'],
'runkit_function_add' => ['bool', 'funcname'=>'string', 'arglist'=>'string', 'code'=>'string', 'doccomment='=>'?string'],
'runkit_function_add\'1' => ['bool', 'funcname'=>'string', 'closure'=>'Closure', 'doccomment='=>'?string'],
'runkit_function_copy' => ['bool', 'funcname'=>'string', 'targetname'=>'string'],
'runkit_function_redefine' => ['bool', 'funcname'=>'string', 'arglist'=>'string', 'code'=>'string', 'doccomment='=>'?string'],
'runkit_function_redefine\'1' => ['bool', 'funcname'=>'string', 'closure'=>'Closure', 'doccomment='=>'?string'],
'runkit_function_remove' => ['bool', 'funcname'=>'string'],
'runkit_function_rename' => ['bool', 'funcname'=>'string', 'newname'=>'string'],
'runkit_import' => ['bool', 'filename'=>'string', 'flags='=>'int'],
'runkit_lint' => ['bool', 'code'=>'string'],
'runkit_lint_file' => ['bool', 'filename'=>'string'],
'runkit_method_add' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'args'=>'string', 'code'=>'string', 'flags='=>'int', 'doccomment='=>'?string'],
'runkit_method_add\'1' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'closure'=>'Closure', 'flags='=>'int', 'doccomment='=>'?string'],
'runkit_method_copy' => ['bool', 'dclass'=>'string', 'dmethod'=>'string', 'sclass'=>'string', 'smethod='=>'string'],
'runkit_method_redefine' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'args'=>'string', 'code'=>'string', 'flags='=>'int', 'doccomment='=>'?string'],
'runkit_method_redefine\'1' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'closure'=>'Closure', 'flags='=>'int', 'doccomment='=>'?string'],
'runkit_method_remove' => ['bool', 'classname'=>'string', 'methodname'=>'string'],
'runkit_method_rename' => ['bool', 'classname'=>'string', 'methodname'=>'string', 'newname'=>'string'],
'runkit_return_value_used' => ['bool'],
'Runkit_Sandbox::__construct' => ['void', 'options='=>'array'],
'runkit_sandbox_output_handler' => ['mixed', 'sandbox'=>'object', 'callback='=>'mixed'],
'Runkit_Sandbox_Parent' => [''],
'Runkit_Sandbox_Parent::__construct' => ['void'],
'runkit_superglobals' => ['array'],
'RuntimeException::__clone' => ['void'],
'RuntimeException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?RuntimeException'],
'RuntimeException::__toString' => ['string'],
'RuntimeException::getCode' => ['int'],
'RuntimeException::getFile' => ['string'],
'RuntimeException::getLine' => ['int'],
'RuntimeException::getMessage' => ['string'],
'RuntimeException::getPrevious' => ['Throwable|RuntimeException'],
'RuntimeException::getTrace' => ['array'],
'RuntimeException::getTraceAsString' => ['string'],
'SAMConnection::commit' => ['bool'],
'SAMConnection::connect' => ['bool', 'protocol'=>'string', 'properties='=>'array'],
'SAMConnection::disconnect' => ['bool'],
'SAMConnection::errno' => ['int'],
'SAMConnection::error' => ['string'],
'SAMConnection::isConnected' => ['bool'],
'SAMConnection::peek' => ['SAMMessage', 'target'=>'string', 'properties='=>'array'],
'SAMConnection::peekAll' => ['array', 'target'=>'string', 'properties='=>'array'],
'SAMConnection::receive' => ['SAMMessage', 'target'=>'string', 'properties='=>'array'],
'SAMConnection::remove' => ['SAMMessage', 'target'=>'string', 'properties='=>'array'],
'SAMConnection::rollback' => ['bool'],
'SAMConnection::send' => ['string', 'target'=>'string', 'msg'=>'sammessage', 'properties='=>'array'],
'SAMConnection::setDebug' => ['', 'switch'=>'bool'],
'SAMConnection::subscribe' => ['string', 'targettopic'=>'string'],
'SAMConnection::unsubscribe' => ['bool', 'subscriptionid'=>'string', 'targettopic='=>'string'],
'SAMMessage::body' => ['string'],
'SAMMessage::header' => ['object'],
'sapi_windows_cp_conv' => ['string', 'in_codepage'=>'int|string', 'out_codepage'=>'int|string', 'subject'=>'string'],
'sapi_windows_cp_get' => ['int'],
'sapi_windows_cp_is_utf8' => ['bool'],
'sapi_windows_cp_set' => ['bool', 'code_page'=>'int'],
'sapi_windows_vt100_support' => ['bool', 'stream'=>'resource', 'enable='=>'bool'],
'SCA::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'],
'SCA::getService' => ['', 'target'=>'string', 'binding='=>'string', 'config='=>'array'],
'SCA_LocalProxy::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'],
'SCA_SoapProxy::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'],
'scalebarObj::convertToString' => ['string'],
'scalebarObj::free' => ['void'],
'scalebarObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'scalebarObj::setImageColor' => ['int', 'red'=>'int', 'green'=>'int', 'blue'=>'int'],
'scalebarObj::updateFromString' => ['int', 'snippet'=>'string'],
'scandir' => ['array<int,string>|false', 'dir'=>'string', 'sorting_order='=>'int', 'context='=>'resource'],
'SDO_DAS_ChangeSummary::beginLogging' => [''],
'SDO_DAS_ChangeSummary::endLogging' => [''],
'SDO_DAS_ChangeSummary::getChangedDataObjects' => ['SDO_List'],
'SDO_DAS_ChangeSummary::getChangeType' => ['int', 'dataobject'=>'sdo_dataobject'],
'SDO_DAS_ChangeSummary::getOldContainer' => ['SDO_DataObject', 'data_object'=>'sdo_dataobject'],
'SDO_DAS_ChangeSummary::getOldValues' => ['SDO_List', 'data_object'=>'sdo_dataobject'],
'SDO_DAS_ChangeSummary::isLogging' => ['bool'],
'SDO_DAS_DataFactory::addPropertyToType' => ['', 'parent_type_namespace_uri'=>'string', 'parent_type_name'=>'string', 'property_name'=>'string', 'type_namespace_uri'=>'string', 'type_name'=>'string', 'options='=>'array'],
'SDO_DAS_DataFactory::addType' => ['', 'type_namespace_uri'=>'string', 'type_name'=>'string', 'options='=>'array'],
'SDO_DAS_DataFactory::getDataFactory' => ['SDO_DAS_DataFactory'],
'SDO_DAS_DataObject::getChangeSummary' => ['SDO_DAS_ChangeSummary'],
'SDO_DAS_Relational::__construct' => ['void', 'database_metadata'=>'array', 'application_root_type='=>'string', 'sdo_containment_references_metadata='=>'array'],
'SDO_DAS_Relational::applyChanges' => ['', 'database_handle'=>'pdo', 'root_data_object'=>'sdodataobject'],
'SDO_DAS_Relational::createRootDataObject' => ['SDODataObject'],
'SDO_DAS_Relational::executePreparedQuery' => ['SDODataObject', 'database_handle'=>'pdo', 'prepared_statement'=>'pdostatement', 'value_list'=>'array', 'column_specifier='=>'array'],
'SDO_DAS_Relational::executeQuery' => ['SDODataObject', 'database_handle'=>'pdo', 'sql_statement'=>'string', 'column_specifier='=>'array'],
'SDO_DAS_Setting::getListIndex' => ['int'],
'SDO_DAS_Setting::getPropertyIndex' => ['int'],
'SDO_DAS_Setting::getPropertyName' => ['string'],
'SDO_DAS_Setting::getValue' => [''],
'SDO_DAS_Setting::isSet' => ['bool'],
'SDO_DAS_XML::addTypes' => ['', 'xsd_file'=>'string'],
'SDO_DAS_XML::create' => ['SDO_DAS_XML', 'xsd_file='=>'mixed', 'key='=>'string'],
'SDO_DAS_XML::createDataObject' => ['SDO_DataObject', 'namespace_uri'=>'string', 'type_name'=>'string'],
'SDO_DAS_XML::createDocument' => ['SDO_DAS_XML_Document', 'document_element_name'=>'string', 'document_element_namespace_uri'=>'string', 'dataobject='=>'sdo_dataobject'],
'SDO_DAS_XML::loadFile' => ['SDO_XMLDocument', 'xml_file'=>'string'],
'SDO_DAS_XML::loadString' => ['SDO_DAS_XML_Document', 'xml_string'=>'string'],
'SDO_DAS_XML::saveFile' => ['', 'xdoc'=>'sdo_xmldocument', 'xml_file'=>'string', 'indent='=>'int'],
'SDO_DAS_XML::saveString' => ['string', 'xdoc'=>'sdo_xmldocument', 'indent='=>'int'],
'SDO_DAS_XML_Document::getRootDataObject' => ['SDO_DataObject'],
'SDO_DAS_XML_Document::getRootElementName' => ['string'],
'SDO_DAS_XML_Document::getRootElementURI' => ['string'],
'SDO_DAS_XML_Document::setEncoding' => ['', 'encoding'=>'string'],
'SDO_DAS_XML_Document::setXMLDeclaration' => ['', 'xmldeclatation'=>'bool'],
'SDO_DAS_XML_Document::setXMLVersion' => ['', 'xmlversion'=>'string'],
'SDO_DataFactory::create' => ['void', 'type_namespace_uri'=>'string', 'type_name'=>'string'],
'SDO_DataObject::clear' => ['void'],
'SDO_DataObject::createDataObject' => ['SDO_DataObject', 'identifier'=>''],
'SDO_DataObject::getContainer' => ['SDO_DataObject'],
'SDO_DataObject::getSequence' => ['SDO_Sequence'],
'SDO_DataObject::getTypeName' => ['string'],
'SDO_DataObject::getTypeNamespaceURI' => ['string'],
'SDO_Exception::getCause' => [''],
'SDO_List::insert' => ['void', 'value'=>'mixed', 'index='=>'int'],
'SDO_Model_Property::getContainingType' => ['SDO_Model_Type'],
'SDO_Model_Property::getDefault' => [''],
'SDO_Model_Property::getName' => ['string'],
'SDO_Model_Property::getType' => ['SDO_Model_Type'],
'SDO_Model_Property::isContainment' => ['bool'],
'SDO_Model_Property::isMany' => ['bool'],
'SDO_Model_ReflectionDataObject::__construct' => ['void', 'data_object'=>'sdo_dataobject'],
'SDO_Model_ReflectionDataObject::export' => ['mixed', 'rdo'=>'sdo_model_reflectiondataobject', 'return='=>'bool'],
'SDO_Model_ReflectionDataObject::getContainmentProperty' => ['SDO_Model_Property'],
'SDO_Model_ReflectionDataObject::getInstanceProperties' => ['array'],
'SDO_Model_ReflectionDataObject::getType' => ['SDO_Model_Type'],
'SDO_Model_Type::getBaseType' => ['SDO_Model_Type'],
'SDO_Model_Type::getName' => ['string'],
'SDO_Model_Type::getNamespaceURI' => ['string'],
'SDO_Model_Type::getProperties' => ['array'],
'SDO_Model_Type::getProperty' => ['SDO_Model_Property', 'identifier'=>''],
'SDO_Model_Type::isAbstractType' => ['bool'],
'SDO_Model_Type::isDataType' => ['bool'],
'SDO_Model_Type::isInstance' => ['bool', 'data_object'=>'sdo_dataobject'],
'SDO_Model_Type::isOpenType' => ['bool'],
'SDO_Model_Type::isSequencedType' => ['bool'],
'SDO_Sequence::getProperty' => ['SDO_Model_Property', 'sequence_index'=>'int'],
'SDO_Sequence::insert' => ['void', 'value'=>'mixed', 'sequenceindex='=>'int', 'propertyidentifier='=>'mixed'],
'SDO_Sequence::move' => ['void', 'toindex'=>'int', 'fromindex'=>'int'],
'SeekableIterator::seek' => ['void', 'position'=>'int'],
'sem_acquire' => ['bool', 'sem_identifier'=>'resource', 'nowait='=>'bool'],
'sem_get' => ['resource', 'key'=>'int', 'max_acquire='=>'int', 'perm='=>'int', 'auto_release='=>'int'],
'sem_release' => ['bool', 'sem_identifier'=>'resource'],
'sem_remove' => ['bool', 'sem_identifier'=>'resource'],
'Serializable::serialize' => ['string'],
'Serializable::unserialize' => ['void', 'serialized'=>''],
'serialize' => ['string', 'variable'=>'mixed'],
'ServerRequest::withInput' => ['ServerRequest', 'input'=>'mixed'],
'ServerRequest::withoutParams' => ['ServerRequest', 'params'=>'int|string'],
'ServerRequest::withParam' => ['ServerRequest', 'key'=>'int|string', 'val'=>'mixed'],
'ServerRequest::withParams' => ['ServerRequest', 'params'=>'mixed'],
'ServerRequest::withUrl' => ['ServerRequest', 'url'=>'array'],
'ServerResponse::addHeader' => ['void', 'label'=>'string', 'value'=>'string'],
'ServerResponse::date' => ['string', 'date'=>'string|DateTimeInterface'],
'ServerResponse::getHeader' => ['string', 'label'=>'string'],
'ServerResponse::getHeaders' => ['string[]'],
'ServerResponse::getStatus' => ['int'],
'ServerResponse::getVersion' => ['string'],
'ServerResponse::setHeader' => ['void', 'label'=>'string', 'value'=>'string'],
'ServerResponse::setStatus' => ['void', 'status'=>'int'],
'ServerResponse::setVersion' => ['void', 'version'=>'string'],
'session_abort' => ['bool'],
'session_cache_expire' => ['int', 'new_cache_expire='=>'int'],
'session_cache_limiter' => ['string', 'new_cache_limiter='=>'string'],
'session_commit' => ['bool'],
'session_create_id' => ['string', 'prefix'=>'string'],
'session_decode' => ['bool', 'data'=>'string'],
'session_destroy' => ['bool'],
'session_encode' => ['string'],
'session_gc' => ['int'],
'session_get_cookie_params' => ['array'],
'session_id' => ['string', 'newid='=>'string'],
'session_is_registered' => ['bool', 'name'=>'string'],
'session_module_name' => ['string', 'newname='=>'string'],
'session_name' => ['string', 'newname='=>'string'],
'session_pgsql_add_error' => ['bool', 'error_level'=>'int', 'error_message='=>'string'],
'session_pgsql_get_error' => ['array', 'with_error_message='=>'bool'],
'session_pgsql_get_field' => ['string'],
'session_pgsql_reset' => ['bool'],
'session_pgsql_set_field' => ['bool', 'value'=>'string'],
'session_pgsql_status' => ['array'],
'session_regenerate_id' => ['bool', 'delete_old_session='=>'bool'],
'session_register' => ['bool', 'name'=>'mixed', '...args='=>'mixed'],
'session_register_shutdown' => ['void'],
'session_reset' => ['bool'],
'session_save_path' => ['string', 'newname='=>'string'],
'session_set_cookie_params' => ['bool', 'lifetime'=>'int', 'path='=>'string', 'domain='=>'?string', 'secure='=>'bool', 'httponly='=>'bool'],
'session_set_save_handler' => ['bool', 'open'=>'callable(string,string):bool', 'close'=>'callable():bool', 'read'=>'callable(string):string', 'write'=>'callable(string,string):bool', 'destroy'=>'callable(string):bool', 'gc'=>'callable(string):bool', 'create_sid='=>'callable():string', 'validate_sid='=>'callable(string):bool', 'update_timestamp='=>'callable(string):bool'],
'session_set_save_handler\'1' => ['bool', 'sessionhandler'=>'SessionHandlerInterface', 'register_shutdown='=>'bool'],
'session_start' => ['bool', 'options='=>'array'],
'session_status' => ['int'],
'session_unregister' => ['bool', 'name'=>'string'],
'session_unset' => ['bool'],
'session_write_close' => ['bool'],
'SessionHandler::close' => ['bool'],
'SessionHandler::create_sid' => ['char'],
'SessionHandler::destroy' => ['bool', 'id'=>'string'],
'SessionHandler::gc' => ['bool', 'maxlifetime'=>'int'],
'SessionHandler::open' => ['bool', 'save_path'=>'string', 'session_name'=>'string'],
'SessionHandler::read' => ['string', 'id'=>'string'],
'SessionHandler::updateTimestamp' => ['bool', 'session_id'=>'string', 'session_data'=>'string'],
'SessionHandler::validateId' => ['bool', 'session_id'=>'string'],
'SessionHandler::write' => ['bool', 'id'=>'string', 'data'=>'string'],
'SessionHandlerInterface::close' => ['bool'],
'SessionHandlerInterface::destroy' => ['bool', 'session_id'=>'string'],
'SessionHandlerInterface::gc' => ['bool', 'maxlifetime'=>'int'],
'SessionHandlerInterface::open' => ['bool', 'save_path'=>'string', 'name'=>'string'],
'SessionHandlerInterface::read' => ['string', 'session_id'=>'string'],
'SessionHandlerInterface::write' => ['bool', 'session_id'=>'string', 'session_data'=>'string'],
'SessionIdInterface::create_sid' => ['string'],
'SessionUpdateTimestampHandler::updateTimestamp' => ['bool', 'id'=>'string', 'data'=>'string'],
'SessionUpdateTimestampHandler::validateId' => ['char', 'id'=>'string'],
'SessionUpdateTimestampHandlerInterface::updateTimestamp' => ['bool', 'key'=>'string', 'val'=>'string'],
'SessionUpdateTimestampHandlerInterface::validateId' => ['bool', 'key'=>'string'],
'set_error_handler' => ['?string', 'error_handler'=>'null|callable(int,string,string,int,array):bool|callable(int,string,string,int):bool|callable(int,string,string):bool|callable(int,string):bool', 'error_types='=>'int'],
'set_exception_handler' => ['null|callable(Throwable):void', 'exception_handler'=>'null|callable(Throwable):void'],
'set_file_buffer' => ['int', 'fp'=>'resource', 'buffer'=>'int'],
'set_include_path' => ['string', 'new_include_path'=>'string'],
'set_magic_quotes_runtime' => ['bool', 'new_setting'=>'bool'],
'set_time_limit' => ['bool', 'seconds'=>'int'],
'setcookie' => ['bool', 'name'=>'string', 'value='=>'string', 'expires='=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool'],
'setLeftFill' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'],
'setLine' => ['void', 'width'=>'int', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'],
'setlocale' => ['string|false', 'category'=>'int', 'locale'=>'string', '...args='=>'string'],
'setlocale\'1' => ['string|false', 'category'=>'int', 'locale'=>'?array'],
'setproctitle' => ['void', 'title'=>'string'],
'setrawcookie' => ['bool', 'name'=>'string', 'value='=>'string', 'expires='=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool'],
'setRightFill' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'],
'setthreadtitle' => ['bool', 'title'=>'string'],
'settype' => ['bool', '&rw_var'=>'mixed', 'type'=>'string'],
'sha1' => ['string', 'str'=>'string', 'raw_output='=>'bool'],
'sha1_file' => ['string', 'filename'=>'string', 'raw_output='=>'bool'],
'sha256' => ['string', 'str'=>'string', 'raw_output='=>'bool'],
'sha256_file' => ['string', 'filename'=>'string', 'raw_output='=>'bool'],
'shapefileObj::__construct' => ['void', 'filename'=>'string', 'type'=>'int'],
'shapefileObj::addPoint' => ['int', 'point'=>'pointObj'],
'shapefileObj::addShape' => ['int', 'shape'=>'shapeObj'],
'shapefileObj::free' => ['void'],
'shapefileObj::getExtent' => ['rectObj', 'i'=>'int'],
'shapefileObj::getPoint' => ['shapeObj', 'i'=>'int'],
'shapefileObj::getShape' => ['shapeObj', 'i'=>'int'],
'shapefileObj::getTransformed' => ['shapeObj', 'map'=>'MapObj', 'i'=>'int'],
'shapefileObj::ms_newShapefileObj' => ['shapefileObj', 'filename'=>'string', 'type'=>'int'],
'shapeObj::__construct' => ['void', 'type'=>'int'],
'shapeObj::add' => ['int', 'line'=>'lineObj'],
'shapeObj::boundary' => ['shapeObj'],
'shapeObj::contains' => ['bool', 'point'=>'pointObj'],
'shapeObj::containsShape' => ['int', 'shape2'=>'shapeObj'],
'shapeObj::convexhull' => ['shapeObj'],
'shapeObj::crosses' => ['int', 'shape'=>'shapeObj'],
'shapeObj::difference' => ['shapeObj', 'shape'=>'shapeObj'],
'shapeObj::disjoint' => ['int', 'shape'=>'shapeObj'],
'shapeObj::draw' => ['int', 'map'=>'MapObj', 'layer'=>'layerObj', 'img'=>'imageObj'],
'shapeObj::equals' => ['int', 'shape'=>'shapeObj'],
'shapeObj::free' => ['void'],
'shapeObj::getArea' => ['float'],
'shapeObj::getCentroid' => ['pointObj'],
'shapeObj::getLabelPoint' => ['pointObj'],
'shapeObj::getLength' => ['float'],
'shapeObj::getPointUsingMeasure' => ['pointObj', 'm'=>'float'],
'shapeObj::getValue' => ['string', 'layer'=>'layerObj', 'filedname'=>'string'],
'shapeObj::intersection' => ['shapeObj', 'shape'=>'shapeObj'],
'shapeObj::intersects' => ['bool', 'shape'=>'shapeObj'],
'shapeObj::line' => ['lineObj', 'i'=>'int'],
'shapeObj::ms_shapeObjFromWkt' => ['shapeObj', 'wkt'=>'string'],
'shapeObj::overlaps' => ['int', 'shape'=>'shapeObj'],
'shapeObj::project' => ['int', 'in'=>'projectionObj', 'out'=>'projectionObj'],
'shapeObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'shapeObj::setBounds' => ['int'],
'shapeObj::simplify' => ['shapeObj', 'tolerance'=>'float'],
'shapeObj::symdifference' => ['shapeObj', 'shape'=>'shapeObj'],
'shapeObj::topologyPreservingSimplify' => ['shapeObj', 'tolerance'=>'float'],
'shapeObj::touches' => ['int', 'shape'=>'shapeObj'],
'shapeObj::toWkt' => ['string'],
'shapeObj::union' => ['shapeObj', 'shape'=>'shapeObj'],
'shapeObj::within' => ['int', 'shape2'=>'shapeObj'],
'shell_exec' => ['?string', 'cmd'=>'string'],
'shm_attach' => ['resource', 'key'=>'int', 'memsize='=>'int', 'perm='=>'int'],
'shm_detach' => ['bool', 'shm_identifier'=>'resource'],
'shm_get_var' => ['mixed', 'id'=>'resource', 'variable_key'=>'int'],
'shm_has_var' => ['bool', 'shm_identifier'=>'resource', 'variable_key'=>'int'],
'shm_put_var' => ['bool', 'shm_identifier'=>'resource', 'variable_key'=>'int', 'variable'=>'mixed'],
'shm_remove' => ['bool', 'shm_identifier'=>'resource'],
'shm_remove_var' => ['bool', 'shm_identifier'=>'resource', 'variable_key'=>'int'],
'shmop_close' => ['void', 'shmid'=>'int'],
'shmop_delete' => ['bool', 'shmid'=>'int'],
'shmop_open' => ['int', 'key'=>'int', 'flags'=>'string', 'mode'=>'int', 'size'=>'int'],
'shmop_read' => ['string', 'shmid'=>'int', 'start'=>'int', 'count'=>'int'],
'shmop_size' => ['int', 'shmid'=>'int'],
'shmop_write' => ['int', 'shmid'=>'int', 'data'=>'string', 'offset'=>'int'],
'show_source' => ['', 'file_name'=>'', 'return'=>''],
'shuffle' => ['bool', '&rw_array_arg'=>'array'],
'signeurlpaiement' => ['string', 'clent'=>'string', 'data'=>'string'],
'similar_text' => ['int', 'str1'=>'string', 'str2'=>'string', '&w_percent='=>'float'],
'simplexml_import_dom' => ['SimpleXMLElement|false', 'node'=>'DOMNode', 'class_name='=>'string'],
'simplexml_load_file' => ['SimpleXMLElement', 'filename'=>'string', 'class_name='=>'string', 'options='=>'int', 'ns='=>'string', 'is_prefix='=>'bool'],
'simplexml_load_string' => ['SimpleXMLElement', 'data'=>'string', 'class_name='=>'string', 'options='=>'int', 'ns='=>'string', 'is_prefix='=>'bool'],
'SimpleXMLElement::__construct' => ['void', 'data'=>'string', 'options='=>'int', 'data_is_url='=>'bool', 'ns='=>'string', 'is_prefix='=>'bool'],
'SimpleXMLElement::__toString' => ['string'],
'SimpleXMLElement::addAttribute' => ['', 'qname'=>'string', 'value'=>'string', 'ns='=>'string'],
'SimpleXMLElement::addChild' => ['SimpleXMLElement', 'qname'=>'string', 'value='=>'string', 'ns='=>'string'],
'SimpleXMLElement::asXML' => ['string|false', 'filename='=>'string'],
'SimpleXMLElement::attributes' => ['array', 'ns='=>'string', 'is_prefix='=>'bool'],
'SimpleXMLElement::children' => ['SimpleXMLElement', 'ns='=>'string', 'is_prefix='=>'bool'],
'SimpleXMLElement::count' => ['int'],
'SimpleXMLElement::getDocNamespaces' => ['string[]', 'recursive='=>'bool', 'from_root='=>'bool'],
'SimpleXMLElement::getName' => ['string'],
'SimpleXMLElement::getNamespaces' => ['string[]', 'recursve='=>'bool'],
'SimpleXMLElement::registerXPathNamespace' => ['bool', 'prefix'=>'string', 'ns'=>'string'],
'SimpleXMLElement::xpath' => ['array', 'path'=>'string'],
'SimpleXMLIterator::current' => ['SimpleXMLIterator'],
'SimpleXMLIterator::getChildren' => ['SimpleXMLIterator'],
'SimpleXMLIterator::hasChildren' => ['bool'],
'SimpleXMLIterator::key' => ['string'],
'SimpleXMLIterator::next' => ['void'],
'SimpleXMLIterator::rewind' => ['void'],
'SimpleXMLIterator::valid' => ['bool'],
'sin' => ['float', 'number'=>'float'],
'sinh' => ['float', 'number'=>'float'],
'sizeof' => ['int', 'var'=>'Countable|array', 'mode='=>'int'],
'sleep' => ['int|false', 'seconds'=>'int'],
'snmp2_get' => ['string', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmp2_getnext' => ['string', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmp2_real_walk' => ['array', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmp2_set' => ['bool', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'type'=>'string', 'value'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmp2_walk' => ['array', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmp3_get' => ['string', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmp3_getnext' => ['string', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmp3_real_walk' => ['array', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmp3_set' => ['bool', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'type'=>'string', 'value'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmp3_walk' => ['array', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'SNMP::__construct' => ['void', 'version'=>'int', 'hostname'=>'string', 'community'=>'string', 'timeout'=>'int', 'retries'=>'int'],
'SNMP::close' => ['bool'],
'SNMP::get' => ['mixed', 'object_id'=>'mixed', 'preserve_keys'=>'bool'],
'SNMP::getErrno' => ['int'],
'SNMP::getError' => ['int'],
'SNMP::getnext' => ['mixed', 'object_id'=>'mixed'],
'SNMP::set' => ['bool', 'object_id'=>'mixed', 'type'=>'mixed', 'value'=>'mixed'],
'SNMP::setSecurity' => ['bool', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'contextname'=>'string', 'contextengineid'=>'string'],
'SNMP::walk' => ['array', 'object_id'=>'string', 'suffix_as_key'=>'bool', 'max_repetitions'=>'int', 'non_repeaters'=>'int'],
'snmp_get_quick_print' => ['bool'],
'snmp_get_valueretrieval' => ['int'],
'snmp_read_mib' => ['bool', 'filename'=>'string'],
'snmp_set_enum_print' => ['bool', 'enum_print'=>'int'],
'snmp_set_oid_numeric_print' => ['void', 'oid_format'=>'int'],
'snmp_set_oid_output_format' => ['bool', 'oid_format'=>'int'],
'snmp_set_quick_print' => ['bool', 'quick_print'=>'int'],
'snmp_set_valueretrieval' => ['bool', 'method'=>'int'],
'snmpget' => ['string', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmpgetnext' => ['string', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmprealwalk' => ['array', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmpset' => ['bool', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'type'=>'string', 'value'=>'mixed', 'timeout='=>'int', 'retries='=>'int'],
'snmpwalk' => ['array', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'snmpwalkoid' => ['array', 'hostname'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'],
'SoapClient::__call' => ['', 'function_name'=>'string', 'arguments'=>'string'],
'SoapClient::__construct' => ['void', 'wsdl'=>'mixed', 'options='=>'array'],
'SoapClient::__doRequest' => ['string', 'request'=>'string', 'action'=>'string', 'version'=>'int', 'one_way='=>'int'],
'SoapClient::__getCookies' => ['array'],
'SoapClient::__getFunctions' => ['array'],
'SoapClient::__getLastRequest' => ['string'],
'SoapClient::__getLastRequestHeaders' => ['string'],
'SoapClient::__getLastResponse' => ['string'],
'SoapClient::__getLastResponseHeaders' => ['string'],
'SoapClient::__getTypes' => ['array'],
'SoapClient::__setCookie' => ['', 'name'=>'string', 'value='=>'string'],
'SoapClient::__setLocation' => ['string', 'new_location='=>'string'],
'SoapClient::__setSoapHeaders' => ['bool', 'soapheaders='=>''],
'SoapClient::__soapCall' => ['', 'function_name'=>'string', 'arguments'=>'array', 'options='=>'array', 'input_headers='=>'', '&w_output_headers='=>'array'],
'SoapClient::SoapClient' => ['object', 'wsdl'=>'mixed', 'options='=>'array'],
'SoapFault::__construct' => ['void', 'faultcode'=>'string', 'faultstring'=>'string', 'faultactor='=>'string', 'detail='=>'string', 'faultname='=>'string', 'headerfault='=>'string'],
'SoapFault::__toString' => ['string'],
'SoapFault::SoapFault' => ['object', 'faultcode'=>'string', 'faultstring'=>'string', 'faultactor'=>'string', 'detail'=>'string', 'faultname'=>'string', 'headerfault'=>'string'],
'SoapHeader::__construct' => ['void', 'namespace'=>'string', 'name'=>'string', 'data='=>'mixed', 'mustunderstand='=>'bool', 'actor='=>'string'],
'SoapHeader::SoapHeader' => ['object', 'namespace'=>'string', 'name'=>'string', 'data='=>'mixed', 'mustunderstand='=>'bool', 'actor='=>'string'],
'SoapParam::__construct' => ['void', 'data'=>'mixed', 'name'=>'string'],
'SoapParam::SoapParam' => ['object', 'data'=>'mixed', 'name'=>'string'],
'SoapServer::__construct' => ['void', 'wsdl'=>'?string', 'options='=>'array'],
'SoapServer::addFunction' => ['void', 'functions'=>'mixed'],
'SoapServer::addSoapHeader' => ['void', 'object'=>'soapheader'],
'SoapServer::fault' => ['void', 'code'=>'string', 'string'=>'string', 'actor='=>'string', 'details='=>'string', 'name='=>'string'],
'SoapServer::getFunctions' => ['array'],
'SoapServer::handle' => ['void', 'soap_request='=>'string'],
'SoapServer::setClass' => ['void', 'class_name'=>'string', '...args='=>'mixed'],
'SoapServer::setObject' => ['void', 'obj'=>'object'],
'SoapServer::setPersistence' => ['void', 'mode'=>'int'],
'SoapServer::SoapServer' => ['object', 'wsdl'=>'?string', 'options'=>'array'],
'SoapVar::__construct' => ['void', 'data'=>'mixed', 'encoding'=>'int', 'type_name='=>'string', 'type_namespace='=>'string', 'node_name='=>'string', 'node_namespace='=>'string'],
'SoapVar::SoapVar' => ['object', 'data'=>'mixed', 'encoding'=>'int', 'type_name='=>'string', 'type_namespace='=>'string', 'node_name='=>'string', 'node_namespace='=>'string'],
'socket_accept' => ['resource|false', 'socket'=>'resource'],
'socket_addrinfo_bind' => ['resource', 'addrinfo'=>'resource'],
'socket_addrinfo_connect' => ['resource', 'addrinfo'=>'resource'],
'socket_addrinfo_explain' => ['array', 'addrinfo'=>'resource'],
'socket_addrinfo_lookup' => ['resource[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'],
'socket_bind' => ['bool', 'socket'=>'resource', 'addr'=>'string', 'port='=>'int'],
'socket_clear_error' => ['void', 'socket='=>'resource'],
'socket_close' => ['void', 'socket'=>'resource'],
'socket_cmsg_space' => ['int', 'level'=>'int', 'type'=>'int'],
'socket_connect' => ['bool', 'socket'=>'resource', 'addr'=>'string', 'port='=>'int'],
'socket_create' => ['resource|false', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int'],
'socket_create_listen' => ['resource|false', 'port'=>'int', 'backlog='=>'int'],
'socket_create_pair' => ['bool', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int', '&w_fd'=>'resource[]'],
'socket_export_stream' => ['resource', 'socket'=>'resource'],
'socket_get_option' => ['mixed', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int'],
'socket_getopt' => ['mixed', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int'],
'socket_getpeername' => ['bool', 'socket'=>'resource', '&w_addr'=>'string', '&w_port='=>'int'],
'socket_getsockname' => ['bool', 'socket'=>'resource', '&w_addr'=>'string', '&w_port='=>'int'],
'socket_import_stream' => ['resource', 'stream'=>'resource'],
'socket_last_error' => ['int', 'socket='=>'resource'],
'socket_listen' => ['bool', 'socket'=>'resource', 'backlog='=>'int'],
'socket_read' => ['string|false', 'socket'=>'resource', 'length'=>'int', 'type='=>'int'],
'socket_recv' => ['int', 'socket'=>'resource', '&w_buf'=>'string', 'len'=>'int', 'flags'=>'int'],
'socket_recvfrom' => ['int', 'socket'=>'resource', '&w_buf'=>'string', 'len'=>'int', 'flags'=>'int', '&w_name'=>'string', '&w_port='=>'int'],
'socket_recvmsg' => ['int', 'socket'=>'resource', '&w_message'=>'string', 'flags='=>'int'],
'socket_select' => ['int', '&rw_read_fds'=>'resource[]|null', '&rw_write_fds'=>'resource[]|null', '&rw_except_fds'=>'resource[]|null', 'tv_sec'=>'int', 'tv_usec='=>'int'],
'socket_send' => ['int', 'socket'=>'resource', 'buf'=>'string', 'len'=>'int', 'flags'=>'int'],
'socket_sendmsg' => ['int', 'socket'=>'resource', 'message'=>'array', 'flags'=>'int'],
'socket_sendto' => ['int', 'socket'=>'resource', 'buf'=>'string', 'len'=>'int', 'flags'=>'int', 'addr'=>'string', 'port='=>'int'],
'socket_set_block' => ['bool', 'socket'=>'resource'],
'socket_set_nonblock' => ['bool', 'socket'=>'resource'],
'socket_set_option' => ['bool', 'socket'=>'resource', 'level'=>'int', 'optname'=>'int', 'optval'=>'int|array'],
'socket_shutdown' => ['bool', 'socket'=>'resource', 'how='=>'int'],
'socket_strerror' => ['string', 'errno'=>'int'],
'socket_write' => ['int|false', 'socket'=>'resource', 'buf'=>'string', 'length='=>'int'],
'Sodium\add' => ['', '&left'=>'string', 'right'=>'string'],
'Sodium\bin2hex' => ['string', 'binary'=>'string'],
'Sodium\compare' => ['int', 'left'=>'string', 'right'=>'string'],
'Sodium\crypto_aead_aes256gcm_decrypt' => ['string', 'msg'=>'string', 'nonce'=>'string', 'key'=>'string', 'ad='=>'string'],
'Sodium\crypto_aead_aes256gcm_encrypt' => ['string', 'msg'=>'string', 'nonce'=>'string', 'key'=>'string', 'ad='=>'string'],
'Sodium\crypto_aead_aes256gcm_is_available' => ['bool'],
'Sodium\crypto_aead_chacha20poly1305_decrypt' => ['string', 'msg'=>'string', 'nonce'=>'string', 'key'=>'string', 'ad='=>'string'],
'Sodium\crypto_aead_chacha20poly1305_encrypt' => ['string', 'msg'=>'string', 'nonce'=>'string', 'key'=>'string', 'ad='=>'string'],
'Sodium\crypto_auth' => ['string', 'msg'=>'string', 'key'=>'string'],
'Sodium\crypto_auth_verify' => ['bool', 'mac'=>'string', 'msg'=>'string', 'key'=>'string'],
'Sodium\crypto_box' => ['string', 'msg'=>'string', 'nonce'=>'string', 'keypair'=>'string'],
'Sodium\crypto_box_keypair' => ['string'],
'Sodium\crypto_box_keypair_from_secretkey_and_publickey' => ['string', 'secretkey'=>'string', 'publickey'=>'string'],
'Sodium\crypto_box_open' => ['string', 'msg'=>'string', 'nonce'=>'string', 'keypair'=>'string'],
'Sodium\crypto_box_publickey' => ['string', 'keypair'=>'string'],
'Sodium\crypto_box_publickey_from_secretkey' => ['string', 'secretkey'=>'string'],
'Sodium\crypto_box_seal' => ['string', 'message'=>'string', 'publickey'=>'string'],
'Sodium\crypto_box_seal_open' => ['string', 'encrypted'=>'string', 'keypair'=>'string'],
'Sodium\crypto_box_secretkey' => ['string', 'keypair'=>'string'],
'Sodium\crypto_box_seed_keypair' => ['string', 'seed'=>'string'],
'Sodium\crypto_generichash' => ['string', 'input'=>'string', 'key='=>'string', 'length='=>'int'],
'Sodium\crypto_generichash_final' => ['string', 'state'=>'string', 'length='=>'int'],
'Sodium\crypto_generichash_init' => ['string', 'key='=>'string', 'length='=>'int'],
'Sodium\crypto_generichash_update' => ['bool', '&hashState'=>'string', 'append'=>'string'],
'Sodium\crypto_kx' => ['string', 'secretkey'=>'string', 'publickey'=>'string', 'client_publickey'=>'string', 'server_publickey'=>'string'],
'Sodium\crypto_pwhash' => ['string', 'out_len'=>'int', 'passwd'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
'Sodium\crypto_pwhash_scryptsalsa208sha256' => ['string', 'out_len'=>'int', 'passwd'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
'Sodium\crypto_pwhash_scryptsalsa208sha256_str' => ['string', 'passwd'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
'Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify' => ['bool', 'hash'=>'string', 'passwd'=>'string'],
'Sodium\crypto_pwhash_str' => ['string', 'passwd'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
'Sodium\crypto_pwhash_str_verify' => ['bool', 'hash'=>'string', 'passwd'=>'string'],
'Sodium\crypto_scalarmult' => ['string', 'ecdhA'=>'string', 'ecdhB'=>'string'],
'Sodium\crypto_scalarmult_base' => ['string', 'sk'=>'string'],
'Sodium\crypto_secretbox' => ['string', 'plaintext'=>'string', 'nonce'=>'string', 'key'=>'string'],
'Sodium\crypto_secretbox_open' => ['string', 'ciphertext'=>'string', 'nonce'=>'string', 'key'=>'string'],
'Sodium\crypto_shorthash' => ['string', 'message'=>'string', 'key'=>'string'],
'Sodium\crypto_sign' => ['string', 'message'=>'string', 'secretkey'=>'string'],
'Sodium\crypto_sign_detached' => ['string', 'message'=>'string', 'secretkey'=>'string'],
'Sodium\crypto_sign_ed25519_pk_to_curve25519' => ['string', 'sign_pk'=>'string'],
'Sodium\crypto_sign_ed25519_sk_to_curve25519' => ['string', 'sign_sk'=>'string'],
'Sodium\crypto_sign_keypair' => ['string'],
'Sodium\crypto_sign_keypair_from_secretkey_and_publickey' => ['string', 'secretkey'=>'string', 'publickey'=>'string'],
'Sodium\crypto_sign_open' => ['string', 'signed_message'=>'string', 'publickey'=>'string'],
'Sodium\crypto_sign_publickey' => ['string', 'keypair'=>'string'],
'Sodium\crypto_sign_publickey_from_secretkey' => ['string', 'secretkey'=>'string'],
'Sodium\crypto_sign_secretkey' => ['string', 'keypair'=>'string'],
'Sodium\crypto_sign_seed_keypair' => ['string', 'seed'=>'string'],
'Sodium\crypto_sign_verify_detached' => ['bool', 'signature'=>'string', 'msg'=>'string', 'publickey'=>'string'],
'Sodium\crypto_stream' => ['string', 'length'=>'int', 'nonce'=>'string', 'key'=>'string'],
'Sodium\crypto_stream_xor' => ['string', 'plaintext'=>'string', 'nonce'=>'string', 'key'=>'string'],
'Sodium\hex2bin' => ['string', 'hex'=>'string'],
'Sodium\increment' => ['string', '&nonce'=>'string'],
'Sodium\library_version_major' => ['int'],
'Sodium\library_version_minor' => ['int'],
'Sodium\memcmp' => ['int', 'left'=>'string', 'right'=>'string'],
'Sodium\memzero' => ['', '&target'=>'string'],
'Sodium\randombytes_buf' => ['string', 'length'=>'int'],
'Sodium\randombytes_random16' => ['int|string'],
'Sodium\randombytes_uniform' => ['int', 'upperBoundNonInclusive'=>'int'],
'Sodium\version_string' => ['string'],
'sodium_add' => ['string', 'string_1'=>'string', 'string_2'=>'string'],
'sodium_base642bin' => ['string', 'base64'=>'string', 'variant'=>'int', 'ignore'=>'string'],
'sodium_bin2base64' => ['string', 'binary'=>'string', 'variant'=>'int'],
'sodium_bin2hex' => ['string', 'binary'=>'string'],
'sodium_compare' => ['int', 'string_1'=>'string', 'string_2'=>'string'],
'sodium_crypto_aead_aes256gcm_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_aead_aes256gcm_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_aead_aes256gcm_is_available' => ['bool'],
'sodium_crypto_aead_aes256gcm_keygen' => ['string'],
'sodium_crypto_aead_chacha20poly1305_decrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_aead_chacha20poly1305_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_aead_chacha20poly1305_ietf_keygen' => ['string'],
'sodium_crypto_aead_chacha20poly1305_keygen' => ['string'],
'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => ['string', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_aead_xchacha20poly1305_ietf_keygen' => ['string'],
'sodium_crypto_auth' => ['string', 'message'=>'string', 'key'=>'string'],
'sodium_crypto_auth_keygen' => ['string'],
'sodium_crypto_auth_verify' => ['bool', 'mac'=>'string', 'message'=>'string', 'key'=>'string'],
'sodium_crypto_box' => ['string', 'string'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_box_keypair' => ['string'],
'sodium_crypto_box_keypair_from_secretkey_and_publickey' => ['string', 'secret_key'=>'string', 'public_key'=>'string'],
'sodium_crypto_box_open' => ['string|false', 'message'=>'string', 'nonce'=>'string', 'message_keypair'=>'string'],
'sodium_crypto_box_publickey' => ['string', 'keypair'=>'string'],
'sodium_crypto_box_publickey_from_secretkey' => ['string', 'secretkey'=>'string'],
'sodium_crypto_box_seal' => ['string', 'message'=>'string', 'publickey'=>'string'],
'sodium_crypto_box_seal_open' => ['string|false', 'message'=>'string', 'recipient_keypair'=>'string'],
'sodium_crypto_box_secretkey' => ['string', 'keypair'=>'string'],
'sodium_crypto_box_seed_keypair' => ['string', 'seed'=>'string'],
'sodium_crypto_generichash' => ['string', 'msg'=>'string', 'key='=>'?string', 'length='=>'?int'],
'sodium_crypto_generichash_final' => ['string', 'state'=>'string', 'length='=>'?int'],
'sodium_crypto_generichash_init' => ['string', 'key='=>'?string', 'length='=>'?int'],
'sodium_crypto_generichash_keygen' => ['string'],
'sodium_crypto_generichash_update' => ['bool', 'state'=>'string', 'string'=>'string'],
'sodium_crypto_kdf_derive_from_key' => ['string', 'subkey_len'=>'int', 'subkey_id'=>'int', 'context'=>'string', 'key'=>'string'],
'sodium_crypto_kdf_keygen' => ['string'],
'sodium_crypto_kx' => ['string', 'secretkey'=>'string', 'publickey'=>'string', 'client_publickey'=>'string', 'server_publickey'=>'string'],
'sodium_crypto_kx_client_session_keys' => ['string', 'client_keypair'=>'string', 'server_key'=>'string'],
'sodium_crypto_kx_keypair' => ['string'],
'sodium_crypto_kx_publickey' => ['string', 'keypair'=>'string'],
'sodium_crypto_kx_secretkey' => ['string', 'keypair'=>'string'],
'sodium_crypto_kx_seed_keypair' => ['string', 'seed'=>'string'],
'sodium_crypto_kx_server_session_keys' => ['string', 'server_keypair'=>'string', 'client_key'=>'string'],
'sodium_crypto_pwhash' => ['string', 'length'=>'int', 'password'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int', 'alg='=>'int'],
'sodium_crypto_pwhash_scryptsalsa208sha256' => ['string', 'length'=>'int', 'password'=>'string', 'salt'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
'sodium_crypto_pwhash_scryptsalsa208sha256_str' => ['string', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
'sodium_crypto_pwhash_scryptsalsa208sha256_str_verify' => ['bool', 'hash'=>'string', 'password'=>'string'],
'sodium_crypto_pwhash_str' => ['string', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
'sodium_crypto_pwhash_str_needs_rehash' => ['bool', 'password'=>'string', 'opslimit'=>'int', 'memlimit'=>'int'],
'sodium_crypto_pwhash_str_verify' => ['bool', 'hash'=>'string', 'password'=>'string'],
'sodium_crypto_scalarmult' => ['string', 'string_1'=>'string', 'string_2'=>'string'],
'sodium_crypto_scalarmult_base' => ['string', 'string_1'=>'string', 'string_2'=>'string'],
'sodium_crypto_secretbox' => ['string', 'plaintext'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_secretbox_keygen' => ['string'],
'sodium_crypto_secretbox_open' => ['string|false', 'ciphertext'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_secretstream_xchacha20poly1305_init_pull' => ['string', 'header'=>'string', 'key'=>'string'],
'sodium_crypto_secretstream_xchacha20poly1305_init_push' => ['array', 'key'=>'string'],
'sodium_crypto_secretstream_xchacha20poly1305_keygen' => ['string'],
'sodium_crypto_secretstream_xchacha20poly1305_pull' => ['array', 'state'=>'string', 'c'=>'string', 'ad='=>'string'],
'sodium_crypto_secretstream_xchacha20poly1305_push' => ['string', 'state'=>'string', 'msg'=>'string', 'ad='=>'string', 'tag='=>'int'],
'sodium_crypto_secretstream_xchacha20poly1305_rekey' => ['void', 'state'=>'string'],
'sodium_crypto_shorthash' => ['string', 'message'=>'string', 'key'=>'string'],
'sodium_crypto_shorthash_keygen' => ['string'],
'sodium_crypto_sign' => ['string', 'message'=>'string', 'secretkey'=>'string'],
'sodium_crypto_sign_detached' => ['string', 'message'=>'string', 'secretkey'=>'string'],
'sodium_crypto_sign_ed25519_pk_to_curve25519' => ['string', 'ed25519pk'=>'string'],
'sodium_crypto_sign_ed25519_sk_to_curve25519' => ['string', 'ed25519sk'=>'string'],
'sodium_crypto_sign_keypair' => ['string'],
'sodium_crypto_sign_keypair_from_secretkey_and_publickey' => ['string', 'secret_key'=>'string', 'public_key'=>'string'],
'sodium_crypto_sign_open' => ['string|false', 'message'=>'string', 'publickey'=>'string'],
'sodium_crypto_sign_publickey' => ['string', 'keypair'=>'string'],
'sodium_crypto_sign_publickey_from_secretkey' => ['string', 'secretkey'=>'string'],
'sodium_crypto_sign_secretkey' => ['string', 'keypair'=>'string'],
'sodium_crypto_sign_seed_keypair' => ['string', 'seed'=>'string'],
'sodium_crypto_sign_verify_detached' => ['bool', 'signature'=>'string', 'message'=>'string', 'publickey'=>'string'],
'sodium_crypto_stream' => ['string', 'length'=>'int', 'nonce'=>'string', 'key'=>'string'],
'sodium_crypto_stream_keygen' => ['string'],
'sodium_crypto_stream_xor' => ['string', 'message'=>'string', 'nonce'=>'string', 'key'=>'string'],
'sodium_hex2bin' => ['string', 'hex'=>'string', 'ignore='=>'string'],
'sodium_increment' => ['string', '&binary_string'=>'string'],
'sodium_library_version_major' => ['int'],
'sodium_library_version_minor' => ['int'],
'sodium_memcmp' => ['int', 'string_1'=>'string', 'string_2'=>'string'],
'sodium_memzero' => ['void', '&secret'=>'string'],
'sodium_pad' => ['string', 'unpadded'=>'string', 'length'=>'int'],
'sodium_randombytes_buf' => ['string', 'length'=>'int'],
'sodium_randombytes_random16' => ['int|string'],
'sodium_randombytes_uniform' => ['int', 'upperBoundNonInclusive'=>'int'],
'sodium_unpad' => ['string', 'padded'=>'string', 'length'=>'int'],
'sodium_version_string' => ['string'],
'solid_fetch_prev' => ['bool', 'result_id'=>''],
'solr_get_version' => ['string'],
'SolrClient::__construct' => ['void', 'clientOptions'=>'array'],
'SolrClient::__destruct' => [''],
'SolrClient::addDocument' => ['SolrUpdateResponse', 'doc'=>'solrinputdocument', 'allowdups='=>'bool', 'commitwithin='=>'int'],
'SolrClient::addDocuments' => ['SolrUpdateResponse', 'docs'=>'array', 'allowdups='=>'bool', 'commitwithin='=>'int'],
'SolrClient::commit' => ['SolrUpdateResponse', 'maxsegments='=>'int', 'waitflush='=>'bool', 'waitsearcher='=>'bool'],
'SolrClient::deleteById' => ['SolrUpdateResponse', 'id'=>'string'],
'SolrClient::deleteByIds' => ['SolrUpdateResponse', 'ids'=>'array'],
'SolrClient::deleteByQueries' => ['SolrUpdateResponse', 'queries'=>'array'],
'SolrClient::deleteByQuery' => ['SolrUpdateResponse', 'query'=>'string'],
'SolrClient::getById' => ['SolrQueryResponse', 'id'=>'string'],
'SolrClient::getByIds' => ['SolrQueryResponse', 'ids'=>'array'],
'SolrClient::getDebug' => ['string'],
'SolrClient::getOptions' => ['array'],
'SolrClient::optimize' => ['SolrUpdateResponse', 'maxsegments='=>'int', 'waitflush='=>'bool', 'waitsearcher='=>'bool'],
'SolrClient::ping' => ['SolrPingResponse'],
'SolrClient::query' => ['SolrQueryResponse', 'query'=>'solrparams'],
'SolrClient::request' => ['SolrUpdateResponse', 'raw_request'=>'string'],
'SolrClient::rollback' => ['SolrUpdateResponse'],
'SolrClient::setResponseWriter' => ['void', 'responsewriter'=>'string'],
'SolrClient::setServlet' => ['bool', 'type'=>'int', 'value'=>'string'],
'SolrClient::system' => ['void'],
'SolrClient::threads' => ['void'],
'SolrClientException::getInternalInfo' => ['array'],
'SolrCollapseFunction::__toString' => ['string'],
'SolrCollapseFunction::getField' => ['string'],
'SolrCollapseFunction::getHint' => ['string'],
'SolrCollapseFunction::getMax' => ['string'],
'SolrCollapseFunction::getMin' => ['string'],
'SolrCollapseFunction::getNullPolicy' => ['string'],
'SolrCollapseFunction::getSize' => ['int'],
'SolrCollapseFunction::setField' => ['SolrCollapseFunction', 'fieldName'=>'string'],
'SolrCollapseFunction::setHint' => ['SolrCollapseFunction', 'hint'=>'string'],
'SolrCollapseFunction::setMax' => ['SolrCollapseFunction', 'max'=>'string'],
'SolrCollapseFunction::setMin' => ['SolrCollapseFunction', 'min'=>'string'],
'SolrCollapseFunction::setNullPolicy' => ['SolrCollapseFunction', 'nullPolicy'=>'string'],
'SolrCollapseFunction::setSize' => ['SolrCollapseFunction', 'size'=>'int'],
'SolrDisMaxQuery::__construct' => ['void', 'q='=>'string'],
'SolrDisMaxQuery::addBigramPhraseField' => ['SolrDisMaxQuery', 'field'=>'string', 'boost'=>'string', 'slop='=>'string'],
'SolrDisMaxQuery::addBoostQuery' => ['SolrDisMaxQuery', 'field'=>'string', 'value'=>'string', 'boost='=>'string'],
'SolrDisMaxQuery::addExpandFilterQuery' => ['SolrQuery', 'fq'=>'string'],
'SolrDisMaxQuery::addExpandSortField' => ['SolrQuery', 'field'=>'string', 'order'=>'string'],
'SolrDisMaxQuery::addFacetDateField' => ['SolrQuery', 'dateField'=>'string'],
'SolrDisMaxQuery::addFacetDateOther' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::addFacetField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::addFacetQuery' => ['SolrQuery', 'facetQuery'=>'string'],
'SolrDisMaxQuery::addField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::addFilterQuery' => ['SolrQuery', 'fq'=>'string'],
'SolrDisMaxQuery::addGroupField' => ['SolrQuery', 'value'=>'string'],
'SolrDisMaxQuery::addGroupFunction' => ['SolrQuery', 'value'=>'string'],
'SolrDisMaxQuery::addGroupQuery' => ['SolrQuery', 'value'=>'string'],
'SolrDisMaxQuery::addGroupSortField' => ['SolrQuery', 'field'=>'string', 'order'=>'int'],
'SolrDisMaxQuery::addHighlightField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::addMltField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::addMltQueryField' => ['SolrQuery', 'field'=>'string', 'boost'=>'float'],
'SolrDisMaxQuery::addParam' => ['SolrParams', 'name'=>'string', 'value'=>'string'],
'SolrDisMaxQuery::addPhraseField' => ['SolrDisMaxQuery', 'field'=>'string', 'boost'=>'string', 'slop='=>'string'],
'SolrDisMaxQuery::addQueryField' => ['SolrDisMaxQuery', 'field'=>'string', 'boost='=>'string'],
'SolrDisMaxQuery::addSortField' => ['SolrQuery', 'field'=>'string', 'order='=>'int'],
'SolrDisMaxQuery::addStatsFacet' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::addStatsField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::addTrigramPhraseField' => ['SolrDisMaxQuery', 'field'=>'string', 'boost'=>'string', 'slop='=>'string'],
'SolrDisMaxQuery::addUserField' => ['SolrDisMaxQuery', 'field'=>'string'],
'SolrDisMaxQuery::collapse' => ['SolrQuery', 'collapseFunction'=>'SolrCollapseFunction'],
'SolrDisMaxQuery::get' => ['mixed', 'param_name'=>'string'],
'SolrDisMaxQuery::getExpand' => ['bool'],
'SolrDisMaxQuery::getExpandFilterQueries' => ['array'],
'SolrDisMaxQuery::getExpandQuery' => ['array'],
'SolrDisMaxQuery::getExpandRows' => ['int'],
'SolrDisMaxQuery::getExpandSortFields' => ['array'],
'SolrDisMaxQuery::getFacet' => ['bool'],
'SolrDisMaxQuery::getFacetDateEnd' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetDateFields' => ['array'],
'SolrDisMaxQuery::getFacetDateGap' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetDateHardEnd' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetDateOther' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetDateStart' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetFields' => ['array'],
'SolrDisMaxQuery::getFacetLimit' => ['int', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetMethod' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetMinCount' => ['int', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetMissing' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetOffset' => ['int', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetPrefix' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getFacetQueries' => ['string'],
'SolrDisMaxQuery::getFacetSort' => ['int', 'field_override'=>'string'],
'SolrDisMaxQuery::getFields' => ['string'],
'SolrDisMaxQuery::getFilterQueries' => ['string'],
'SolrDisMaxQuery::getGroup' => ['bool'],
'SolrDisMaxQuery::getGroupCachePercent' => ['int'],
'SolrDisMaxQuery::getGroupFacet' => ['bool'],
'SolrDisMaxQuery::getGroupFields' => ['array'],
'SolrDisMaxQuery::getGroupFormat' => ['string'],
'SolrDisMaxQuery::getGroupFunctions' => ['array'],
'SolrDisMaxQuery::getGroupLimit' => ['int'],
'SolrDisMaxQuery::getGroupMain' => ['bool'],
'SolrDisMaxQuery::getGroupNGroups' => ['bool'],
'SolrDisMaxQuery::getGroupOffset' => ['bool'],
'SolrDisMaxQuery::getGroupQueries' => ['array'],
'SolrDisMaxQuery::getGroupSortFields' => ['array'],
'SolrDisMaxQuery::getGroupTruncate' => ['bool'],
'SolrDisMaxQuery::getHighlight' => ['bool'],
'SolrDisMaxQuery::getHighlightAlternateField' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getHighlightFields' => ['array'],
'SolrDisMaxQuery::getHighlightFormatter' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getHighlightFragmenter' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getHighlightFragsize' => ['int', 'field_override'=>'string'],
'SolrDisMaxQuery::getHighlightHighlightMultiTerm' => ['bool'],
'SolrDisMaxQuery::getHighlightMaxAlternateFieldLength' => ['int', 'field_override'=>'string'],
'SolrDisMaxQuery::getHighlightMaxAnalyzedChars' => ['int'],
'SolrDisMaxQuery::getHighlightMergeContiguous' => ['bool', 'field_override'=>'string'],
'SolrDisMaxQuery::getHighlightRegexMaxAnalyzedChars' => ['int'],
'SolrDisMaxQuery::getHighlightRegexPattern' => ['string'],
'SolrDisMaxQuery::getHighlightRegexSlop' => ['float'],
'SolrDisMaxQuery::getHighlightRequireFieldMatch' => ['bool'],
'SolrDisMaxQuery::getHighlightSimplePost' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getHighlightSimplePre' => ['string', 'field_override'=>'string'],
'SolrDisMaxQuery::getHighlightSnippets' => ['int', 'field_override'=>'string'],
'SolrDisMaxQuery::getHighlightUsePhraseHighlighter' => ['bool'],
'SolrDisMaxQuery::getMlt' => ['bool'],
'SolrDisMaxQuery::getMltBoost' => ['bool'],
'SolrDisMaxQuery::getMltCount' => ['int'],
'SolrDisMaxQuery::getMltFields' => ['array'],
'SolrDisMaxQuery::getMltMaxNumQueryTerms' => ['int'],
'SolrDisMaxQuery::getMltMaxNumTokens' => ['int'],
'SolrDisMaxQuery::getMltMaxWordLength' => ['int'],
'SolrDisMaxQuery::getMltMinDocFrequency' => ['int'],
'SolrDisMaxQuery::getMltMinTermFrequency' => ['int'],
'SolrDisMaxQuery::getMltMinWordLength' => ['int'],
'SolrDisMaxQuery::getMltQueryFields' => ['array'],
'SolrDisMaxQuery::getParam' => ['mixed', 'param_name'=>'string'],
'SolrDisMaxQuery::getParams' => ['array'],
'SolrDisMaxQuery::getPreparedParams' => ['array'],
'SolrDisMaxQuery::getQuery' => ['string'],
'SolrDisMaxQuery::getRows' => ['int'],
'SolrDisMaxQuery::getSortFields' => ['array'],
'SolrDisMaxQuery::getStart' => ['int'],
'SolrDisMaxQuery::getStats' => ['bool'],
'SolrDisMaxQuery::getStatsFacets' => ['array'],
'SolrDisMaxQuery::getStatsFields' => ['array'],
'SolrDisMaxQuery::getTerms' => ['bool'],
'SolrDisMaxQuery::getTermsField' => ['string'],
'SolrDisMaxQuery::getTermsIncludeLowerBound' => ['bool'],
'SolrDisMaxQuery::getTermsIncludeUpperBound' => ['bool'],
'SolrDisMaxQuery::getTermsLimit' => ['int'],
'SolrDisMaxQuery::getTermsLowerBound' => ['string'],
'SolrDisMaxQuery::getTermsMaxCount' => ['int'],
'SolrDisMaxQuery::getTermsMinCount' => ['int'],
'SolrDisMaxQuery::getTermsPrefix' => ['string'],
'SolrDisMaxQuery::getTermsReturnRaw' => ['bool'],
'SolrDisMaxQuery::getTermsSort' => ['int'],
'SolrDisMaxQuery::getTermsUpperBound' => ['string'],
'SolrDisMaxQuery::getTimeAllowed' => ['int'],
'SolrDisMaxQuery::removeBigramPhraseField' => ['SolrDisMaxQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeBoostQuery' => ['SolrDisMaxQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeExpandFilterQuery' => ['SolrQuery', 'fq'=>'string'],
'SolrDisMaxQuery::removeExpandSortField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeFacetDateField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeFacetDateOther' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::removeFacetField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeFacetQuery' => ['SolrQuery', 'value'=>'string'],
'SolrDisMaxQuery::removeField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeFilterQuery' => ['SolrQuery', 'fq'=>'string'],
'SolrDisMaxQuery::removeHighlightField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeMltField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeMltQueryField' => ['SolrQuery', 'queryField'=>'string'],
'SolrDisMaxQuery::removePhraseField' => ['SolrDisMaxQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeQueryField' => ['SolrDisMaxQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeSortField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeStatsFacet' => ['SolrQuery', 'value'=>'string'],
'SolrDisMaxQuery::removeStatsField' => ['SolrQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeTrigramPhraseField' => ['SolrDisMaxQuery', 'field'=>'string'],
'SolrDisMaxQuery::removeUserField' => ['SolrDisMaxQuery', 'field'=>'string'],
'SolrDisMaxQuery::serialize' => ['string'],
'SolrDisMaxQuery::set' => ['SolrParams', 'name'=>'string', 'value'=>''],
'SolrDisMaxQuery::setBigramPhraseFields' => ['SolrDisMaxQuery', 'fields'=>'string'],
'SolrDisMaxQuery::setBigramPhraseSlop' => ['SolrDisMaxQuery', 'slop'=>'string'],
'SolrDisMaxQuery::setBoostFunction' => ['SolrDisMaxQuery', 'function'=>'string'],
'SolrDisMaxQuery::setBoostQuery' => ['SolrDisMaxQuery', 'q'=>'string'],
'SolrDisMaxQuery::setEchoHandler' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setEchoParams' => ['SolrQuery', 'type'=>'string'],
'SolrDisMaxQuery::setExpand' => ['SolrQuery', 'value'=>'bool'],
'SolrDisMaxQuery::setExpandQuery' => ['SolrQuery', 'q'=>'string'],
'SolrDisMaxQuery::setExpandRows' => ['SolrQuery', 'value'=>'int'],
'SolrDisMaxQuery::setExplainOther' => ['SolrQuery', 'query'=>'string'],
'SolrDisMaxQuery::setFacet' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setFacetDateEnd' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetDateGap' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetDateHardEnd' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetDateStart' => ['SolrQuery', 'value'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetEnumCacheMinDefaultFrequency' => ['SolrQuery', 'frequency'=>'int', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetLimit' => ['SolrQuery', 'limit'=>'int', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetMethod' => ['SolrQuery', 'method'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetMinCount' => ['SolrQuery', 'mincount'=>'int', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetMissing' => ['SolrQuery', 'flag'=>'bool', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetOffset' => ['SolrQuery', 'offset'=>'int', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetPrefix' => ['SolrQuery', 'prefix'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setFacetSort' => ['SolrQuery', 'facetSort'=>'int', 'field_override'=>'string'],
'SolrDisMaxQuery::setGroup' => ['SolrQuery', 'value'=>'bool'],
'SolrDisMaxQuery::setGroupCachePercent' => ['SolrQuery', 'percent'=>'int'],
'SolrDisMaxQuery::setGroupFacet' => ['SolrQuery', 'value'=>'bool'],
'SolrDisMaxQuery::setGroupFormat' => ['SolrQuery', 'value'=>'string'],
'SolrDisMaxQuery::setGroupLimit' => ['SolrQuery', 'value'=>'int'],
'SolrDisMaxQuery::setGroupMain' => ['SolrQuery', 'value'=>'string'],
'SolrDisMaxQuery::setGroupNGroups' => ['SolrQuery', 'value'=>'bool'],
'SolrDisMaxQuery::setGroupOffset' => ['SolrQuery', 'value'=>'int'],
'SolrDisMaxQuery::setGroupTruncate' => ['SolrQuery', 'value'=>'bool'],
'SolrDisMaxQuery::setHighlight' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setHighlightAlternateField' => ['SolrQuery', 'field'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setHighlightFormatter' => ['SolrQuery', 'formatter'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setHighlightFragmenter' => ['SolrQuery', 'fragmenter'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setHighlightFragsize' => ['SolrQuery', 'size'=>'int', 'field_override'=>'string'],
'SolrDisMaxQuery::setHighlightHighlightMultiTerm' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setHighlightMaxAlternateFieldLength' => ['SolrQuery', 'fieldLength'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setHighlightMaxAnalyzedChars' => ['SolrQuery', 'value'=>'int'],
'SolrDisMaxQuery::setHighlightMergeContiguous' => ['SolrQuery', 'flag'=>'bool', 'field_override'=>'string'],
'SolrDisMaxQuery::setHighlightRegexMaxAnalyzedChars' => ['SolrQuery', 'maxAnalyzedChars'=>'int'],
'SolrDisMaxQuery::setHighlightRegexPattern' => ['SolrQuery', 'value'=>'string'],
'SolrDisMaxQuery::setHighlightRegexSlop' => ['SolrQuery', 'factor'=>'float'],
'SolrDisMaxQuery::setHighlightRequireFieldMatch' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setHighlightSimplePost' => ['SolrQuery', 'simplePost'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setHighlightSimplePre' => ['SolrQuery', 'simplePre'=>'string', 'field_override'=>'string'],
'SolrDisMaxQuery::setHighlightSnippets' => ['SolrQuery', 'value'=>'int', 'field_override'=>'string'],
'SolrDisMaxQuery::setHighlightUsePhraseHighlighter' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setMinimumMatch' => ['SolrDisMaxQuery', 'value'=>'string'],
'SolrDisMaxQuery::setMlt' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setMltBoost' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setMltCount' => ['SolrQuery', 'count'=>'int'],
'SolrDisMaxQuery::setMltMaxNumQueryTerms' => ['SolrQuery', 'value'=>'int'],
'SolrDisMaxQuery::setMltMaxNumTokens' => ['SolrQuery', 'value'=>'int'],
'SolrDisMaxQuery::setMltMaxWordLength' => ['SolrQuery', 'maxWordLength'=>'int'],
'SolrDisMaxQuery::setMltMinDocFrequency' => ['SolrQuery', 'minDocFrequency'=>'int'],
'SolrDisMaxQuery::setMltMinTermFrequency' => ['SolrQuery', 'minTermFrequency'=>'int'],
'SolrDisMaxQuery::setMltMinWordLength' => ['SolrQuery', 'minWordLength'=>'int'],
'SolrDisMaxQuery::setOmitHeader' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setParam' => ['SolrParams', 'name'=>'string', 'value'=>''],
'SolrDisMaxQuery::setPhraseFields' => ['SolrDisMaxQuery', 'fields'=>'string'],
'SolrDisMaxQuery::setPhraseSlop' => ['SolrDisMaxQuery', 'slop'=>'string'],
'SolrDisMaxQuery::setQuery' => ['SolrQuery', 'query'=>'string'],
'SolrDisMaxQuery::setQueryAlt' => ['SolrDisMaxQuery', 'q'=>'string'],
'SolrDisMaxQuery::setQueryPhraseSlop' => ['SolrDisMaxQuery', 'slop'=>'string'],
'SolrDisMaxQuery::setRows' => ['SolrQuery', 'rows'=>'int'],
'SolrDisMaxQuery::setShowDebugInfo' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setStart' => ['SolrQuery', 'start'=>'int'],
'SolrDisMaxQuery::setStats' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setTerms' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setTermsField' => ['SolrQuery', 'fieldname'=>'string'],
'SolrDisMaxQuery::setTermsIncludeLowerBound' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setTermsIncludeUpperBound' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setTermsLimit' => ['SolrQuery', 'limit'=>'int'],
'SolrDisMaxQuery::setTermsLowerBound' => ['SolrQuery', 'lowerBound'=>'string'],
'SolrDisMaxQuery::setTermsMaxCount' => ['SolrQuery', 'frequency'=>'int'],
'SolrDisMaxQuery::setTermsMinCount' => ['SolrQuery', 'frequency'=>'int'],
'SolrDisMaxQuery::setTermsPrefix' => ['SolrQuery', 'prefix'=>'string'],
'SolrDisMaxQuery::setTermsReturnRaw' => ['SolrQuery', 'flag'=>'bool'],
'SolrDisMaxQuery::setTermsSort' => ['SolrQuery', 'sortType'=>'int'],
'SolrDisMaxQuery::setTermsUpperBound' => ['SolrQuery', 'upperBound'=>'string'],
'SolrDisMaxQuery::setTieBreaker' => ['SolrDisMaxQuery', 'tieBreaker'=>'string'],
'SolrDisMaxQuery::setTimeAllowed' => ['SolrQuery', 'timeAllowed'=>'int'],
'SolrDisMaxQuery::setTrigramPhraseFields' => ['SolrDisMaxQuery', 'fields'=>'string'],
'SolrDisMaxQuery::setTrigramPhraseSlop' => ['SolrDisMaxQuery', 'slop'=>'string'],
'SolrDisMaxQuery::setUserFields' => ['SolrDisMaxQuery', 'fields'=>'string'],
'SolrDisMaxQuery::toString' => ['string', 'url_encode='=>'bool|false'],
'SolrDisMaxQuery::unserialize' => ['void', 'serialized'=>'string'],
'SolrDisMaxQuery::useDisMaxQueryParser' => ['SolrDisMaxQuery'],
'SolrDisMaxQuery::useEDisMaxQueryParser' => ['SolrDisMaxQuery'],
'SolrDocument::__clone' => ['void'],
'SolrDocument::__construct' => ['void'],
'SolrDocument::__destruct' => [''],
'SolrDocument::__get' => ['SolrDocumentField', 'fieldname'=>'string'],
'SolrDocument::__isset' => ['bool', 'fieldname'=>'string'],
'SolrDocument::__set' => ['bool', 'fieldname'=>'string', 'fieldvalue'=>'string'],
'SolrDocument::__unset' => ['bool', 'fieldname'=>'string'],
'SolrDocument::addField' => ['bool', 'fieldname'=>'string', 'fieldvalue'=>'string'],
'SolrDocument::clear' => ['bool'],
'SolrDocument::current' => ['SolrDocumentField'],
'SolrDocument::deleteField' => ['bool', 'fieldname'=>'string'],
'SolrDocument::fieldExists' => ['bool', 'fieldname'=>'string'],
'SolrDocument::getChildDocuments' => ['array'],
'SolrDocument::getChildDocumentsCount' => ['int'],
'SolrDocument::getField' => ['SolrDocumentField', 'fieldname'=>'string'],
'SolrDocument::getFieldCount' => ['int'],
'SolrDocument::getFieldNames' => ['array'],
'SolrDocument::getInputDocument' => ['SolrInputDocument'],
'SolrDocument::hasChildDocuments' => ['bool'],
'SolrDocument::key' => ['string'],
'SolrDocument::merge' => ['bool', 'sourcedoc'=>'solrdocument', 'overwrite='=>'bool'],
'SolrDocument::next' => ['void'],
'SolrDocument::offsetExists' => ['bool', 'fieldname'=>'string'],
'SolrDocument::offsetGet' => ['SolrDocumentField', 'fieldname'=>'string'],
'SolrDocument::offsetSet' => ['void', 'fieldname'=>'string', 'fieldvalue'=>'string'],
'SolrDocument::offsetUnset' => ['void', 'fieldname'=>'string'],
'SolrDocument::reset' => ['bool'],
'SolrDocument::rewind' => ['void'],
'SolrDocument::serialize' => ['string'],
'SolrDocument::sort' => ['bool', 'sortorderby'=>'int', 'sortdirection='=>'int'],
'SolrDocument::toArray' => ['array'],
'SolrDocument::unserialize' => ['void', 'serialized'=>'string'],
'SolrDocument::valid' => ['bool'],
'SolrDocumentField::__construct' => ['void'],
'SolrDocumentField::__destruct' => [''],
'SolrException::__clone' => ['void'],
'SolrException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\Exception|?\Throwable'],
'SolrException::__toString' => ['string'],
'SolrException::__wakeup' => ['void'],
'SolrException::getCode' => ['int'],
'SolrException::getFile' => ['string'],
'SolrException::getInternalInfo' => ['array'],
'SolrException::getLine' => ['int'],
'SolrException::getMessage' => ['string'],
'SolrException::getPrevious' => ['Exception|Throwable'],
'SolrException::getTrace' => ['array'],
'SolrException::getTraceAsString' => ['string'],
'SolrGenericResponse::__construct' => ['void'],
'SolrGenericResponse::__destruct' => [''],
'SolrGenericResponse::getDigestedResponse' => ['string'],
'SolrGenericResponse::getHttpStatus' => ['int'],
'SolrGenericResponse::getHttpStatusMessage' => ['string'],
'SolrGenericResponse::getRawRequest' => ['string'],
'SolrGenericResponse::getRawRequestHeaders' => ['string'],
'SolrGenericResponse::getRawResponse' => ['string'],
'SolrGenericResponse::getRawResponseHeaders' => ['string'],
'SolrGenericResponse::getRequestUrl' => ['string'],
'SolrGenericResponse::getResponse' => ['SolrObject'],
'SolrGenericResponse::setParseMode' => ['bool', 'parser_mode='=>'int'],
'SolrGenericResponse::success' => ['bool'],
'SolrIllegalArgumentException::__clone' => ['void'],
'SolrIllegalArgumentException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\Exception|?\Throwable'],
'SolrIllegalArgumentException::__toString' => ['string'],
'SolrIllegalArgumentException::__wakeup' => ['void'],
'SolrIllegalArgumentException::getCode' => ['int'],
'SolrIllegalArgumentException::getFile' => ['string'],
'SolrIllegalArgumentException::getInternalInfo' => ['array'],
'SolrIllegalArgumentException::getLine' => ['int'],
'SolrIllegalArgumentException::getMessage' => ['string'],
'SolrIllegalArgumentException::getPrevious' => ['Exception|Throwable'],
'SolrIllegalArgumentException::getTrace' => ['array'],
'SolrIllegalArgumentException::getTraceAsString' => ['string'],
'SolrIllegalOperationException::__clone' => ['void'],
'SolrIllegalOperationException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\Exception|?\Throwable'],
'SolrIllegalOperationException::__toString' => ['string'],
'SolrIllegalOperationException::__wakeup' => ['void'],
'SolrIllegalOperationException::getCode' => ['int'],
'SolrIllegalOperationException::getFile' => ['string'],
'SolrIllegalOperationException::getInternalInfo' => ['array'],
'SolrIllegalOperationException::getLine' => ['int'],
'SolrIllegalOperationException::getMessage' => ['string'],
'SolrIllegalOperationException::getPrevious' => ['Exception|Throwable'],
'SolrIllegalOperationException::getTrace' => ['array'],
'SolrIllegalOperationException::getTraceAsString' => ['string'],
'SolrInputDocument::__clone' => ['void'],
'SolrInputDocument::__construct' => ['void'],
'SolrInputDocument::__destruct' => [''],
'SolrInputDocument::addChildDocument' => ['void', 'child'=>'SolrInputDocument'],
'SolrInputDocument::addChildDocuments' => ['void', 'docs'=>'array'],
'SolrInputDocument::addField' => ['bool', 'fieldname'=>'string', 'fieldvalue'=>'string', 'fieldboostvalue='=>'float'],
'SolrInputDocument::clear' => ['bool'],
'SolrInputDocument::deleteField' => ['bool', 'fieldname'=>'string'],
'SolrInputDocument::fieldExists' => ['bool', 'fieldname'=>'string'],
'SolrInputDocument::getBoost' => ['float'],
'SolrInputDocument::getChildDocuments' => ['array'],
'SolrInputDocument::getChildDocumentsCount' => ['int'],
'SolrInputDocument::getField' => ['SolrDocumentField', 'fieldname'=>'string'],
'SolrInputDocument::getFieldBoost' => ['float', 'fieldname'=>'string'],
'SolrInputDocument::getFieldCount' => ['int'],
'SolrInputDocument::getFieldNames' => ['array'],
'SolrInputDocument::hasChildDocuments' => ['bool'],
'SolrInputDocument::merge' => ['bool', 'sourcedoc'=>'solrinputdocument', 'overwrite='=>'bool'],
'SolrInputDocument::reset' => ['bool'],
'SolrInputDocument::setBoost' => ['bool', 'documentboostvalue'=>'float'],
'SolrInputDocument::setFieldBoost' => ['bool', 'fieldname'=>'string', 'fieldboostvalue'=>'float'],
'SolrInputDocument::sort' => ['bool', 'sortorderby'=>'int', 'sortdirection='=>'int'],
'SolrInputDocument::toArray' => ['array'],
'SolrModifiableParams::__construct' => ['void'],
'SolrModifiableParams::__destruct' => [''],
'SolrModifiableParams::add' => ['SolrParams', 'name'=>'string', 'value'=>'string'],
'SolrModifiableParams::addParam' => ['SolrParams', 'name'=>'string', 'value'=>'string'],
'SolrModifiableParams::get' => ['mixed', 'param_name'=>'string'],
'SolrModifiableParams::getParam' => ['mixed', 'param_name'=>'string'],
'SolrModifiableParams::getParams' => ['array'],
'SolrModifiableParams::getPreparedParams' => ['array'],
'SolrModifiableParams::serialize' => ['string'],
'SolrModifiableParams::set' => ['SolrParams', 'name'=>'string', 'value'=>''],
'SolrModifiableParams::setParam' => ['SolrParams', 'name'=>'string', 'value'=>''],
'SolrModifiableParams::toString' => ['string', 'url_encode='=>'bool|false'],
'SolrModifiableParams::unserialize' => ['void', 'serialized'=>'string'],
'SolrObject::__construct' => ['void'],
'SolrObject::__destruct' => [''],
'SolrObject::getPropertyNames' => ['array'],
'SolrObject::offsetExists' => ['bool', 'property_name'=>'string'],
'SolrObject::offsetGet' => ['mixed', 'property_name'=>'string'],
'SolrObject::offsetSet' => ['void', 'property_name'=>'string', 'property_value'=>'string'],
'SolrObject::offsetUnset' => ['void', 'property_name'=>'string'],
'SolrParams::__construct' => ['void'],
'SolrParams::add' => ['SolrParams', 'name'=>'string', 'value'=>'string'],
'SolrParams::addParam' => ['SolrParams', 'name'=>'string', 'value'=>'string'],
'SolrParams::get' => ['mixed', 'param_name'=>'string'],
'SolrParams::getParam' => ['mixed', 'param_name='=>'string'],
'SolrParams::getParams' => ['array'],
'SolrParams::getPreparedParams' => ['array'],
'SolrParams::serialize' => ['string'],
'SolrParams::set' => ['void', 'name'=>'string', 'value'=>'string'],
'SolrParams::setParam' => ['SolrParams', 'name'=>'string', 'value'=>'string'],
'SolrParams::toString' => ['string', 'url_encode='=>'bool'],
'SolrParams::unserialize' => ['void', 'serialized'=>'string'],
'SolrPingResponse::__construct' => ['void'],
'SolrPingResponse::__destruct' => [''],
'SolrPingResponse::getDigestedResponse' => ['string'],
'SolrPingResponse::getHttpStatus' => ['int'],
'SolrPingResponse::getHttpStatusMessage' => ['string'],
'SolrPingResponse::getRawRequest' => ['string'],
'SolrPingResponse::getRawRequestHeaders' => ['string'],
'SolrPingResponse::getRawResponse' => ['string'],
'SolrPingResponse::getRawResponseHeaders' => ['string'],
'SolrPingResponse::getRequestUrl' => ['string'],
'SolrPingResponse::getResponse' => ['string'],
'SolrPingResponse::setParseMode' => ['bool', 'parser_mode='=>'int'],
'SolrPingResponse::success' => ['bool'],
'SolrQuery::__construct' => ['void', 'q='=>'string'],
'SolrQuery::__destruct' => [''],
'SolrQuery::add' => ['SolrParams', 'name'=>'string', 'value'=>'string'],
'SolrQuery::addExpandFilterQuery' => ['SolrQuery', 'fq'=>'string'],
'SolrQuery::addExpandSortField' => ['SolrQuery', 'field'=>'string', 'order='=>'string'],
'SolrQuery::addFacetDateField' => ['SolrQuery', 'datefield'=>'string'],
'SolrQuery::addFacetDateOther' => ['SolrQuery', 'value'=>'string', 'field_override='=>'string'],
'SolrQuery::addFacetField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::addFacetQuery' => ['SolrQuery', 'facetquery'=>'string'],
'SolrQuery::addField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::addFilterQuery' => ['SolrQuery', 'fq'=>'string'],
'SolrQuery::addGroupField' => ['SolrQuery', 'value'=>'string'],
'SolrQuery::addGroupFunction' => ['SolrQuery', 'value'=>'string'],
'SolrQuery::addGroupQuery' => ['SolrQuery', 'value'=>'string'],
'SolrQuery::addGroupSortField' => ['SolrQuery', 'field'=>'string', 'order='=>'int'],
'SolrQuery::addHighlightField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::addMltField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::addMltQueryField' => ['SolrQuery', 'field'=>'string', 'boost'=>'float'],
'SolrQuery::addParam' => ['SolrParams', 'name'=>'string', 'value'=>'string'],
'SolrQuery::addSortField' => ['SolrQuery', 'field'=>'string', 'order='=>'int'],
'SolrQuery::addStatsFacet' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::addStatsField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::collapse' => ['SolrQuery', 'collapseFunction'=>'SolrCollapseFunction'],
'SolrQuery::get' => ['mixed', 'param_name'=>'string'],
'SolrQuery::getExpand' => ['bool'],
'SolrQuery::getExpandFilterQueries' => ['array'],
'SolrQuery::getExpandQuery' => ['array'],
'SolrQuery::getExpandRows' => ['int'],
'SolrQuery::getExpandSortFields' => ['array'],
'SolrQuery::getFacet' => ['bool'],
'SolrQuery::getFacetDateEnd' => ['string', 'field_override='=>'string'],
'SolrQuery::getFacetDateFields' => ['array'],
'SolrQuery::getFacetDateGap' => ['string', 'field_override='=>'string'],
'SolrQuery::getFacetDateHardEnd' => ['string', 'field_override='=>'string'],
'SolrQuery::getFacetDateOther' => ['array', 'field_override='=>'string'],
'SolrQuery::getFacetDateStart' => ['string', 'field_override='=>'string'],
'SolrQuery::getFacetFields' => ['array'],
'SolrQuery::getFacetLimit' => ['int', 'field_override='=>'string'],
'SolrQuery::getFacetMethod' => ['string', 'field_override='=>'string'],
'SolrQuery::getFacetMinCount' => ['int', 'field_override='=>'string'],
'SolrQuery::getFacetMissing' => ['bool', 'field_override='=>'string'],
'SolrQuery::getFacetOffset' => ['int', 'field_override='=>'string'],
'SolrQuery::getFacetPrefix' => ['string', 'field_override='=>'string'],
'SolrQuery::getFacetQueries' => ['array'],
'SolrQuery::getFacetSort' => ['int', 'field_override='=>'string'],
'SolrQuery::getFields' => ['array'],
'SolrQuery::getFilterQueries' => ['array'],
'SolrQuery::getGroup' => ['bool'],
'SolrQuery::getGroupCachePercent' => ['int'],
'SolrQuery::getGroupFacet' => ['bool'],
'SolrQuery::getGroupFields' => ['array'],
'SolrQuery::getGroupFormat' => ['string'],
'SolrQuery::getGroupFunctions' => ['array'],
'SolrQuery::getGroupLimit' => ['int'],
'SolrQuery::getGroupMain' => ['bool'],
'SolrQuery::getGroupNGroups' => ['bool'],
'SolrQuery::getGroupOffset' => ['int'],
'SolrQuery::getGroupQueries' => ['array'],
'SolrQuery::getGroupSortFields' => ['array'],
'SolrQuery::getGroupTruncate' => ['bool'],
'SolrQuery::getHighlight' => ['bool'],
'SolrQuery::getHighlightAlternateField' => ['string', 'field_override='=>'string'],
'SolrQuery::getHighlightFields' => ['array'],
'SolrQuery::getHighlightFormatter' => ['string', 'field_override='=>'string'],
'SolrQuery::getHighlightFragmenter' => ['string', 'field_override='=>'string'],
'SolrQuery::getHighlightFragsize' => ['int', 'field_override='=>'string'],
'SolrQuery::getHighlightHighlightMultiTerm' => ['bool'],
'SolrQuery::getHighlightMaxAlternateFieldLength' => ['int', 'field_override='=>'string'],
'SolrQuery::getHighlightMaxAnalyzedChars' => ['int'],
'SolrQuery::getHighlightMergeContiguous' => ['bool', 'field_override='=>'string'],
'SolrQuery::getHighlightRegexMaxAnalyzedChars' => ['int'],
'SolrQuery::getHighlightRegexPattern' => ['string'],
'SolrQuery::getHighlightRegexSlop' => ['float'],
'SolrQuery::getHighlightRequireFieldMatch' => ['bool'],
'SolrQuery::getHighlightSimplePost' => ['string', 'field_override='=>'string'],
'SolrQuery::getHighlightSimplePre' => ['string', 'field_override='=>'string'],
'SolrQuery::getHighlightSnippets' => ['int', 'field_override='=>'string'],
'SolrQuery::getHighlightUsePhraseHighlighter' => ['bool'],
'SolrQuery::getMlt' => ['bool'],
'SolrQuery::getMltBoost' => ['bool'],
'SolrQuery::getMltCount' => ['int'],
'SolrQuery::getMltFields' => ['array'],
'SolrQuery::getMltMaxNumQueryTerms' => ['int'],
'SolrQuery::getMltMaxNumTokens' => ['int'],
'SolrQuery::getMltMaxWordLength' => ['int'],
'SolrQuery::getMltMinDocFrequency' => ['int'],
'SolrQuery::getMltMinTermFrequency' => ['int'],
'SolrQuery::getMltMinWordLength' => ['int'],
'SolrQuery::getMltQueryFields' => ['array'],
'SolrQuery::getParam' => ['mixed', 'param_name'=>'string'],
'SolrQuery::getParams' => ['array'],
'SolrQuery::getPreparedParams' => ['array'],
'SolrQuery::getQuery' => ['string'],
'SolrQuery::getRows' => ['int'],
'SolrQuery::getSortFields' => ['array'],
'SolrQuery::getStart' => ['int'],
'SolrQuery::getStats' => ['bool'],
'SolrQuery::getStatsFacets' => ['array'],
'SolrQuery::getStatsFields' => ['array'],
'SolrQuery::getTerms' => ['bool'],
'SolrQuery::getTermsField' => ['string'],
'SolrQuery::getTermsIncludeLowerBound' => ['bool'],
'SolrQuery::getTermsIncludeUpperBound' => ['bool'],
'SolrQuery::getTermsLimit' => ['int'],
'SolrQuery::getTermsLowerBound' => ['string'],
'SolrQuery::getTermsMaxCount' => ['int'],
'SolrQuery::getTermsMinCount' => ['int'],
'SolrQuery::getTermsPrefix' => ['string'],
'SolrQuery::getTermsReturnRaw' => ['bool'],
'SolrQuery::getTermsSort' => ['int'],
'SolrQuery::getTermsUpperBound' => ['string'],
'SolrQuery::getTimeAllowed' => ['int'],
'SolrQuery::removeExpandFilterQuery' => ['SolrQuery', 'fq'=>'string'],
'SolrQuery::removeExpandSortField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::removeFacetDateField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::removeFacetDateOther' => ['SolrQuery', 'value'=>'string', 'field_override='=>'string'],
'SolrQuery::removeFacetField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::removeFacetQuery' => ['SolrQuery', 'value'=>'string'],
'SolrQuery::removeField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::removeFilterQuery' => ['SolrQuery', 'fq'=>'string'],
'SolrQuery::removeHighlightField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::removeMltField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::removeMltQueryField' => ['SolrQuery', 'queryfield'=>'string'],
'SolrQuery::removeSortField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::removeStatsFacet' => ['SolrQuery', 'value'=>'string'],
'SolrQuery::removeStatsField' => ['SolrQuery', 'field'=>'string'],
'SolrQuery::serialize' => ['string'],
'SolrQuery::set' => ['SolrParams', 'name'=>'string', 'value'=>''],
'SolrQuery::setEchoHandler' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setEchoParams' => ['SolrQuery', 'type'=>'string'],
'SolrQuery::setExpand' => ['SolrQuery', 'value'=>'bool'],
'SolrQuery::setExpandQuery' => ['SolrQuery', 'q'=>'string'],
'SolrQuery::setExpandRows' => ['SolrQuery', 'value'=>'int'],
'SolrQuery::setExplainOther' => ['SolrQuery', 'query'=>'string'],
'SolrQuery::setFacet' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setFacetDateEnd' => ['SolrQuery', 'value'=>'string', 'field_override='=>'string'],
'SolrQuery::setFacetDateGap' => ['SolrQuery', 'value'=>'string', 'field_override='=>'string'],
'SolrQuery::setFacetDateHardEnd' => ['SolrQuery', 'value'=>'bool', 'field_override='=>'string'],
'SolrQuery::setFacetDateStart' => ['SolrQuery', 'value'=>'string', 'field_override='=>'string'],
'SolrQuery::setFacetEnumCacheMinDefaultFrequency' => ['SolrQuery', 'frequency'=>'int', 'field_override='=>'string'],
'SolrQuery::setFacetLimit' => ['SolrQuery', 'limit'=>'int', 'field_override='=>'string'],
'SolrQuery::setFacetMethod' => ['SolrQuery', 'method'=>'string', 'field_override='=>'string'],
'SolrQuery::setFacetMinCount' => ['SolrQuery', 'mincount'=>'int', 'field_override='=>'string'],
'SolrQuery::setFacetMissing' => ['SolrQuery', 'flag'=>'bool', 'field_override='=>'string'],
'SolrQuery::setFacetOffset' => ['SolrQuery', 'offset'=>'int', 'field_override='=>'string'],
'SolrQuery::setFacetPrefix' => ['SolrQuery', 'prefix'=>'string', 'field_override='=>'string'],
'SolrQuery::setFacetSort' => ['SolrQuery', 'facetsort'=>'int', 'field_override='=>'string'],
'SolrQuery::setGroup' => ['SolrQuery', 'value'=>'bool'],
'SolrQuery::setGroupCachePercent' => ['SolrQuery', 'percent'=>'int'],
'SolrQuery::setGroupFacet' => ['SolrQuery', 'value'=>'bool'],
'SolrQuery::setGroupFormat' => ['SolrQuery', 'value'=>'string'],
'SolrQuery::setGroupLimit' => ['SolrQuery', 'value'=>'int'],
'SolrQuery::setGroupMain' => ['SolrQuery', 'value'=>'string'],
'SolrQuery::setGroupNGroups' => ['SolrQuery', 'value'=>'bool'],
'SolrQuery::setGroupOffset' => ['SolrQuery', 'value'=>'int'],
'SolrQuery::setGroupTruncate' => ['SolrQuery', 'value'=>'bool'],
'SolrQuery::setHighlight' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setHighlightAlternateField' => ['SolrQuery', 'field'=>'string', 'field_override='=>'string'],
'SolrQuery::setHighlightFormatter' => ['SolrQuery', 'formatter'=>'string', 'field_override='=>'string'],
'SolrQuery::setHighlightFragmenter' => ['SolrQuery', 'fragmenter'=>'string', 'field_override='=>'string'],
'SolrQuery::setHighlightFragsize' => ['SolrQuery', 'size'=>'int', 'field_override='=>'string'],
'SolrQuery::setHighlightHighlightMultiTerm' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setHighlightMaxAlternateFieldLength' => ['SolrQuery', 'fieldlength'=>'int', 'field_override='=>'string'],
'SolrQuery::setHighlightMaxAnalyzedChars' => ['SolrQuery', 'value'=>'int'],
'SolrQuery::setHighlightMergeContiguous' => ['SolrQuery', 'flag'=>'bool', 'field_override='=>'string'],
'SolrQuery::setHighlightRegexMaxAnalyzedChars' => ['SolrQuery', 'maxanalyzedchars'=>'int'],
'SolrQuery::setHighlightRegexPattern' => ['SolrQuery', 'value'=>'string'],
'SolrQuery::setHighlightRegexSlop' => ['SolrQuery', 'factor'=>'float'],
'SolrQuery::setHighlightRequireFieldMatch' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setHighlightSimplePost' => ['SolrQuery', 'simplepost'=>'string', 'field_override='=>'string'],
'SolrQuery::setHighlightSimplePre' => ['SolrQuery', 'simplepre'=>'string', 'field_override='=>'string'],
'SolrQuery::setHighlightSnippets' => ['SolrQuery', 'value'=>'int', 'field_override='=>'string'],
'SolrQuery::setHighlightUsePhraseHighlighter' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setMlt' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setMltBoost' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setMltCount' => ['SolrQuery', 'count'=>'int'],
'SolrQuery::setMltMaxNumQueryTerms' => ['SolrQuery', 'value'=>'int'],
'SolrQuery::setMltMaxNumTokens' => ['SolrQuery', 'value'=>'int'],
'SolrQuery::setMltMaxWordLength' => ['SolrQuery', 'maxwordlength'=>'int'],
'SolrQuery::setMltMinDocFrequency' => ['SolrQuery', 'mindocfrequency'=>'int'],
'SolrQuery::setMltMinTermFrequency' => ['SolrQuery', 'mintermfrequency'=>'int'],
'SolrQuery::setMltMinWordLength' => ['SolrQuery', 'minwordlength'=>'int'],
'SolrQuery::setOmitHeader' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setParam' => ['SolrParams', 'name'=>'string', 'value'=>''],
'SolrQuery::setQuery' => ['SolrQuery', 'query'=>'string'],
'SolrQuery::setRows' => ['SolrQuery', 'rows'=>'int'],
'SolrQuery::setShowDebugInfo' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setStart' => ['SolrQuery', 'start'=>'int'],
'SolrQuery::setStats' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setTerms' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setTermsField' => ['SolrQuery', 'fieldname'=>'string'],
'SolrQuery::setTermsIncludeLowerBound' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setTermsIncludeUpperBound' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setTermsLimit' => ['SolrQuery', 'limit'=>'int'],
'SolrQuery::setTermsLowerBound' => ['SolrQuery', 'lowerbound'=>'string'],
'SolrQuery::setTermsMaxCount' => ['SolrQuery', 'frequency'=>'int'],
'SolrQuery::setTermsMinCount' => ['SolrQuery', 'frequency'=>'int'],
'SolrQuery::setTermsPrefix' => ['SolrQuery', 'prefix'=>'string'],
'SolrQuery::setTermsReturnRaw' => ['SolrQuery', 'flag'=>'bool'],
'SolrQuery::setTermsSort' => ['SolrQuery', 'sorttype'=>'int'],
'SolrQuery::setTermsUpperBound' => ['SolrQuery', 'upperbound'=>'string'],
'SolrQuery::setTimeAllowed' => ['SolrQuery', 'timeallowed'=>'int'],
'SolrQuery::toString' => ['string', 'url_encode='=>'bool|false'],
'SolrQuery::unserialize' => ['void', 'serialized'=>'string'],
'SolrQueryResponse::__construct' => ['void'],
'SolrQueryResponse::__destruct' => [''],
'SolrQueryResponse::getDigestedResponse' => ['string'],
'SolrQueryResponse::getHttpStatus' => ['int'],
'SolrQueryResponse::getHttpStatusMessage' => ['string'],
'SolrQueryResponse::getRawRequest' => ['string'],
'SolrQueryResponse::getRawRequestHeaders' => ['string'],
'SolrQueryResponse::getRawResponse' => ['string'],
'SolrQueryResponse::getRawResponseHeaders' => ['string'],
'SolrQueryResponse::getRequestUrl' => ['string'],
'SolrQueryResponse::getResponse' => ['SolrObject'],
'SolrQueryResponse::setParseMode' => ['bool', 'parser_mode='=>'int'],
'SolrQueryResponse::success' => ['bool'],
'SolrResponse::getDigestedResponse' => ['string'],
'SolrResponse::getHttpStatus' => ['int'],
'SolrResponse::getHttpStatusMessage' => ['string'],
'SolrResponse::getRawRequest' => ['string'],
'SolrResponse::getRawRequestHeaders' => ['string'],
'SolrResponse::getRawResponse' => ['string'],
'SolrResponse::getRawResponseHeaders' => ['string'],
'SolrResponse::getRequestUrl' => ['string'],
'SolrResponse::getResponse' => ['SolrObject'],
'SolrResponse::setParseMode' => ['bool', 'parser_mode='=>'int'],
'SolrResponse::success' => ['bool'],
'SolrServerException::__clone' => ['void'],
'SolrServerException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\Exception|?\Throwable'],
'SolrServerException::__toString' => ['string'],
'SolrServerException::__wakeup' => ['void'],
'SolrServerException::getCode' => ['int'],
'SolrServerException::getFile' => ['string'],
'SolrServerException::getInternalInfo' => ['array'],
'SolrServerException::getLine' => ['int'],
'SolrServerException::getMessage' => ['string'],
'SolrServerException::getPrevious' => ['Exception|Throwable'],
'SolrServerException::getTrace' => ['array'],
'SolrServerException::getTraceAsString' => ['string'],
'SolrUpdateResponse::__construct' => ['void'],
'SolrUpdateResponse::__destruct' => [''],
'SolrUpdateResponse::getDigestedResponse' => ['string'],
'SolrUpdateResponse::getHttpStatus' => ['int'],
'SolrUpdateResponse::getHttpStatusMessage' => ['string'],
'SolrUpdateResponse::getRawRequest' => ['string'],
'SolrUpdateResponse::getRawRequestHeaders' => ['string'],
'SolrUpdateResponse::getRawResponse' => ['string'],
'SolrUpdateResponse::getRawResponseHeaders' => ['string'],
'SolrUpdateResponse::getRequestUrl' => ['string'],
'SolrUpdateResponse::getResponse' => ['SolrObject'],
'SolrUpdateResponse::setParseMode' => ['bool', 'parser_mode='=>'int'],
'SolrUpdateResponse::success' => ['bool'],
'SolrUtils::digestXmlResponse' => ['SolrObject', 'xmlresponse'=>'string', 'parse_mode='=>'int'],
'SolrUtils::escapeQueryChars' => ['string', 'str'=>'string'],
'SolrUtils::getSolrVersion' => ['string'],
'SolrUtils::queryPhrase' => ['string', 'str'=>'string'],
'sort' => ['bool', '&rw_array_arg'=>'array', 'sort_flags='=>'int'],
'soundex' => ['string', 'str'=>'string'],
'SphinxClient::__construct' => ['void'],
'SphinxClient::addQuery' => ['int', 'query'=>'string', 'index='=>'string', 'comment='=>'string'],
'SphinxClient::buildExcerpts' => ['array', 'docs'=>'array', 'index'=>'string', 'words'=>'string', 'opts='=>'array'],
'SphinxClient::buildKeywords' => ['array', 'query'=>'string', 'index'=>'string', 'hits'=>'bool'],
'SphinxClient::close' => ['bool'],
'SphinxClient::escapeString' => ['string', 'string'=>'string'],
'SphinxClient::getLastError' => ['string'],
'SphinxClient::getLastWarning' => ['string'],
'SphinxClient::open' => ['bool'],
'SphinxClient::query' => ['array', 'query'=>'string', 'index='=>'string', 'comment='=>'string'],
'SphinxClient::resetFilters' => ['void'],
'SphinxClient::resetGroupBy' => ['void'],
'SphinxClient::runQueries' => ['array'],
'SphinxClient::setArrayResult' => ['bool', 'array_result'=>'bool'],
'SphinxClient::setConnectTimeout' => ['bool', 'timeout'=>'float'],
'SphinxClient::setFieldWeights' => ['bool', 'weights'=>'array'],
'SphinxClient::setFilter' => ['bool', 'attribute'=>'string', 'values'=>'array', 'exclude='=>'bool'],
'SphinxClient::setFilterFloatRange' => ['bool', 'attribute'=>'string', 'min'=>'float', 'max'=>'float', 'exclude='=>'bool'],
'SphinxClient::setFilterRange' => ['bool', 'attribute'=>'string', 'min'=>'int', 'max'=>'int', 'exclude='=>'bool'],
'SphinxClient::setGeoAnchor' => ['bool', 'attrlat'=>'string', 'attrlong'=>'string', 'latitude'=>'float', 'longitude'=>'float'],
'SphinxClient::setGroupBy' => ['bool', 'attribute'=>'string', 'func'=>'int', 'groupsort='=>'string'],
'SphinxClient::setGroupDistinct' => ['bool', 'attribute'=>'string'],
'SphinxClient::setIDRange' => ['bool', 'min'=>'int', 'max'=>'int'],
'SphinxClient::setIndexWeights' => ['bool', 'weights'=>'array'],
'SphinxClient::setLimits' => ['bool', 'offset'=>'int', 'limit'=>'int', 'max_matches='=>'int', 'cutoff='=>'int'],
'SphinxClient::setMatchMode' => ['bool', 'mode'=>'int'],
'SphinxClient::setMaxQueryTime' => ['bool', 'qtime'=>'int'],
'SphinxClient::setOverride' => ['bool', 'attribute'=>'string', 'type'=>'int', 'values'=>'array'],
'SphinxClient::setRankingMode' => ['bool', 'ranker'=>'int'],
'SphinxClient::setRetries' => ['bool', 'count'=>'int', 'delay='=>'int'],
'SphinxClient::setSelect' => ['bool', 'clause'=>'string'],
'SphinxClient::setServer' => ['bool', 'server'=>'string', 'port'=>'int'],
'SphinxClient::setSortMode' => ['bool', 'mode'=>'int', 'sortby='=>'string'],
'SphinxClient::status' => ['array'],
'SphinxClient::updateAttributes' => ['int', 'index'=>'string', 'attributes'=>'array', 'values'=>'array', 'mva='=>'bool'],
'spl_autoload' => ['void', 'class_name'=>'string', 'file_extensions='=>'string'],
'spl_autoload_call' => ['void', 'class_name'=>'string'],
'spl_autoload_extensions' => ['string', 'file_extensions='=>'string'],
'spl_autoload_functions' => ['false|array'],
'spl_autoload_register' => ['bool', 'autoload_function='=>'callable(string):void', 'throw='=>'bool', 'prepend='=>'bool'],
'spl_autoload_unregister' => ['bool', 'autoload_function'=>'mixed'],
'spl_classes' => ['array'],
'spl_object_hash' => ['string', 'obj'=>'object'],
'spl_object_id' => ['int', 'obj'=>'object'],
'SplDoublyLinkedList::add' => ['void', 'index'=>'mixed', 'newval'=>'mixed'],
'SplDoublyLinkedList::bottom' => ['mixed'],
'SplDoublyLinkedList::count' => ['int'],
'SplDoublyLinkedList::current' => ['mixed'],
'SplDoublyLinkedList::getIteratorMode' => ['int'],
'SplDoublyLinkedList::isEmpty' => ['bool'],
'SplDoublyLinkedList::key' => ['mixed'],
'SplDoublyLinkedList::next' => ['void'],
'SplDoublyLinkedList::offsetExists' => ['bool', 'index'=>'mixed'],
'SplDoublyLinkedList::offsetGet' => ['mixed', 'index'=>'mixed'],
'SplDoublyLinkedList::offsetSet' => ['void', 'index'=>'mixed', 'newval'=>'mixed'],
'SplDoublyLinkedList::offsetUnset' => ['void', 'index'=>'mixed'],
'SplDoublyLinkedList::pop' => ['mixed'],
'SplDoublyLinkedList::prev' => ['void'],
'SplDoublyLinkedList::push' => ['void', 'value'=>'mixed'],
'SplDoublyLinkedList::rewind' => ['void'],
'SplDoublyLinkedList::serialize' => ['string'],
'SplDoublyLinkedList::setIteratorMode' => ['int', 'flags'=>'int'],
'SplDoublyLinkedList::shift' => ['mixed'],
'SplDoublyLinkedList::top' => ['mixed'],
'SplDoublyLinkedList::unserialize' => ['void', 'serialized'=>'string'],
'SplDoublyLinkedList::unshift' => ['bool', 'value'=>'mixed'],
'SplDoublyLinkedList::valid' => ['bool'],
'SplEnum::__construct' => ['void', 'initial_value='=>'mixed', 'strict='=>'bool|true'],
'SplEnum::getConstList' => ['array', 'include_default='=>'bool'],
'SplFileInfo::__construct' => ['void', 'file_name'=>'string'],
'SplFileInfo::__toString' => ['string'],
'SplFileInfo::getATime' => ['int'],
'SplFileInfo::getBasename' => ['string', 'suffix='=>'string'],
'SplFileInfo::getCTime' => ['int'],
'SplFileInfo::getExtension' => ['string'],
'SplFileInfo::getFileInfo' => ['SplFileInfo', 'class_name='=>'string'],
'SplFileInfo::getFilename' => ['string'],
'SplFileInfo::getGroup' => ['int'],
'SplFileInfo::getInode' => ['int'],
'SplFileInfo::getLinkTarget' => ['string'],
'SplFileInfo::getMTime' => ['int'],
'SplFileInfo::getOwner' => ['int'],
'SplFileInfo::getPath' => ['string'],
'SplFileInfo::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'],
'SplFileInfo::getPathname' => ['string'],
'SplFileInfo::getPerms' => ['int'],
'SplFileInfo::getRealPath' => ['string'],
'SplFileInfo::getSize' => ['int'],
'SplFileInfo::getType' => ['string'],
'SplFileInfo::isDir' => ['bool'],
'SplFileInfo::isExecutable' => ['bool'],
'SplFileInfo::isFile' => ['bool'],
'SplFileInfo::isLink' => ['bool'],
'SplFileInfo::isReadable' => ['bool'],
'SplFileInfo::isWritable' => ['bool'],
'SplFileInfo::openFile' => ['SplFileObject', 'mode='=>'string', 'use_include_path='=>'bool', 'context='=>'resource'],
'SplFileInfo::setFileClass' => ['void', 'class_name='=>'string'],
'SplFileInfo::setInfoClass' => ['void', 'class_name='=>'string'],
'SplFileObject::__construct' => ['void', 'filename'=>'string', 'mode='=>'string', 'use_include_path='=>'bool', 'context='=>''],
'SplFileObject::__toString' => ['string'],
'SplFileObject::current' => ['string|array'],
'SplFileObject::eof' => ['bool'],
'SplFileObject::fflush' => ['bool'],
'SplFileObject::fgetc' => ['string|false'],
'SplFileObject::fgetcsv' => ['array', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'],
'SplFileObject::fgets' => ['string'],
'SplFileObject::fgetss' => ['string', 'allowable_tags='=>'string'],
'SplFileObject::flock' => ['bool', 'operation'=>'int', '&w_wouldblock='=>'int'],
'SplFileObject::fpassthru' => ['int'],
'SplFileObject::fputcsv' => ['int|false', 'fields'=>'array', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'],
'SplFileObject::fread' => ['string', 'length'=>'int'],
'SplFileObject::fscanf' => ['bool', 'format'=>'string', '&...w_vars='=>'string|int|float'],
'SplFileObject::fseek' => ['int', 'pos'=>'int', 'whence='=>'int'],
'SplFileObject::fstat' => ['bool'],
'SplFileObject::ftell' => ['int'],
'SplFileObject::ftruncate' => ['bool', 'size'=>'int'],
'SplFileObject::fwrite' => ['int', 'str'=>'string', 'length='=>'int'],
'SplFileObject::getChildren' => ['bool'],
'SplFileObject::getCsvControl' => ['array'],
'SplFileObject::getFlags' => ['int'],
'SplFileObject::getMaxLineLen' => ['int'],
'SplFileObject::hasChildren' => ['bool'],
'SplFileObject::key' => ['int'],
'SplFileObject::next' => ['void'],
'SplFileObject::rewind' => ['void'],
'SplFileObject::seek' => ['void', 'line_pos'=>'int'],
'SplFileObject::setCsvControl' => ['void', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'],
'SplFileObject::setFlags' => ['void', 'flags'=>'int'],
'SplFileObject::setMaxLineLen' => ['void', 'max_len'=>'int'],
'SplFileObject::valid' => ['bool'],
'SplFixedArray::__construct' => ['void', 'size'=>'int'],
'SplFixedArray::__wakeup' => ['void'],
'SplFixedArray::count' => ['int'],
'SplFixedArray::current' => ['mixed'],
'SplFixedArray::fromArray' => ['SplFixedArray', 'data'=>'array', 'save_indexes='=>'bool'],
'SplFixedArray::getSize' => ['int'],
'SplFixedArray::key' => ['int'],
'SplFixedArray::next' => ['void'],
'SplFixedArray::offsetExists' => ['bool', 'index'=>'int'],
'SplFixedArray::offsetGet' => ['mixed', 'index'=>'int'],
'SplFixedArray::offsetSet' => ['void', 'index'=>'int', 'newval'=>'mixed'],
'SplFixedArray::offsetUnset' => ['void', 'index'=>'int'],
'SplFixedArray::rewind' => ['void'],
'SplFixedArray::setSize' => ['bool', 'size'=>'int'],
'SplFixedArray::toArray' => ['array'],
'SplFixedArray::valid' => ['bool'],
'SplHeap::compare' => ['int', 'value1'=>'mixed', 'value2'=>'mixed'],
'SplHeap::count' => ['int'],
'SplHeap::current' => ['mixed'],
'SplHeap::extract' => ['mixed'],
'SplHeap::insert' => ['bool', 'value'=>'mixed'],
'SplHeap::isCorrupted' => ['int'],
'SplHeap::isEmpty' => ['bool'],
'SplHeap::key' => ['int'],
'SplHeap::next' => ['void'],
'SplHeap::recoverFromCorruption' => ['int'],
'SplHeap::rewind' => ['void'],
'SplHeap::top' => ['mixed'],
'SplHeap::valid' => ['bool'],
'split' => ['array', 'pattern'=>'string', 'string'=>'string', 'limit='=>'int'],
'spliti' => ['array', 'pattern'=>'string', 'string'=>'string', 'limit='=>'int'],
'SplMaxHeap::compare' => ['int', 'a'=>'mixed', 'b'=>'mixed'],
'SplMinHeap::compare' => ['int', 'a'=>'mixed', 'b'=>'mixed'],
'SplObjectStorage::addAll' => ['void', 'os'=>'splobjectstorage'],
'SplObjectStorage::attach' => ['void', 'obj'=>'object', 'inf='=>'mixed'],
'SplObjectStorage::contains' => ['bool', 'obj'=>'object'],
'SplObjectStorage::count' => ['int'],
'SplObjectStorage::current' => ['object'],
'SplObjectStorage::detach' => ['void', 'obj'=>'object'],
'SplObjectStorage::getHash' => ['string', 'obj'=>'object'],
'SplObjectStorage::getInfo' => ['mixed'],
'SplObjectStorage::key' => ['int'],
'SplObjectStorage::next' => ['void'],
'SplObjectStorage::offsetExists' => ['bool', 'object'=>'object'],
'SplObjectStorage::offsetGet' => ['mixed', 'obj'=>'object'],
'SplObjectStorage::offsetSet' => ['object', 'object'=>'object', 'data='=>'mixed'],
'SplObjectStorage::offsetUnset' => ['object', 'object'=>'object'],
'SplObjectStorage::removeAll' => ['void', 'os'=>'splobjectstorage'],
'SplObjectStorage::removeAllExcept' => ['void', 'os'=>'splobjectstorage'],
'SplObjectStorage::rewind' => ['void'],
'SplObjectStorage::serialize' => ['string'],
'SplObjectStorage::setInfo' => ['void', 'inf'=>'mixed'],
'SplObjectStorage::unserialize' => ['void', 'serialized'=>'string'],
'SplObjectStorage::valid' => ['bool'],
'SplObserver::update' => ['void', 'subject'=>'splsubject'],
'SplPriorityQueue::compare' => ['int', 'a'=>'mixed', 'b'=>'mixed'],
'SplPriorityQueue::count' => ['int'],
'SplPriorityQueue::current' => ['mixed'],
'SplPriorityQueue::extract' => ['mixed'],
'SplPriorityQueue::getExtractFlags' => ['int'],
'SplPriorityQueue::insert' => ['bool', 'value'=>'mixed', 'priority'=>'mixed'],
'SplPriorityQueue::isEmpty' => ['bool'],
'SplPriorityQueue::key' => ['mixed'],
'SplPriorityQueue::next' => ['void'],
'SplPriorityQueue::recoverFromCorruption' => ['void'],
'SplPriorityQueue::rewind' => ['void'],
'SplPriorityQueue::setExtractFlags' => ['void', 'flags'=>'int'],
'SplPriorityQueue::top' => ['mixed'],
'SplPriorityQueue::valid' => ['bool'],
'SplQueue::dequeue' => ['mixed'],
'SplQueue::enqueue' => ['void', 'value'=>'mixed'],
'SplQueue::setIteratorMode' => ['void', 'mode'=>'int'],
'SplStack::setIteratorMode' => ['void', 'mode'=>'int'],
'SplSubject::attach' => ['void', 'observer'=>'splobserver'],
'SplSubject::detach' => ['void', 'observer'=>'splobserver'],
'SplSubject::notify' => ['void'],
'SplTempFileObject::__construct' => ['void', 'max_memory='=>'int'],
'SplType::__construct' => ['void', 'initial_value='=>'mixed', 'strict='=>'bool'],
'Spoofchecker::__construct' => ['void'],
'Spoofchecker::areConfusable' => ['bool', 's1'=>'string', 's2'=>'string', '&w_error='=>'string'],
'Spoofchecker::isSuspicious' => ['bool', 'text'=>'string', '&w_error='=>'string'],
'Spoofchecker::setAllowedLocales' => ['void', 'locale_list'=>'string'],
'Spoofchecker::setChecks' => ['void', 'checks'=>'long'],
'sprintf' => ['string', 'format'=>'string', '...vars='=>'string|int|float'],
'sql_regcase' => ['string', 'string'=>'string'],
'SQLite3::__construct' => ['void', 'filename'=>'string', 'flags='=>'int', 'encryption_key='=>'string'],
'SQLite3::busyTimeout' => ['bool', 'msecs'=>'int'],
'SQLite3::changes' => ['int'],
'SQLite3::close' => ['bool'],
'SQLite3::createAggregate' => ['bool', 'name'=>'string', 'step'=>'mixed', 'final'=>'mixed', 'argcount='=>'int'],
'SQLite3::createCollation' => ['bool', 'name'=>'string', 'callback'=>'callable'],
'SQLite3::createFunction' => ['bool', 'name'=>'string', 'callback'=>'mixed', 'argcount='=>'int'],
'SQLite3::enableExceptions' => ['bool', 'enableexceptions'=>'bool'],
'SQLite3::escapeString' => ['string', 'value'=>'string'],
'SQLite3::exec' => ['bool', 'query'=>'string'],
'SQLite3::lastErrorCode' => ['int'],
'SQLite3::lastErrorMsg' => ['string'],
'SQLite3::lastInsertRowID' => ['int'],
'SQLite3::loadExtension' => ['bool', 'shared'=>'string'],
'SQLite3::open' => ['void', 'filename'=>'string', 'flags='=>'int', 'encryption='=>'string'],
'SQLite3::openBlob' => ['resource', 'table'=>'string', 'column'=>'string', 'rowid'=>'int', 'dbname'=>'string'],
'SQLite3::prepare' => ['SQLite3Stmt', 'query'=>'string'],
'SQLite3::query' => ['SQLite3Result', 'query'=>'string'],
'SQLite3::querySingle' => ['SQLite3Result', 'query'=>'string', 'entire_row='=>'bool'],
'SQLite3::version' => ['array'],
'SQLite3Result::__construct' => ['void'],
'SQLite3Result::columnName' => ['string', 'column'=>'int'],
'SQLite3Result::columnType' => ['int', 'column'=>'int'],
'SQLite3Result::fetchArray' => ['array', 'mode='=>'int'],
'SQLite3Result::finalize' => ['bool'],
'SQLite3Result::numColumns' => ['int'],
'SQLite3Result::reset' => ['bool'],
'SQLite3Stmt::__construct' => ['void', 'dbobject'=>'sqlite3', 'statement'=>'string'],
'SQLite3Stmt::bindParam' => ['bool', 'parameter_name_or_number'=>'string|int', '&rw_parameter'=>'mixed', 'type='=>'int'],
'SQLite3Stmt::bindValue' => ['bool', 'parameter_name_or_number'=>'string|int', 'parameter'=>'mixed', 'type='=>'int'],
'SQLite3Stmt::clear' => ['bool'],
'SQLite3Stmt::close' => ['bool'],
'SQLite3Stmt::execute' => ['SQLite3Result'],
'SQLite3Stmt::paramCount' => ['int'],
'SQLite3Stmt::readOnly' => ['bool'],
'SQLite3Stmt::reset' => ['bool'],
'sqlite_array_query' => ['array', 'dbhandle'=>'', 'query'=>'string', 'result_type='=>'int', 'decode_binary='=>'bool'],
'sqlite_busy_timeout' => ['', 'dbhandle'=>'', 'milliseconds'=>'int'],
'sqlite_changes' => ['int', 'dbhandle'=>''],
'sqlite_close' => ['void', 'dbhandle'=>'resource'],
'sqlite_column' => ['', 'result'=>'', 'index_or_name'=>'', 'decode_binary='=>'bool'],
'sqlite_create_aggregate' => ['', 'dbhandle'=>'', 'function_name'=>'string', 'step_func'=>'callable', 'finalize_func'=>'callable', 'num_args='=>'int'],
'sqlite_create_function' => ['', 'dbhandle'=>'', 'function_name'=>'string', 'callback'=>'callable', 'num_args='=>'int'],
'sqlite_current' => ['array', 'result'=>'', 'result_type='=>'int', 'decode_binary='=>'bool'],
'sqlite_error_string' => ['string', 'error_code'=>'int'],
'sqlite_escape_string' => ['string', 'item'=>'string'],
'sqlite_exec' => ['bool', 'dbhandle'=>'', 'query'=>'string', 'error_msg='=>'string'],
'sqlite_factory' => ['SQLiteDatabase', 'filename'=>'string', 'mode='=>'int', 'error_message='=>'string'],
'sqlite_fetch_all' => ['array', 'result'=>'', 'result_type='=>'int', 'decode_binary='=>'bool'],
'sqlite_fetch_array' => ['array', 'result'=>'', 'result_type='=>'int', 'decode_binary='=>'bool'],
'sqlite_fetch_column_types' => ['array', 'table_name'=>'string', 'dbhandle'=>'', 'result_type='=>'int'],
'sqlite_fetch_object' => ['object', 'result'=>'', 'class_name='=>'string', 'ctor_params='=>'array', 'decode_binary='=>'bool'],
'sqlite_fetch_single' => ['string', 'result'=>'', 'decode_binary='=>'bool'],
'sqlite_field_name' => ['string', 'result'=>'', 'field_index'=>'int'],
'sqlite_has_more' => ['bool', 'result'=>'resource'],
'sqlite_has_prev' => ['bool', 'result'=>''],
'sqlite_key' => ['int', 'result'=>''],
'sqlite_last_error' => ['int', 'dbhandle'=>''],
'sqlite_last_insert_rowid' => ['int', 'dbhandle'=>''],
'sqlite_libencoding' => ['string'],
'sqlite_libversion' => ['string'],
'sqlite_next' => ['bool', 'result'=>''],
'sqlite_num_fields' => ['int', 'result'=>''],
'sqlite_num_rows' => ['int', 'result'=>''],
'sqlite_open' => ['resource', 'filename'=>'string', 'mode='=>'int', 'error_message='=>'string'],
'sqlite_popen' => ['resource', 'filename'=>'string', 'mode='=>'int', 'error_message='=>'string'],
'sqlite_prev' => ['bool', 'result'=>''],
'sqlite_query' => ['SQLiteResult', 'dbhandle'=>'', 'query'=>'string', 'result_type='=>'int', 'error_msg='=>'string'],
'sqlite_rewind' => ['bool', 'result'=>''],
'sqlite_seek' => ['bool', 'result'=>'', 'rownum'=>'int'],
'sqlite_single_query' => ['array', 'db'=>'', 'query'=>'string', 'first_row_only='=>'bool', 'decode_binary='=>'bool'],
'sqlite_udf_decode_binary' => ['string', 'data'=>'string'],
'sqlite_udf_encode_binary' => ['string', 'data'=>'string'],
'sqlite_unbuffered_query' => ['SQLiteUnbuffered', 'dbhandle'=>'', 'query'=>'string', 'result_type='=>'int', 'error_msg='=>'string'],
'sqlite_valid' => ['bool', 'result'=>''],
'SQLiteDatabase::arrayQuery' => ['array', 'dbhandle'=>'', 'query'=>'string', 'result_type='=>'int', 'decode_binary='=>'bool'],
'SQLiteDatabase::busyTimeout' => ['', 'dbhandle'=>'', 'milliseconds'=>'int'],
'SQLiteDatabase::changes' => ['int', 'dbhandle'=>''],
'SQLiteDatabase::createAggregate' => ['', 'dbhandle'=>'', 'function_name'=>'string', 'step_func'=>'callable', 'finalize_func'=>'callable', 'num_args='=>'int'],
'SQLiteDatabase::createFunction' => ['', 'dbhandle'=>'', 'function_name'=>'string', 'callback'=>'callable', 'num_args='=>'int'],
'SQLiteDatabase::exec' => ['bool', 'dbhandle'=>'', 'query'=>'string', 'error_msg='=>'string'],
'SQLiteDatabase::fetchColumnTypes' => ['array', 'table_name'=>'string', 'dbhandle'=>'', 'result_type='=>'int'],
'SQLiteDatabase::lastError' => ['int', 'dbhandle'=>''],
'SQLiteDatabase::lastInsertRowid' => ['int', 'dbhandle'=>''],
'SQLiteDatabase::query' => ['SQLiteResult', 'dbhandle'=>'', 'query'=>'string', 'result_type='=>'int', 'error_msg='=>'string'],
'SQLiteDatabase::queryExec' => ['bool', 'query'=>'string', '&w_error_msg='=>'string'],
'SQLiteDatabase::singleQuery' => ['array', 'db'=>'', 'query'=>'string', 'first_row_only='=>'bool', 'decode_binary='=>'bool'],
'SQLiteDatabase::unbufferedQuery' => ['SQLiteUnbuffered', 'dbhandle'=>'', 'query'=>'string', 'result_type='=>'int', 'error_msg='=>'string'],
'SQLiteResult::column' => ['', 'result'=>'', 'index_or_name'=>'', 'decode_binary='=>'bool'],
'SQLiteResult::current' => ['array', 'result'=>'', 'result_type='=>'int', 'decode_binary='=>'bool'],
'SQLiteResult::fetch' => ['array', 'result'=>'', 'result_type='=>'int', 'decode_binary='=>'bool'],
'SQLiteResult::fetchAll' => ['array', 'result'=>'', 'result_type='=>'int', 'decode_binary='=>'bool'],
'SQLiteResult::fetchObject' => ['object', 'result'=>'', 'class_name='=>'string', 'ctor_params='=>'array', 'decode_binary='=>'bool'],
'SQLiteResult::fetchSingle' => ['string', 'result'=>'', 'decode_binary='=>'bool'],
'SQLiteResult::fieldName' => ['string', 'result'=>'', 'field_index'=>'int'],
'SQLiteResult::hasPrev' => ['bool', 'result'=>''],
'SQLiteResult::key' => ['int', 'result'=>''],
'SQLiteResult::next' => ['bool', 'result'=>''],
'SQLiteResult::numFields' => ['int', 'result'=>''],
'SQLiteResult::numRows' => ['int', 'result'=>''],
'SQLiteResult::prev' => ['bool', 'result'=>''],
'SQLiteResult::rewind' => ['bool', 'result'=>''],
'SQLiteResult::seek' => ['bool', 'result'=>'', 'rownum'=>'int'],
'SQLiteResult::valid' => ['bool', 'result'=>''],
'SQLiteUnbuffered::column' => ['', 'result'=>'', 'index_or_name'=>'', 'decode_binary='=>'bool'],
'SQLiteUnbuffered::current' => ['array', 'result'=>'', 'result_type='=>'int', 'decode_binary='=>'bool'],
'SQLiteUnbuffered::fetch' => ['array', 'result'=>'', 'result_type='=>'int', 'decode_binary='=>'bool'],
'SQLiteUnbuffered::fetchAll' => ['array', 'result'=>'', 'result_type='=>'int', 'decode_binary='=>'bool'],
'SQLiteUnbuffered::fetchObject' => ['object', 'result'=>'', 'class_name='=>'string', 'ctor_params='=>'array', 'decode_binary='=>'bool'],
'SQLiteUnbuffered::fetchSingle' => ['string', 'result'=>'', 'decode_binary='=>'bool'],
'SQLiteUnbuffered::fieldName' => ['string', 'result'=>'', 'field_index'=>'int'],
'SQLiteUnbuffered::next' => ['bool', 'result'=>''],
'SQLiteUnbuffered::numFields' => ['int', 'result'=>''],
'SQLiteUnbuffered::valid' => ['bool', 'result'=>''],
'sqlsrv_begin_transaction' => ['bool', 'conn'=>'resource'],
'sqlsrv_cancel' => ['bool', 'stmt'=>'resource'],
'sqlsrv_client_info' => ['array', 'conn'=>'resource'],
'sqlsrv_close' => ['bool', 'conn'=>'resource'],
'sqlsrv_commit' => ['bool', 'conn'=>'resource'],
'sqlsrv_configure' => ['bool', 'setting'=>'string', 'value'=>'mixed'],
'sqlsrv_connect' => ['resource', 'serverName'=>'string', 'connectionInfo='=>'array'],
'sqlsrv_errors' => ['mixed', 'errorsOrWarnings='=>'int'],
'sqlsrv_execute' => ['bool', 'stmt'=>'resource'],
'sqlsrv_fetch' => ['mixed', 'stmt'=>'resource', 'row='=>'int', 'offset='=>'int'],
'sqlsrv_fetch_array' => ['array', 'stmt'=>'resource', 'fetchType='=>'int', 'row='=>'int', 'offset='=>'int'],
'sqlsrv_fetch_object' => ['mixed', 'stmt'=>'resource', 'className='=>'string', 'ctorParams='=>'array', 'row='=>'int', 'offset='=>'int'],
'sqlsrv_field_metadata' => ['mixed', 'stmt'=>'resource'],
'sqlsrv_free_stmt' => ['bool', 'stmt'=>'resource'],
'sqlsrv_get_config' => ['mixed', 'setting'=>'string'],
'sqlsrv_get_field' => ['mixed', 'stmt'=>'resource', 'fieldIndex'=>'int', 'getAsType='=>'int'],
'sqlsrv_has_rows' => ['bool', 'stmt'=>'resource'],
'sqlsrv_next_result' => ['mixed', 'stmt'=>'resource'],
'sqlsrv_num_fields' => ['mixed', 'stmt'=>'resource'],
'sqlsrv_num_rows' => ['mixed', 'stmt'=>'resource'],
'sqlsrv_prepare' => ['mixed', 'conn'=>'resource', 'sql'=>'string', 'params='=>'array', 'options='=>'array'],
'sqlsrv_query' => ['mixed', 'conn'=>'resource', 'sql'=>'string', 'params='=>'array', 'options='=>'array'],
'sqlsrv_rollback' => ['bool', 'conn'=>'resource'],
'sqlsrv_rows_affected' => ['int', 'stmt'=>'resource'],
'sqlsrv_send_stream_data' => ['bool', 'stmt'=>'resource'],
'sqlsrv_server_info' => ['array', 'conn'=>'resource'],
'sqrt' => ['float', 'number'=>'float'],
'srand' => ['void', 'seed='=>'int', 'mode='=>'int'],
'sscanf' => ['mixed', 'str'=>'string', 'format'=>'string', '&...w_vars='=>'string|int|float'],
'ssdeep_fuzzy_compare' => ['int', 'signature1'=>'string', 'signature2'=>'string'],
'ssdeep_fuzzy_hash' => ['string', 'to_hash'=>'string'],
'ssdeep_fuzzy_hash_filename' => ['string', 'file_name'=>'string'],
'ssh2_auth_agent' => ['bool', 'session'=>'resource', 'username'=>'string'],
'ssh2_auth_hostbased_file' => ['bool', 'session'=>'resource', 'username'=>'string', 'hostname'=>'string', 'pubkeyfile'=>'string', 'privkeyfile'=>'string', 'passphrase='=>'string', 'local_username='=>'string'],
'ssh2_auth_none' => ['mixed', 'session'=>'resource', 'username'=>'string'],
'ssh2_auth_password' => ['bool', 'session'=>'resource', 'username'=>'string', 'password'=>'string'],
'ssh2_auth_pubkey_file' => ['bool', 'session'=>'resource', 'username'=>'string', 'pubkeyfile'=>'string', 'privkeyfile'=>'string', 'passphrase='=>'string'],
'ssh2_connect' => ['resource', 'host'=>'string', 'port='=>'int', 'methods='=>'array', 'callbacks='=>'array'],
'ssh2_exec' => ['resource', 'session'=>'resource', 'command'=>'string', 'pty='=>'string', 'env='=>'array', 'width='=>'int', 'height='=>'int', 'width_height_type='=>'int'],
'ssh2_fetch_stream' => ['resource', 'channel'=>'resource', 'streamid'=>'int'],
'ssh2_fingerprint' => ['string', 'session'=>'resource', 'flags='=>'int'],
'ssh2_methods_negotiated' => ['array', 'session'=>'resource'],
'ssh2_publickey_add' => ['bool', 'pkey'=>'resource', 'algoname'=>'string', 'blob'=>'string', 'overwrite='=>'bool', 'attributes='=>'array'],
'ssh2_publickey_init' => ['resource', 'session'=>'resource'],
'ssh2_publickey_list' => ['array', 'pkey'=>'resource'],
'ssh2_publickey_remove' => ['bool', 'pkey'=>'resource', 'algoname'=>'string', 'blob'=>'string'],
'ssh2_scp_recv' => ['bool', 'session'=>'resource', 'remote_file'=>'string', 'local_file'=>'string'],
'ssh2_scp_send' => ['bool', 'session'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'create_mode='=>'int'],
'ssh2_sftp' => ['resource', 'session'=>'resource'],
'ssh2_sftp_chmod' => ['bool', 'sftp'=>'resource', 'filename'=>'string', 'mode'=>'int'],
'ssh2_sftp_lstat' => ['array', 'sftp'=>'resource', 'path'=>'string'],
'ssh2_sftp_mkdir' => ['bool', 'sftp'=>'resource', 'dirname'=>'string', 'mode='=>'int', 'recursive='=>'bool'],
'ssh2_sftp_readlink' => ['string', 'sftp'=>'resource', 'link'=>'string'],
'ssh2_sftp_realpath' => ['string', 'sftp'=>'resource', 'filename'=>'string'],
'ssh2_sftp_rename' => ['bool', 'sftp'=>'resource', 'from'=>'string', 'to'=>'string'],
'ssh2_sftp_rmdir' => ['bool', 'sftp'=>'resource', 'dirname'=>'string'],
'ssh2_sftp_stat' => ['array', 'sftp'=>'resource', 'path'=>'string'],
'ssh2_sftp_symlink' => ['bool', 'sftp'=>'resource', 'target'=>'string', 'link'=>'string'],
'ssh2_sftp_unlink' => ['bool', 'sftp'=>'resource', 'filename'=>'string'],
'ssh2_shell' => ['resource', 'session'=>'resource', 'term_type='=>'string', 'env='=>'array', 'width='=>'int', 'height='=>'int', 'width_height_type='=>'int'],
'ssh2_tunnel' => ['resource', 'session'=>'resource', 'host'=>'string', 'port'=>'int'],
'stat' => ['array|false', 'filename'=>'string'],
'stats_absolute_deviation' => ['float', 'a'=>'array'],
'stats_cdf_beta' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_binomial' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_cauchy' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_chisquare' => ['float', 'par1'=>'float', 'par2'=>'float', 'which'=>'int'],
'stats_cdf_exponential' => ['float', 'par1'=>'float', 'par2'=>'float', 'which'=>'int'],
'stats_cdf_f' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_gamma' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_laplace' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_logistic' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_negative_binomial' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_noncentral_chisquare' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_noncentral_f' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'par4'=>'float', 'which'=>'int'],
'stats_cdf_noncentral_t' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_normal' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_poisson' => ['float', 'par1'=>'float', 'par2'=>'float', 'which'=>'int'],
'stats_cdf_t' => ['float', 'par1'=>'float', 'par2'=>'float', 'which'=>'int'],
'stats_cdf_uniform' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_cdf_weibull' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_covariance' => ['float', 'a'=>'array', 'b'=>'array'],
'stats_den_uniform' => ['float', 'x'=>'float', 'a'=>'float', 'b'=>'float'],
'stats_dens_beta' => ['float', 'x'=>'float', 'a'=>'float', 'b'=>'float'],
'stats_dens_cauchy' => ['float', 'x'=>'float', 'ave'=>'float', 'stdev'=>'float'],
'stats_dens_chisquare' => ['float', 'x'=>'float', 'dfr'=>'float'],
'stats_dens_exponential' => ['float', 'x'=>'float', 'scale'=>'float'],
'stats_dens_f' => ['float', 'x'=>'float', 'dfr1'=>'float', 'dfr2'=>'float'],
'stats_dens_gamma' => ['float', 'x'=>'float', 'shape'=>'float', 'scale'=>'float'],
'stats_dens_laplace' => ['float', 'x'=>'float', 'ave'=>'float', 'stdev'=>'float'],
'stats_dens_logistic' => ['float', 'x'=>'float', 'ave'=>'float', 'stdev'=>'float'],
'stats_dens_negative_binomial' => ['float', 'x'=>'float', 'n'=>'float', 'pi'=>'float'],
'stats_dens_normal' => ['float', 'x'=>'float', 'ave'=>'float', 'stdev'=>'float'],
'stats_dens_pmf_binomial' => ['float', 'x'=>'float', 'n'=>'float', 'pi'=>'float'],
'stats_dens_pmf_hypergeometric' => ['float', 'n1'=>'float', 'n2'=>'float', 'N1'=>'float', 'N2'=>'float'],
'stats_dens_pmf_negative_binomial' => ['float', 'x'=>'float', 'n'=>'float', 'pi'=>'float'],
'stats_dens_pmf_poisson' => ['float', 'x'=>'float', 'lb'=>'float'],
'stats_dens_t' => ['float', 'x'=>'float', 'dfr'=>'float'],
'stats_dens_uniform' => ['float', 'x'=>'float', 'a'=>'float', 'b'=>'float'],
'stats_dens_weibull' => ['float', 'x'=>'float', 'a'=>'float', 'b'=>'float'],
'stats_harmonic_mean' => ['float', 'a'=>'array'],
'stats_kurtosis' => ['float', 'a'=>'array'],
'stats_rand_gen_beta' => ['float', 'a'=>'float', 'b'=>'float'],
'stats_rand_gen_chisquare' => ['float', 'df'=>'float'],
'stats_rand_gen_exponential' => ['float', 'av'=>'float'],
'stats_rand_gen_f' => ['float', 'dfn'=>'float', 'dfd'=>'float'],
'stats_rand_gen_funiform' => ['float', 'low'=>'float', 'high'=>'float'],
'stats_rand_gen_gamma' => ['float', 'a'=>'float', 'r'=>'float'],
'stats_rand_gen_ibinomial' => ['int', 'n'=>'int', 'pp'=>'float'],
'stats_rand_gen_ibinomial_negative' => ['int', 'n'=>'int', 'p'=>'float'],
'stats_rand_gen_int' => ['int'],
'stats_rand_gen_ipoisson' => ['int', 'mu'=>'float'],
'stats_rand_gen_iuniform' => ['int', 'low'=>'int', 'high'=>'int'],
'stats_rand_gen_noncenral_chisquare' => ['float', 'df'=>'float', 'xnonc'=>'float'],
'stats_rand_gen_noncentral_chisquare' => ['float', 'df'=>'float', 'xnonc'=>'float'],
'stats_rand_gen_noncentral_f' => ['float', 'dfn'=>'float', 'dfd'=>'float', 'xnonc'=>'float'],
'stats_rand_gen_noncentral_t' => ['float', 'df'=>'float', 'xnonc'=>'float'],
'stats_rand_gen_normal' => ['float', 'av'=>'float', 'sd'=>'float'],
'stats_rand_gen_t' => ['float', 'df'=>'float'],
'stats_rand_get_seeds' => ['array'],
'stats_rand_phrase_to_seeds' => ['array', 'phrase'=>'string'],
'stats_rand_ranf' => ['float'],
'stats_rand_setall' => ['void', 'iseed1'=>'int', 'iseed2'=>'int'],
'stats_skew' => ['float', 'a'=>'array'],
'stats_standard_deviation' => ['float', 'a'=>'array', 'sample='=>'bool'],
'stats_stat_binomial_coef' => ['float', 'x'=>'int', 'n'=>'int'],
'stats_stat_correlation' => ['float', 'arr1'=>'array', 'arr2'=>'array'],
'stats_stat_factorial' => ['float', 'n'=>'int'],
'stats_stat_gennch' => ['float', 'n'=>'int'],
'stats_stat_independent_t' => ['float', 'arr1'=>'array', 'arr2'=>'array'],
'stats_stat_innerproduct' => ['float', 'arr1'=>'array', 'arr2'=>'array'],
'stats_stat_noncentral_t' => ['float', 'par1'=>'float', 'par2'=>'float', 'par3'=>'float', 'which'=>'int'],
'stats_stat_paired_t' => ['float', 'arr1'=>'array', 'arr2'=>'array'],
'stats_stat_percentile' => ['float', 'df'=>'float', 'xnonc'=>'float'],
'stats_stat_powersum' => ['float', 'arr'=>'array', 'power'=>'float'],
'stats_variance' => ['float', 'a'=>'array', 'sample='=>'bool'],
'Stomp::__construct' => ['void', 'broker='=>'string', 'username='=>'string', 'password='=>'string', 'headers='=>'array'],
'Stomp::__destruct' => ['bool', 'link'=>''],
'Stomp::abort' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''],
'Stomp::ack' => ['bool', 'msg'=>'', 'headers='=>'array', 'link='=>''],
'Stomp::begin' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''],
'Stomp::commit' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''],
'Stomp::error' => ['string', 'link'=>''],
'Stomp::getReadTimeout' => ['array', 'link'=>''],
'Stomp::getSessionId' => ['string', 'link'=>''],
'Stomp::hasFrame' => ['bool', 'link'=>''],
'Stomp::readFrame' => ['array', 'class_name='=>'string', 'link='=>''],
'Stomp::send' => ['bool', 'destination'=>'string', 'msg'=>'', 'headers='=>'array', 'link='=>''],
'Stomp::setReadTimeout' => ['', 'seconds'=>'int', 'microseconds='=>'int', 'link='=>''],
'Stomp::subscribe' => ['bool', 'destination'=>'string', 'headers='=>'array', 'link='=>''],
'Stomp::unsubscribe' => ['bool', 'destination'=>'string', 'headers='=>'array', 'link='=>''],
'stomp_abort' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''],
'stomp_ack' => ['bool', 'msg'=>'', 'headers='=>'array', 'link='=>''],
'stomp_begin' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''],
'stomp_close' => ['bool', 'link'=>''],
'stomp_commit' => ['bool', 'transaction_id'=>'string', 'headers='=>'array', 'link='=>''],
'stomp_connect' => ['resource', 'broker='=>'string', 'username='=>'string', 'password='=>'string', 'headers='=>'array'],
'stomp_connect_error' => ['string'],
'stomp_error' => ['string', 'link'=>''],
'stomp_get_read_timeout' => ['array', 'link'=>''],
'stomp_get_session_id' => ['string', 'link'=>''],
'stomp_has_frame' => ['bool', 'link'=>''],
'stomp_read_frame' => ['array', 'class_name='=>'string', 'link='=>''],
'stomp_send' => ['bool', 'destination'=>'string', 'msg'=>'', 'headers='=>'array', 'link='=>''],
'stomp_set_read_timeout' => ['', 'seconds'=>'int', 'microseconds='=>'int', 'link='=>''],
'stomp_subscribe' => ['bool', 'destination'=>'string', 'headers='=>'array', 'link='=>''],
'stomp_unsubscribe' => ['bool', 'destination'=>'string', 'headers='=>'array', 'link='=>''],
'stomp_version' => ['string'],
'StompException::getDetails' => ['string'],
'StompFrame::__construct' => ['void', 'command='=>'string', 'headers='=>'array', 'body='=>'string'],
'str_getcsv' => ['array', 'input'=>'string', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'],
'str_ireplace' => ['mixed', 'search'=>'mixed', 'replace'=>'mixed', 'subject'=>'mixed', '&w_replace_count='=>'int'],
'str_pad' => ['string', 'input'=>'string', 'pad_length'=>'int', 'pad_string='=>'string', 'pad_type='=>'int'],
'str_repeat' => ['string', 'input'=>'string', 'mult'=>'int'],
'str_replace' => ['string|array', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'],
'str_rot13' => ['string', 'str'=>'string'],
'str_shuffle' => ['string', 'str'=>'string'],
'str_split' => ['array<int,string>', 'str'=>'string', 'split_length='=>'int'],
'str_word_count' => ['mixed', 'string'=>'string', 'format='=>'int', 'charlist='=>'string'],
'strcasecmp' => ['int', 'str1'=>'string', 'str2'=>'string'],
'strchr' => ['string', 'haystack'=>'string', 'needle'=>'string'],
'strcmp' => ['int', 'str1'=>'string', 'str2'=>'string'],
'strcoll' => ['int', 'str1'=>'string', 'str2'=>'string'],
'strcspn' => ['int', 'str'=>'string', 'mask'=>'string', 'start='=>'int', 'len='=>'int'],
'stream_bucket_append' => ['void', 'brigade'=>'resource', 'bucket'=>'object'],
'stream_bucket_make_writeable' => ['object', 'brigade'=>'resource'],
'stream_bucket_new' => ['resource', 'stream'=>'resource', 'buffer'=>'string'],
'stream_bucket_prepend' => ['void', 'brigade'=>'resource', 'bucket'=>'object'],
'stream_context_create' => ['resource', 'options='=>'array', 'params='=>'array'],
'stream_context_get_default' => ['resource', 'options='=>'array'],
'stream_context_get_options' => ['array', 'context'=>'resource'],
'stream_context_get_params' => ['array', 'context'=>'resource'],
'stream_context_set_default' => ['resource', 'options'=>'array'],
'stream_context_set_option' => ['bool', 'context'=>'', 'wrappername'=>'string', 'optionname'=>'string', 'value'=>''],
'stream_context_set_option\'1' => ['bool', 'context'=>'', 'options'=>'array'],
'stream_context_set_params' => ['bool', 'context'=>'resource', 'options'=>'array'],
'stream_copy_to_stream' => ['int|false', 'source'=>'resource', 'dest'=>'resource', 'maxlen='=>'int', 'pos='=>'int'],
'stream_encoding' => ['bool', 'stream'=>'resource', 'encoding='=>'string'],
'stream_filter_append' => ['resource', 'stream'=>'resource', 'filtername'=>'string', 'read_write='=>'int', 'filterparams='=>'array'],
'stream_filter_prepend' => ['resource', 'stream'=>'resource', 'filtername'=>'string', 'read_write='=>'int', 'filterparams='=>'array'],
'stream_filter_register' => ['bool', 'filtername'=>'string', 'classname'=>'string'],
'stream_filter_remove' => ['bool', 'stream_filter'=>'resource'],
'stream_get_contents' => ['string', 'source'=>'resource', 'maxlen='=>'int', 'offset='=>'int'],
'stream_get_filters' => ['array'],
'stream_get_line' => ['string', 'stream'=>'resource', 'maxlen'=>'int', 'ending='=>'string'],
'stream_get_meta_data' => ['array', 'fp'=>'resource'],
'stream_get_transports' => ['array'],
'stream_get_wrappers' => ['array'],
'stream_is_local' => ['bool', 'stream'=>'resource'],
'stream_isatty' => ['bool', 'stream'=>'resource'],
'stream_notification_callback' => ['callback', 'notification_code'=>'int', 'severity'=>'int', 'message'=>'string', 'message_code'=>'int', 'bytes_transferred'=>'int', 'bytes_max'=>'int'],
'stream_resolve_include_path' => ['string', 'filename'=>'string'],
'stream_select' => ['int|false', '&rw_read_streams'=>'resource[]', '&rw_write_streams'=>'?resource[]', '&rw_except_streams'=>'?resource[]', 'tv_sec'=>'?int', 'tv_usec='=>'?int'],
'stream_set_blocking' => ['bool', 'socket'=>'resource', 'mode'=>'bool'],
'stream_set_chunk_size' => ['int', 'fp'=>'resource', 'chunk_size'=>'int'],
'stream_set_read_buffer' => ['int', 'fp'=>'resource', 'buffer'=>'int'],
'stream_set_timeout' => ['bool', 'stream'=>'resource', 'seconds'=>'int', 'microseconds='=>'int'],
'stream_set_write_buffer' => ['int', 'fp'=>'resource', 'buffer'=>'int'],
'stream_socket_accept' => ['resource', 'serverstream'=>'resource', 'timeout='=>'float', '&w_peername='=>'string'],
'stream_socket_client' => ['resource|false', 'remoteaddress'=>'string', '&w_errcode='=>'int', '&w_errstring='=>'string', 'timeout='=>'float', 'flags='=>'int', 'context='=>'resource'],
'stream_socket_enable_crypto' => ['int', 'stream'=>'resource', 'enable'=>'bool', 'cryptokind='=>'int', 'sessionstream='=>'resource'],
'stream_socket_get_name' => ['string', 'stream'=>'resource', 'want_peer'=>'bool'],
'stream_socket_pair' => ['resource[]', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int'],
'stream_socket_recvfrom' => ['string', 'stream'=>'resource', 'amount'=>'int', 'flags='=>'int', '&w_remote_addr='=>'string'],
'stream_socket_sendto' => ['int', 'stream'=>'resource', 'data'=>'string', 'flags='=>'int', 'target_addr='=>'string'],
'stream_socket_server' => ['resource', 'localaddress'=>'string', '&w_errcode='=>'int', '&w_errstring='=>'string', 'flags='=>'int', 'context='=>'resource'],
'stream_socket_shutdown' => ['bool', 'stream'=>'resource', 'how'=>'int'],
'stream_supports_lock' => ['bool', 'stream'=>'resource'],
'stream_wrapper_register' => ['bool', 'protocol'=>'string', 'classname'=>'string', 'flags='=>'int'],
'stream_wrapper_restore' => ['bool', 'protocol'=>'string'],
'stream_wrapper_unregister' => ['bool', 'protocol'=>'string'],
'streamWrapper::__construct' => ['void'],
'streamWrapper::__destruct' => [''],
'streamWrapper::dir_closedir' => ['bool'],
'streamWrapper::dir_opendir' => ['bool', 'path'=>'string', 'options'=>'int'],
'streamWrapper::dir_readdir' => ['string'],
'streamWrapper::dir_rewinddir' => ['bool'],
'streamWrapper::mkdir' => ['bool', 'path'=>'string', 'mode'=>'int', 'options'=>'int'],
'streamWrapper::rename' => ['bool', 'path_from'=>'string', 'path_to'=>'string'],
'streamWrapper::rmdir' => ['bool', 'path'=>'string', 'options'=>'int'],
'streamWrapper::stream_cast' => ['resource', 'cast_as'=>'int'],
'streamWrapper::stream_close' => ['void'],
'streamWrapper::stream_eof' => ['bool'],
'streamWrapper::stream_flush' => ['bool'],
'streamWrapper::stream_lock' => ['bool', 'operation'=>'mode'],
'streamWrapper::stream_metadata' => ['bool', 'path'=>'string', 'option'=>'int', 'value'=>'mixed'],
'streamWrapper::stream_open' => ['bool', 'path'=>'string', 'mode'=>'string', 'options'=>'int', 'opened_path'=>'string'],
'streamWrapper::stream_read' => ['string', 'count'=>'int'],
'streamWrapper::stream_seek' => ['bool', 'offset'=>'int', 'whence'=>'int'],
'streamWrapper::stream_set_option' => ['bool', 'option'=>'int', 'arg1'=>'int', 'arg2'=>'int'],
'streamWrapper::stream_stat' => ['array'],
'streamWrapper::stream_tell' => ['int'],
'streamWrapper::stream_truncate' => ['bool', 'new_size'=>'int'],
'streamWrapper::stream_write' => ['int', 'data'=>'string'],
'streamWrapper::unlink' => ['bool', 'path'=>'string'],
'streamWrapper::url_stat' => ['array', 'path'=>'string', 'flags'=>'int'],
'strftime' => ['string', 'format'=>'string', 'timestamp='=>'int'],
'strip_tags' => ['string', 'str'=>'string', 'allowable_tags='=>'string'],
'stripcslashes' => ['string', 'str'=>'string'],
'stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'stripslashes' => ['string', 'str'=>'string'],
'stristr' => ['string', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool'],
'strlen' => ['int', 'string'=>'string'],
'strnatcasecmp' => ['int', 's1'=>'string', 's2'=>'string'],
'strnatcmp' => ['int', 's1'=>'string', 's2'=>'string'],
'strncasecmp' => ['int', 'str1'=>'string', 'str2'=>'string', 'len'=>'int'],
'strncmp' => ['int', 'str1'=>'string', 'str2'=>'string', 'len'=>'int'],
'strpbrk' => ['string|false', 'haystack'=>'string', 'char_list'=>'string'],
'strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'strptime' => ['array|false', 'datestr'=>'string', 'format'=>'string'],
'strrchr' => ['string', 'haystack'=>'string', 'needle'=>'string'],
'strrev' => ['string', 'str'=>'string'],
'strripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'strspn' => ['int', 'str'=>'string', 'mask'=>'string', 'start='=>'int', 'len='=>'int'],
'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'mixed', 'part='=>'bool'],
'strtok' => ['string|false', 'str'=>'string', 'token'=>'string'],
'strtok\'1' => ['string|false', 'token'=>'string'],
'strtolower' => ['string', 'str'=>'string'],
'strtotime' => ['int|false', 'time'=>'string', 'now='=>'int'],
'strtoupper' => ['string', 'str'=>'string'],
'strtr' => ['string', 'str'=>'string', 'from'=>'string', 'to'=>'string'],
'strtr\'1' => ['string', 'str'=>'string', 'replace_pairs'=>'array'],
'strval' => ['string', 'var'=>'mixed'],
'substr' => ['string', 'str'=>'string', 'start'=>'int', 'length='=>'int'],
'substr_compare' => ['int', 'main_str'=>'string', 'str'=>'string', 'offset'=>'int', 'length='=>'int', 'case_sensitivity='=>'bool'],
'substr_count' => ['int', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'length='=>'int'],
'substr_replace' => ['mixed', 'str'=>'mixed', 'repl'=>'mixed', 'start'=>'mixed', 'length='=>'mixed'],
'suhosin_encrypt_cookie' => ['string', 'name'=>'string', 'value'=>'string'],
'suhosin_get_raw_cookies' => ['array'],
'SVM::__construct' => ['void'],
'svm::crossvalidate' => ['float', 'problem'=>'array', 'number_of_folds'=>'int'],
'SVM::getOptions' => ['array'],
'SVM::setOptions' => ['bool', 'params'=>'array'],
'svm::train' => ['SVMModel', 'problem'=>'array', 'weights='=>'array'],
'SVMModel::__construct' => ['void', 'filename='=>'string'],
'SVMModel::checkProbabilityModel' => ['bool'],
'SVMModel::getLabels' => ['array'],
'SVMModel::getNrClass' => ['int'],
'SVMModel::getSvmType' => ['int'],
'SVMModel::getSvrProbability' => ['float'],
'SVMModel::load' => ['bool', 'filename'=>'string'],
'SVMModel::predict' => ['float', 'data'=>'array'],
'SVMModel::predict_probability' => ['float', 'data'=>'array'],
'SVMModel::save' => ['bool', 'filename'=>'string'],
'svn_add' => ['bool', 'path'=>'string', 'recursive='=>'bool', 'force='=>'bool'],
'svn_auth_get_parameter' => ['string', 'key'=>'string'],
'svn_auth_set_parameter' => ['void', 'key'=>'string', 'value'=>'string'],
'svn_blame' => ['array', 'repository_url'=>'string', 'revision_no='=>'int'],
'svn_cat' => ['string', 'repos_url'=>'string', 'revision_no='=>'int'],
'svn_checkout' => ['bool', 'repos'=>'string', 'targetpath'=>'string', 'revision='=>'int', 'flags='=>'int'],
'svn_cleanup' => ['bool', 'workingdir'=>'string'],
'svn_client_version' => ['string'],
'svn_commit' => ['array', 'log'=>'string', 'targets'=>'array', 'dontrecurse='=>'bool'],
'svn_delete' => ['bool', 'path'=>'string', 'force='=>'bool'],
'svn_diff' => ['array', 'path1'=>'string', 'rev1'=>'int', 'path2'=>'string', 'rev2'=>'int'],
'svn_export' => ['bool', 'frompath'=>'string', 'topath'=>'string', 'working_copy='=>'bool', 'revision_no='=>'int'],
'svn_fs_abort_txn' => ['bool', 'txn'=>'resource'],
'svn_fs_apply_text' => ['resource', 'root'=>'resource', 'path'=>'string'],
'svn_fs_begin_txn2' => ['resource', 'repos'=>'resource', 'rev'=>'int'],
'svn_fs_change_node_prop' => ['bool', 'root'=>'resource', 'path'=>'string', 'name'=>'string', 'value'=>'string'],
'svn_fs_check_path' => ['int', 'fsroot'=>'resource', 'path'=>'string'],
'svn_fs_contents_changed' => ['bool', 'root1'=>'resource', 'path1'=>'string', 'root2'=>'resource', 'path2'=>'string'],
'svn_fs_copy' => ['bool', 'from_root'=>'resource', 'from_path'=>'string', 'to_root'=>'resource', 'to_path'=>'string'],
'svn_fs_delete' => ['bool', 'root'=>'resource', 'path'=>'string'],
'svn_fs_dir_entries' => ['array', 'fsroot'=>'resource', 'path'=>'string'],
'svn_fs_file_contents' => ['resource', 'fsroot'=>'resource', 'path'=>'string'],
'svn_fs_file_length' => ['int', 'fsroot'=>'resource', 'path'=>'string'],
'svn_fs_is_dir' => ['bool', 'root'=>'resource', 'path'=>'string'],
'svn_fs_is_file' => ['bool', 'root'=>'resource', 'path'=>'string'],
'svn_fs_make_dir' => ['bool', 'root'=>'resource', 'path'=>'string'],
'svn_fs_make_file' => ['bool', 'root'=>'resource', 'path'=>'string'],
'svn_fs_node_created_rev' => ['int', 'fsroot'=>'resource', 'path'=>'string'],
'svn_fs_node_prop' => ['string', 'fsroot'=>'resource', 'path'=>'string', 'propname'=>'string'],
'svn_fs_props_changed' => ['bool', 'root1'=>'resource', 'path1'=>'string', 'root2'=>'resource', 'path2'=>'string'],
'svn_fs_revision_prop' => ['string', 'fs'=>'resource', 'revnum'=>'int', 'propname'=>'string'],
'svn_fs_revision_root' => ['resource', 'fs'=>'resource', 'revnum'=>'int'],
'svn_fs_txn_root' => ['resource', 'txn'=>'resource'],
'svn_fs_youngest_rev' => ['int', 'fs'=>'resource'],
'svn_import' => ['bool', 'path'=>'string', 'url'=>'string', 'nonrecursive'=>'bool'],
'svn_log' => ['array', 'repos_url'=>'string', 'start_revision='=>'int', 'end_revision='=>'int', 'limit='=>'int', 'flags='=>'int'],
'svn_ls' => ['array', 'repos_url'=>'string', 'revision_no='=>'int', 'recurse='=>'bool', 'peg='=>'bool'],
'svn_mkdir' => ['bool', 'path'=>'string', 'log_message='=>'string'],
'svn_move' => ['mixed', 'src_path'=>'string', 'dst_path'=>'string', 'force='=>'bool|false'],
'svn_propget' => ['mixed', 'path'=>'string', 'property_name'=>'string', 'recurse='=>'bool|false', 'revision'=>'int'],
'svn_proplist' => ['mixed', 'path'=>'string', 'recurse='=>'bool|false', 'revision'=>'int'],
'svn_repos_create' => ['resource', 'path'=>'string', 'config='=>'array', 'fsconfig='=>'array'],
'svn_repos_fs' => ['resource', 'repos'=>'resource'],
'svn_repos_fs_begin_txn_for_commit' => ['resource', 'repos'=>'resource', 'rev'=>'int', 'author'=>'string', 'log_msg'=>'string'],
'svn_repos_fs_commit_txn' => ['int', 'txn'=>'resource'],
'svn_repos_hotcopy' => ['bool', 'repospath'=>'string', 'destpath'=>'string', 'cleanlogs'=>'bool'],
'svn_repos_open' => ['resource', 'path'=>'string'],
'svn_repos_recover' => ['bool', 'path'=>'string'],
'svn_revert' => ['bool', 'path'=>'string', 'recursive='=>'bool'],
'svn_status' => ['array', 'path'=>'string', 'flags='=>'int'],
'svn_update' => ['int', 'path'=>'string', 'revno='=>'int', 'recurse='=>'bool'],
'swf_actiongeturl' => ['', 'url'=>'string', 'target'=>'string'],
'swf_actiongotoframe' => ['', 'framenumber'=>'int'],
'swf_actiongotolabel' => ['', 'label'=>'string'],
'swf_actionnextframe' => [''],
'swf_actionplay' => [''],
'swf_actionprevframe' => [''],
'swf_actionsettarget' => ['', 'target'=>'string'],
'swf_actionstop' => [''],
'swf_actiontogglequality' => [''],
'swf_actionwaitforframe' => ['', 'framenumber'=>'int', 'skipcount'=>'int'],
'swf_addbuttonrecord' => ['', 'states'=>'int', 'shapeid'=>'int', 'depth'=>'int'],
'swf_addcolor' => ['', 'r'=>'float', 'g'=>'float', 'b'=>'float', 'a'=>'float'],
'swf_closefile' => ['', 'return_file='=>'int'],
'swf_definebitmap' => ['', 'objid'=>'int', 'image_name'=>'string'],
'swf_definefont' => ['', 'fontid'=>'int', 'fontname'=>'string'],
'swf_defineline' => ['', 'objid'=>'int', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'width'=>'float'],
'swf_definepoly' => ['', 'objid'=>'int', 'coords'=>'array', 'npoints'=>'int', 'width'=>'float'],
'swf_definerect' => ['', 'objid'=>'int', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'width'=>'float'],
'swf_definetext' => ['', 'objid'=>'int', 'str'=>'string', 'docenter'=>'int'],
'swf_endbutton' => [''],
'swf_enddoaction' => [''],
'swf_endshape' => [''],
'swf_endsymbol' => [''],
'swf_fontsize' => ['', 'size'=>'float'],
'swf_fontslant' => ['', 'slant'=>'float'],
'swf_fonttracking' => ['', 'tracking'=>'float'],
'swf_getbitmapinfo' => ['array', 'bitmapid'=>'int'],
'swf_getfontinfo' => ['array'],
'swf_getframe' => ['int'],
'swf_labelframe' => ['', 'name'=>'string'],
'swf_lookat' => ['', 'view_x'=>'float', 'view_y'=>'float', 'view_z'=>'float', 'reference_x'=>'float', 'reference_y'=>'float', 'reference_z'=>'float', 'twist'=>'float'],
'swf_modifyobject' => ['', 'depth'=>'int', 'how'=>'int'],
'swf_mulcolor' => ['', 'r'=>'float', 'g'=>'float', 'b'=>'float', 'a'=>'float'],
'swf_nextid' => ['int'],
'swf_oncondition' => ['', 'transition'=>'int'],
'swf_openfile' => ['', 'filename'=>'string', 'width'=>'float', 'height'=>'float', 'framerate'=>'float', 'r'=>'float', 'g'=>'float', 'b'=>'float'],
'swf_ortho' => ['', 'xmin'=>'float', 'xmax'=>'float', 'ymin'=>'float', 'ymax'=>'float', 'zmin'=>'float', 'zmax'=>'float'],
'swf_ortho2' => ['', 'xmin'=>'float', 'xmax'=>'float', 'ymin'=>'float', 'ymax'=>'float'],
'swf_perspective' => ['', 'fovy'=>'float', 'aspect'=>'float', 'near'=>'float', 'far'=>'float'],
'swf_placeobject' => ['', 'objid'=>'int', 'depth'=>'int'],
'swf_polarview' => ['', 'dist'=>'float', 'azimuth'=>'float', 'incidence'=>'float', 'twist'=>'float'],
'swf_popmatrix' => [''],
'swf_posround' => ['', 'round'=>'int'],
'swf_pushmatrix' => [''],
'swf_removeobject' => ['', 'depth'=>'int'],
'swf_rotate' => ['', 'angle'=>'float', 'axis'=>'string'],
'swf_scale' => ['', 'x'=>'float', 'y'=>'float', 'z'=>'float'],
'swf_setfont' => ['', 'fontid'=>'int'],
'swf_setframe' => ['', 'framenumber'=>'int'],
'swf_shapearc' => ['', 'x'=>'float', 'y'=>'float', 'r'=>'float', 'ang1'=>'float', 'ang2'=>'float'],
'swf_shapecurveto' => ['', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float'],
'swf_shapecurveto3' => ['', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x3'=>'float', 'y3'=>'float'],
'swf_shapefillbitmapclip' => ['', 'bitmapid'=>'int'],
'swf_shapefillbitmaptile' => ['', 'bitmapid'=>'int'],
'swf_shapefilloff' => [''],
'swf_shapefillsolid' => ['', 'r'=>'float', 'g'=>'float', 'b'=>'float', 'a'=>'float'],
'swf_shapelinesolid' => ['', 'r'=>'float', 'g'=>'float', 'b'=>'float', 'a'=>'float', 'width'=>'float'],
'swf_shapelineto' => ['', 'x'=>'float', 'y'=>'float'],
'swf_shapemoveto' => ['', 'x'=>'float', 'y'=>'float'],
'swf_showframe' => [''],
'swf_startbutton' => ['', 'objid'=>'int', 'type'=>'int'],
'swf_startdoaction' => [''],
'swf_startshape' => ['', 'objid'=>'int'],
'swf_startsymbol' => ['', 'objid'=>'int'],
'swf_textwidth' => ['float', 'str'=>'string'],
'swf_translate' => ['', 'x'=>'float', 'y'=>'float', 'z'=>'float'],
'swf_viewport' => ['', 'xmin'=>'float', 'xmax'=>'float', 'ymin'=>'float', 'ymax'=>'float'],
'SWFAction::__construct' => ['void', 'script'=>'string'],
'SWFBitmap::__construct' => ['void', 'file'=>'', 'alphafile='=>''],
'SWFBitmap::getHeight' => ['float'],
'SWFBitmap::getWidth' => ['float'],
'SWFButton::__construct' => ['void'],
'SWFButton::addAction' => ['void', 'action'=>'swfaction', 'flags'=>'int'],
'SWFButton::addASound' => ['SWFSoundInstance', 'sound'=>'swfsound', 'flags'=>'int'],
'SWFButton::addShape' => ['void', 'shape'=>'swfshape', 'flags'=>'int'],
'SWFButton::setAction' => ['void', 'action'=>'swfaction'],
'SWFButton::setDown' => ['void', 'shape'=>'swfshape'],
'SWFButton::setHit' => ['void', 'shape'=>'swfshape'],
'SWFButton::setMenu' => ['void', 'flag'=>'int'],
'SWFButton::setOver' => ['void', 'shape'=>'swfshape'],
'SWFButton::setUp' => ['void', 'shape'=>'swfshape'],
'SWFDisplayItem::addAction' => ['void', 'action'=>'swfaction', 'flags'=>'int'],
'SWFDisplayItem::addColor' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'],
'SWFDisplayItem::endMask' => ['void'],
'SWFDisplayItem::getRot' => ['float'],
'SWFDisplayItem::getX' => ['float'],
'SWFDisplayItem::getXScale' => ['float'],
'SWFDisplayItem::getXSkew' => ['float'],
'SWFDisplayItem::getY' => ['float'],
'SWFDisplayItem::getYScale' => ['float'],
'SWFDisplayItem::getYSkew' => ['float'],
'SWFDisplayItem::move' => ['void', 'dx'=>'float', 'dy'=>'float'],
'SWFDisplayItem::moveTo' => ['void', 'x'=>'float', 'y'=>'float'],
'SWFDisplayItem::multColor' => ['void', 'red'=>'float', 'green'=>'float', 'blue'=>'float', 'a='=>'float'],
'SWFDisplayItem::remove' => ['void'],
'SWFDisplayItem::rotate' => ['void', 'angle'=>'float'],
'SWFDisplayItem::rotateTo' => ['void', 'angle'=>'float'],
'SWFDisplayItem::scale' => ['void', 'dx'=>'float', 'dy'=>'float'],
'SWFDisplayItem::scaleTo' => ['void', 'x'=>'float', 'y='=>'float'],
'SWFDisplayItem::setDepth' => ['void', 'depth'=>'int'],
'SWFDisplayItem::setMaskLevel' => ['void', 'level'=>'int'],
'SWFDisplayItem::setMatrix' => ['void', 'a'=>'float', 'b'=>'float', 'c'=>'float', 'd'=>'float', 'x'=>'float', 'y'=>'float'],
'SWFDisplayItem::setName' => ['void', 'name'=>'string'],
'SWFDisplayItem::setRatio' => ['void', 'ratio'=>'float'],
'SWFDisplayItem::skewX' => ['void', 'ddegrees'=>'float'],
'SWFDisplayItem::skewXTo' => ['void', 'degrees'=>'float'],
'SWFDisplayItem::skewY' => ['void', 'ddegrees'=>'float'],
'SWFDisplayItem::skewYTo' => ['void', 'degrees'=>'float'],
'SWFFill::moveTo' => ['void', 'x'=>'float', 'y'=>'float'],
'SWFFill::rotateTo' => ['void', 'angle'=>'float'],
'SWFFill::scaleTo' => ['void', 'x'=>'float', 'y='=>'float'],
'SWFFill::skewXTo' => ['void', 'x'=>'float'],
'SWFFill::skewYTo' => ['void', 'y'=>'float'],
'SWFFont::__construct' => ['void', 'filename'=>'string'],
'SWFFont::getAscent' => ['float'],
'SWFFont::getDescent' => ['float'],
'SWFFont::getLeading' => ['float'],
'SWFFont::getShape' => ['string', 'code'=>'int'],
'SWFFont::getUTF8Width' => ['float', 'string'=>'string'],
'SWFFont::getWidth' => ['float', 'string'=>'string'],
'SWFFontChar::addChars' => ['void', 'char'=>'string'],
'SWFFontChar::addUTF8Chars' => ['void', 'char'=>'string'],
'SWFGradient::__construct' => ['void'],
'SWFGradient::addEntry' => ['void', 'ratio'=>'float', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha='=>'int'],
'SWFMorph::__construct' => ['void'],
'SWFMorph::getShape1' => ['SWFShape'],
'SWFMorph::getShape2' => ['SWFShape'],
'SWFMovie::__construct' => ['void', 'version='=>'int'],
'SWFMovie::add' => ['mixed', 'instance'=>'object'],
'SWFMovie::addExport' => ['void', 'char'=>'swfcharacter', 'name'=>'string'],
'SWFMovie::addFont' => ['mixed', 'font'=>'swffont'],
'SWFMovie::importChar' => ['SWFSprite', 'libswf'=>'string', 'name'=>'string'],
'SWFMovie::importFont' => ['SWFFontChar', 'libswf'=>'string', 'name'=>'string'],
'SWFMovie::labelFrame' => ['void', 'label'=>'string'],
'SWFMovie::nextFrame' => ['void'],
'SWFMovie::output' => ['int', 'compression='=>'int'],
'SWFMovie::remove' => ['void', 'instance'=>'object'],
'SWFMovie::save' => ['int', 'filename'=>'string', 'compression='=>'int'],
'SWFMovie::saveToFile' => ['int', 'x'=>'resource', 'compression='=>'int'],
'SWFMovie::setbackground' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int'],
'SWFMovie::setDimension' => ['void', 'width'=>'float', 'height'=>'float'],
'SWFMovie::setFrames' => ['void', 'number'=>'int'],
'SWFMovie::setRate' => ['void', 'rate'=>'float'],
'SWFMovie::startSound' => ['SWFSoundInstance', 'sound'=>'swfsound'],
'SWFMovie::stopSound' => ['void', 'sound'=>'swfsound'],
'SWFMovie::streamMP3' => ['int', 'mp3file'=>'mixed', 'skip='=>'float'],
'SWFMovie::writeExports' => ['void'],
'SWFPrebuiltClip::__construct' => ['void', 'file'=>''],
'SWFShape::__construct' => ['void'],
'SWFShape::addFill' => ['SWFFill', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha='=>'int', 'bitmap='=>'swfbitmap', 'flags='=>'int', 'gradient='=>'swfgradient'],
'SWFShape::addFill\'1' => ['SWFFill', 'bitmap'=>'SWFBitmap', 'flags='=>'int'],
'SWFShape::addFill\'2' => ['SWFFill', 'gradient'=>'SWFGradient', 'flags='=>'int'],
'SWFShape::drawArc' => ['void', 'r'=>'float', 'startangle'=>'float', 'endangle'=>'float'],
'SWFShape::drawCircle' => ['void', 'r'=>'float'],
'SWFShape::drawCubic' => ['int', 'bx'=>'float', 'by'=>'float', 'cx'=>'float', 'cy'=>'float', 'dx'=>'float', 'dy'=>'float'],
'SWFShape::drawCubicTo' => ['int', 'bx'=>'float', 'by'=>'float', 'cx'=>'float', 'cy'=>'float', 'dx'=>'float', 'dy'=>'float'],
'SWFShape::drawCurve' => ['int', 'controldx'=>'float', 'controldy'=>'float', 'anchordx'=>'float', 'anchordy'=>'float', 'targetdx='=>'float', 'targetdy='=>'float'],
'SWFShape::drawCurveTo' => ['int', 'controlx'=>'float', 'controly'=>'float', 'anchorx'=>'float', 'anchory'=>'float', 'targetx='=>'float', 'targety='=>'float'],
'SWFShape::drawGlyph' => ['void', 'font'=>'swffont', 'character'=>'string', 'size='=>'int'],
'SWFShape::drawLine' => ['void', 'dx'=>'float', 'dy'=>'float'],
'SWFShape::drawLineTo' => ['void', 'x'=>'float', 'y'=>'float'],
'SWFShape::movePen' => ['void', 'dx'=>'float', 'dy'=>'float'],
'SWFShape::movePenTo' => ['void', 'x'=>'float', 'y'=>'float'],
'SWFShape::setLeftFill' => ['', 'fill'=>'swfgradient', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'],
'SWFShape::setLine' => ['', 'shape'=>'swfshape', 'width'=>'int', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'],
'SWFShape::setRightFill' => ['', 'fill'=>'swfgradient', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'],
'SWFSound' => ['SWFSound', 'filename'=>'string', 'flags='=>'int'],
'SWFSound::__construct' => ['void', 'filename'=>'string', 'flags='=>'int'],
'SWFSoundInstance::loopCount' => ['void', 'point'=>'int'],
'SWFSoundInstance::loopInPoint' => ['void', 'point'=>'int'],
'SWFSoundInstance::loopOutPoint' => ['void', 'point'=>'int'],
'SWFSoundInstance::noMultiple' => ['void'],
'SWFSprite::__construct' => ['void'],
'SWFSprite::add' => ['void', 'object'=>'object'],
'SWFSprite::labelFrame' => ['void', 'label'=>'string'],
'SWFSprite::nextFrame' => ['void'],
'SWFSprite::remove' => ['void', 'object'=>'object'],
'SWFSprite::setFrames' => ['void', 'number'=>'int'],
'SWFSprite::startSound' => ['SWFSoundInstance', 'sount'=>'swfsound'],
'SWFSprite::stopSound' => ['void', 'sount'=>'swfsound'],
'SWFText::__construct' => ['void'],
'SWFText::addString' => ['void', 'string'=>'string'],
'SWFText::addUTF8String' => ['void', 'text'=>'string'],
'SWFText::getAscent' => ['float'],
'SWFText::getDescent' => ['float'],
'SWFText::getLeading' => ['float'],
'SWFText::getUTF8Width' => ['float', 'string'=>'string'],
'SWFText::getWidth' => ['float', 'string'=>'string'],
'SWFText::moveTo' => ['void', 'x'=>'float', 'y'=>'float'],
'SWFText::setColor' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'],
'SWFText::setFont' => ['void', 'font'=>'swffont'],
'SWFText::setHeight' => ['void', 'height'=>'float'],
'SWFText::setSpacing' => ['void', 'spacing'=>'float'],
'SWFTextField::__construct' => ['void', 'flags='=>'int'],
'SWFTextField::addChars' => ['void', 'chars'=>'string'],
'SWFTextField::addString' => ['void', 'string'=>'string'],
'SWFTextField::align' => ['void', 'alignement'=>'int'],
'SWFTextField::setBounds' => ['void', 'width'=>'float', 'height'=>'float'],
'SWFTextField::setColor' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'],
'SWFTextField::setFont' => ['void', 'font'=>'swffont'],
'SWFTextField::setHeight' => ['void', 'height'=>'float'],
'SWFTextField::setIndentation' => ['void', 'width'=>'float'],
'SWFTextField::setLeftMargin' => ['void', 'width'=>'float'],
'SWFTextField::setLineSpacing' => ['void', 'height'=>'float'],
'SWFTextField::setMargins' => ['void', 'left'=>'float', 'right'=>'float'],
'SWFTextField::setName' => ['void', 'name'=>'string'],
'SWFTextField::setPadding' => ['void', 'padding'=>'float'],
'SWFTextField::setRightMargin' => ['void', 'width'=>'float'],
'SWFVideoStream::__construct' => ['void', 'file='=>'string'],
'SWFVideoStream::getNumFrames' => ['int'],
'SWFVideoStream::setDimension' => ['void', 'x'=>'int', 'y'=>'int'],
'Swish::__construct' => ['void', 'index_names'=>'string'],
'Swish::getMetaList' => ['array', 'index_name'=>'string'],
'Swish::getPropertyList' => ['array', 'index_name'=>'string'],
'Swish::prepare' => ['object', 'query='=>'string'],
'Swish::query' => ['object', 'query'=>'string'],
'SwishResult::getMetaList' => ['array'],
'SwishResult::stem' => ['array', 'word'=>'string'],
'SwishResults::getParsedWords' => ['array', 'index_name'=>'string'],
'SwishResults::getRemovedStopwords' => ['array', 'index_name'=>'string'],
'SwishResults::nextResult' => ['object'],
'SwishResults::seekResult' => ['int', 'position'=>'int'],
'SwishSearch::execute' => ['object', 'query='=>'string'],
'SwishSearch::resetLimit' => [''],
'SwishSearch::setLimit' => ['', 'property'=>'string', 'low'=>'string', 'high'=>'string'],
'SwishSearch::setPhraseDelimiter' => ['', 'delimiter'=>'string'],
'SwishSearch::setSort' => ['', 'sort'=>'string'],
'SwishSearch::setStructure' => ['', 'structure'=>'int'],
'swoole_async_dns_lookup' => ['bool', 'hostname'=>'string', 'callback'=>'callable'],
'swoole_async_read' => ['bool', 'filename'=>'string', 'callback'=>'callable', 'chunk_size='=>'int', 'offset='=>'int'],
'swoole_async_readfile' => ['bool', 'filename'=>'string', 'callback'=>'string'],
'swoole_async_set' => ['void', 'settings'=>'array'],
'swoole_async_write' => ['bool', 'filename'=>'string', 'content'=>'string', 'offset='=>'int', 'callback='=>'callable'],
'swoole_async_writefile' => ['bool', 'filename'=>'string', 'content'=>'string', 'callback='=>'string', 'flags='=>'int'],
'swoole_client_select' => ['int', 'read_array'=>'array', 'write_array'=>'array', 'error_array'=>'array', 'timeout='=>'float'],
'swoole_cpu_num' => ['int'],
'swoole_errno' => ['int'],
'swoole_event_add' => ['int', 'fd'=>'int', 'read_callback='=>'callable', 'write_callback='=>'callable', 'events='=>'int'],
'swoole_event_defer' => ['bool', 'callback'=>'callable'],
'swoole_event_del' => ['bool', 'fd'=>'int'],
'swoole_event_exit' => ['void'],
'swoole_event_set' => ['bool', 'fd'=>'int', 'read_callback='=>'callable', 'write_callback='=>'callable', 'events='=>'int'],
'swoole_event_wait' => ['void'],
'swoole_event_write' => ['bool', 'fd'=>'int', 'data'=>'string'],
'swoole_get_local_ip' => ['array'],
'swoole_last_error' => ['int'],
'swoole_load_module' => ['mixed', 'filename'=>'string'],
'swoole_select' => ['int', 'read_array'=>'array', 'write_array'=>'array', 'error_array'=>'array', 'timeout='=>'float'],
'swoole_set_process_name' => ['void', 'process_name'=>'string', 'size='=>'int'],
'swoole_strerror' => ['string', 'errno'=>'int', 'error_type='=>'int'],
'swoole_timer_after' => ['int', 'ms'=>'int', 'callback'=>'callable', 'param='=>'mixed'],
'swoole_timer_exists' => ['bool', 'timer_id'=>'int'],
'swoole_timer_tick' => ['int', 'ms'=>'int', 'callback'=>'callable', 'param='=>'mixed'],
'swoole_version' => ['string'],
'sybase_affected_rows' => ['int', 'link_identifier='=>'resource'],
'sybase_close' => ['bool', 'link_identifier='=>'resource'],
'sybase_connect' => ['resource', 'servername='=>'string', 'username='=>'string', 'password='=>'string', 'charset='=>'string', 'appname='=>'string', 'new='=>'bool'],
'sybase_data_seek' => ['bool', 'result_identifier'=>'resource', 'row_number'=>'int'],
'sybase_deadlock_retry_count' => ['void', 'retry_count'=>'int'],
'sybase_fetch_array' => ['array', 'result'=>'resource'],
'sybase_fetch_assoc' => ['array', 'result'=>'resource'],
'sybase_fetch_field' => ['object', 'result'=>'resource', 'field_offset='=>'int'],
'sybase_fetch_object' => ['object', 'result'=>'resource', 'object='=>'mixed'],
'sybase_fetch_row' => ['array', 'result'=>'resource'],
'sybase_field_seek' => ['bool', 'result'=>'resource', 'field_offset'=>'int'],
'sybase_free_result' => ['bool', 'result'=>'resource'],
'sybase_get_last_message' => ['string'],
'sybase_min_client_severity' => ['void', 'severity'=>'int'],
'sybase_min_error_severity' => ['void', 'severity'=>'int'],
'sybase_min_message_severity' => ['void', 'severity'=>'int'],
'sybase_min_server_severity' => ['void', 'severity'=>'int'],
'sybase_num_fields' => ['int', 'result'=>'resource'],
'sybase_num_rows' => ['int', 'result'=>'resource'],
'sybase_pconnect' => ['resource', 'servername='=>'string', 'username='=>'string', 'password='=>'string', 'charset='=>'string', 'appname='=>'string'],
'sybase_query' => ['mixed', 'query'=>'string', 'link_identifier='=>'resource'],
'sybase_result' => ['string', 'result'=>'resource', 'row'=>'int', 'field'=>'mixed'],
'sybase_select_db' => ['bool', 'database_name'=>'string', 'link_identifier='=>'resource'],
'sybase_set_message_handler' => ['bool', 'handler'=>'callable', 'connection='=>'resource'],
'sybase_unbuffered_query' => ['resource', 'query'=>'string', 'link_identifier'=>'resource', 'store_result='=>'bool'],
'symbolObj::__construct' => ['void', 'map'=>'MapObj', 'symbolname'=>'string'],
'symbolObj::free' => ['void'],
'symbolObj::getPatternArray' => ['array'],
'symbolObj::getPointsArray' => ['array'],
'symbolObj::ms_newSymbolObj' => ['int', 'map'=>'MapObj', 'symbolname'=>'string'],
'symbolObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'symbolObj::setImagePath' => ['int', 'filename'=>'string'],
'symbolObj::setPattern' => ['int', 'int'=>'array'],
'symbolObj::setPoints' => ['int', 'double'=>'array'],
'symlink' => ['bool', 'target'=>'string', 'link'=>'string'],
'SyncEvent::__construct' => ['void', 'name='=>'string', 'manual='=>'bool'],
'SyncEvent::fire' => ['bool'],
'SyncEvent::reset' => ['bool'],
'SyncEvent::wait' => ['bool', 'wait='=>'int'],
'SyncMutex::__construct' => ['void', 'name='=>'string'],
'SyncMutex::lock' => ['bool', 'wait='=>'int'],
'SyncMutex::unlock' => ['bool', 'all='=>'bool'],
'SyncReaderWriter::__construct' => ['void', 'name='=>'string', 'autounlock='=>'bool'],
'SyncReaderWriter::readlock' => ['bool', 'wait='=>'int'],
'SyncReaderWriter::readunlock' => ['bool'],
'SyncReaderWriter::writelock' => ['bool', 'wait='=>'int'],
'SyncReaderWriter::writeunlock' => ['bool'],
'SyncSemaphore::__construct' => ['void', 'name='=>'string', 'initialval='=>'int', 'autounlock='=>'bool'],
'SyncSemaphore::lock' => ['bool', 'wait='=>'int'],
'SyncSemaphore::unlock' => ['bool', '&w_prevcount='=>'int'],
'SyncSharedMemory::__construct' => ['void', 'name'=>'string', 'size'=>'int'],
'SyncSharedMemory::first' => ['bool'],
'SyncSharedMemory::read' => ['', 'start='=>'int', 'length='=>'int'],
'SyncSharedMemory::size' => ['bool'],
'SyncSharedMemory::write' => ['', 'string='=>'string', 'start='=>'int'],
'sys_get_temp_dir' => ['string'],
'sys_getloadavg' => ['array'],
'syslog' => ['bool', 'priority'=>'int', 'message'=>'string'],
'system' => ['string', 'command'=>'string', '&w_return_value='=>'int'],
'taint' => ['bool', '&rw_string'=>'string', '&...w_other_strings='=>'string'],
'tan' => ['float', 'number'=>'float'],
'tanh' => ['float', 'number'=>'float'],
'tcpwrap_check' => ['bool', 'daemon'=>'string', 'address'=>'string', 'user='=>'string', 'nodns='=>'bool'],
'tempnam' => ['string|false', 'dir'=>'string', 'prefix'=>'string'],
'textdomain' => ['string', 'domain'=>'string'],
'Thread::__construct' => ['void'],
'Thread::chunk' => ['array', 'size'=>'int', 'preserve'=>'bool'],
'Thread::count' => ['int'],
'Thread::detach' => ['void'],
'Thread::getCreatorId' => ['int'],
'Thread::getCurrentThread' => ['Thread'],
'Thread::getCurrentThreadId' => ['int'],
'Thread::getTerminationInfo' => ['array'],
'Thread::getThreadId' => ['int'],
'Thread::globally' => ['mixed'],
'Thread::isJoined' => ['bool'],
'Thread::isRunning' => ['bool'],
'Thread::isStarted' => ['bool'],
'Thread::isTerminated' => ['bool'],
'Thread::isWaiting' => ['bool'],
'Thread::join' => ['bool'],
'Thread::kill' => ['void'],
'Thread::lock' => ['bool'],
'Thread::merge' => ['bool', 'from'=>'', 'overwrite='=>'mixed'],
'Thread::notify' => ['bool'],
'Thread::offsetExists' => ['bool', 'offset'=>'mixed'],
'Thread::offsetGet' => ['mixed', 'offset'=>'mixed'],
'Thread::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'],
'Thread::offsetUnset' => ['void', 'offset'=>'mixed'],
'Thread::pop' => ['bool'],
'Thread::run' => ['void'],
'Thread::shift' => ['bool'],
'Thread::start' => ['bool', 'options='=>'int'],
'Thread::synchronized' => ['mixed', 'block'=>'Closure', '_='=>'mixed'],
'Thread::unlock' => ['bool'],
'Thread::wait' => ['bool', 'timeout='=>'int'],
'Threaded::__construct' => ['void'],
'Threaded::chunk' => ['array', 'size'=>'int', 'preserve'=>'bool'],
'Threaded::count' => ['int'],
'Threaded::extend' => ['bool', 'class'=>'string'],
'Threaded::from' => ['Threaded', 'run'=>'Closure', 'construct='=>'Closure', 'args='=>'array'],
'Threaded::getTerminationInfo' => ['array'],
'Threaded::isRunning' => ['bool'],
'Threaded::isTerminated' => ['bool'],
'Threaded::isWaiting' => ['bool'],
'Threaded::lock' => ['bool'],
'Threaded::merge' => ['bool', 'from'=>'mixed', 'overwrite='=>'bool'],
'Threaded::notify' => ['bool'],
'Threaded::notifyOne' => ['bool'],
'Threaded::offsetExists' => ['bool', 'offset'=>'mixed'],
'Threaded::offsetGet' => ['mixed', 'offset'=>'mixed'],
'Threaded::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'],
'Threaded::offsetUnset' => ['void', 'offset'=>'mixed'],
'Threaded::pop' => ['bool'],
'Threaded::run' => ['void'],
'Threaded::shift' => ['mixed'],
'Threaded::synchronized' => ['mixed', 'block'=>'Closure', '...args='=>'mixed'],
'Threaded::unlock' => ['bool'],
'Threaded::wait' => ['bool', 'timeout='=>'int'],
'Throwable::__toString' => ['string'],
'Throwable::getCode' => ['int'],
'Throwable::getFile' => ['string'],
'Throwable::getLine' => ['int'],
'Throwable::getMessage' => ['string'],
'Throwable::getPrevious' => ['Throwable'],
'Throwable::getTrace' => ['array'],
'Throwable::getTraceAsString' => ['string'],
'tidy::__construct' => ['void', 'filename='=>'string', 'config='=>'', 'encoding='=>'string', 'use_include_path='=>'bool'],
'tidy::body' => ['tidyNode'],
'tidy::cleanRepair' => ['bool'],
'tidy::diagnose' => ['bool'],
'tidy::getConfig' => ['array'],
'tidy::getHtmlVer' => ['int'],
'tidy::getOpt' => ['', 'option'=>'string'],
'tidy::getOptDoc' => ['string', 'optname'=>'string'],
'tidy::getRelease' => ['string'],
'tidy::getStatus' => ['int'],
'tidy::head' => ['tidyNode'],
'tidy::html' => ['tidyNode'],
'tidy::htmlver' => ['int'],
'tidy::isXhtml' => ['bool'],
'tidy::isXml' => ['bool'],
'tidy::parseFile' => ['bool', 'filename'=>'string', 'config='=>'', 'encoding='=>'string', 'use_include_path='=>'bool'],
'tidy::parseString' => ['bool', 'input'=>'string', 'config='=>'', 'encoding='=>'string'],
'tidy::repairFile' => ['string', 'filename'=>'string', 'config='=>'', 'encoding='=>'string', 'use_include_path='=>'bool'],
'tidy::repairString' => ['string', 'data'=>'string', 'config='=>'', 'encoding='=>'string'],
'tidy::root' => ['tidyNode'],
'tidy_access_count' => ['int', 'obj'=>'tidy'],
'tidy_clean_repair' => ['bool', 'obj'=>'tidy'],
'tidy_config_count' => ['int', 'obj'=>'tidy'],
'tidy_diagnose' => ['bool', 'obj'=>'tidy'],
'tidy_error_count' => ['int', 'obj'=>'tidy'],
'tidy_get_body' => ['tidyNode', 'obj'=>'tidy'],
'tidy_get_config' => ['array', 'obj'=>'tidy'],
'tidy_get_error_buffer' => ['string', 'obj'=>'tidy'],
'tidy_get_head' => ['tidyNode', 'obj'=>'tidy'],
'tidy_get_html' => ['tidyNode', 'obj'=>'tidy'],
'tidy_get_html_ver' => ['int', 'obj'=>'tidy'],
'tidy_get_opt_doc' => ['string', 'obj'=>'tidy', 'optname'=>'string'],
'tidy_get_output' => ['string', 'obj'=>'tidy'],
'tidy_get_release' => ['string'],
'tidy_get_root' => ['tidyNode', 'obj'=>'tidy'],
'tidy_get_status' => ['int', 'obj'=>'tidy'],
'tidy_getopt' => ['', 'option'=>'string', 'obj'=>'tidy'],
'tidy_is_xhtml' => ['bool', 'obj'=>'tidy'],
'tidy_is_xml' => ['bool', 'obj'=>'tidy'],
'tidy_load_config' => ['void', 'filename'=>'string', 'encoding'=>'string'],
'tidy_parse_file' => ['tidy', 'file'=>'string', 'config_options='=>'', 'encoding='=>'string', 'use_include_path='=>'bool'],
'tidy_parse_string' => ['tidy', 'input'=>'string', 'config_options='=>'', 'encoding='=>'string'],
'tidy_repair_file' => ['string', 'filename'=>'string', 'config_file='=>'', 'encoding='=>'string', 'use_include_path='=>'bool'],
'tidy_repair_string' => ['string', 'data'=>'string', 'config_file='=>'', 'encoding='=>'string'],
'tidy_reset_config' => ['bool'],
'tidy_save_config' => ['bool', 'filename'=>'string'],
'tidy_set_encoding' => ['bool', 'encoding'=>'string'],
'tidy_setopt' => ['bool', 'option'=>'string', 'value'=>'mixed'],
'tidy_warning_count' => ['int', 'obj'=>'tidy'],
'tidyNode::__construct' => ['void'],
'tidyNode::getParent' => ['tidyNode'],
'tidyNode::hasChildren' => ['bool'],
'tidyNode::hasSiblings' => ['bool'],
'tidyNode::isAsp' => ['bool'],
'tidyNode::isComment' => ['bool'],
'tidyNode::isHtml' => ['bool'],
'tidyNode::isJste' => ['bool'],
'tidyNode::isPhp' => ['bool'],
'tidyNode::isText' => ['bool'],
'time' => ['int'],
'time_nanosleep' => ['array|false', 'seconds'=>'int', 'nanoseconds'=>'int'],
'time_sleep_until' => ['bool', 'timestamp'=>'float'],
'timezone_abbreviations_list' => ['array'],
'timezone_identifiers_list' => ['array', 'what='=>'int', 'country='=>'?string'],
'timezone_location_get' => ['array|false', 'object'=>'DateTimeZone'],
'timezone_name_from_abbr' => ['string|false', 'abbr'=>'string', 'gmtoffset='=>'int', 'isdst='=>'int'],
'timezone_name_get' => ['string', 'object'=>'DateTimeZone'],
'timezone_offset_get' => ['int', 'object'=>'DateTimeZone', 'datetime'=>'DateTime'],
'timezone_open' => ['DateTimeZone', 'timezone'=>'string'],
'timezone_transitions_get' => ['array|false', 'object'=>'DateTimeZone', 'timestamp_begin='=>'int', 'timestamp_end='=>'int'],
'timezone_version_get' => ['string'],
'tmpfile' => ['resource'],
'token_get_all' => ['array', 'source'=>'string', 'flags='=>'int'],
'token_name' => ['string', 'type'=>'int'],
'TokyoTyrant::__construct' => ['void', 'host='=>'string', 'port='=>'int', 'options='=>'array'],
'TokyoTyrant::add' => ['int|float', 'key'=>'string', 'increment'=>'float', 'type='=>'int'],
'TokyoTyrant::connect' => ['TokyoTyrant', 'host'=>'string', 'port='=>'int', 'options='=>'array'],
'TokyoTyrant::connectUri' => ['TokyoTyrant', 'uri'=>'string'],
'TokyoTyrant::copy' => ['TokyoTyrant', 'path'=>'string'],
'TokyoTyrant::ext' => ['string', 'name'=>'string', 'options'=>'int', 'key'=>'string', 'value'=>'string'],
'TokyoTyrant::fwmKeys' => ['array', 'prefix'=>'string', 'max_recs'=>'int'],
'TokyoTyrant::get' => ['array', 'keys'=>'mixed'],
'TokyoTyrant::getIterator' => ['TokyoTyrantIterator'],
'TokyoTyrant::num' => ['int'],
'TokyoTyrant::out' => ['string', 'keys'=>'mixed'],
'TokyoTyrant::put' => ['TokyoTyrant', 'keys'=>'mixed', 'value='=>'string'],
'TokyoTyrant::putCat' => ['TokyoTyrant', 'keys'=>'mixed', 'value='=>'string'],
'TokyoTyrant::putKeep' => ['TokyoTyrant', 'keys'=>'mixed', 'value='=>'string'],
'TokyoTyrant::putNr' => ['TokyoTyrant', 'keys'=>'mixed', 'value='=>'string'],
'TokyoTyrant::putShl' => ['mixed', 'key'=>'string', 'value'=>'string', 'width'=>'int'],
'TokyoTyrant::restore' => ['mixed', 'log_dir'=>'string', 'timestamp'=>'int', 'check_consistency='=>'bool'],
'TokyoTyrant::setMaster' => ['mixed', 'host'=>'string', 'port'=>'int', 'timestamp'=>'int', 'check_consistency='=>'bool'],
'TokyoTyrant::size' => ['int', 'key'=>'string'],
'TokyoTyrant::stat' => ['array'],
'TokyoTyrant::sync' => ['mixed'],
'TokyoTyrant::tune' => ['TokyoTyrant', 'timeout'=>'float', 'options='=>'int'],
'TokyoTyrant::vanish' => ['mixed'],
'TokyoTyrantIterator::__construct' => ['void', 'object'=>'mixed'],
'TokyoTyrantIterator::current' => ['mixed'],
'TokyoTyrantIterator::key' => ['mixed'],
'TokyoTyrantIterator::next' => ['mixed'],
'TokyoTyrantIterator::rewind' => ['void'],
'TokyoTyrantIterator::valid' => ['bool'],
'TokyoTyrantQuery::__construct' => ['void', 'table'=>'TokyoTyrantTable'],
'TokyoTyrantQuery::addCond' => ['mixed', 'name'=>'string', 'op'=>'int', 'expr'=>'string'],
'TokyoTyrantQuery::count' => ['int'],
'TokyoTyrantQuery::current' => ['array'],
'TokyoTyrantQuery::hint' => ['string'],
'TokyoTyrantQuery::key' => ['string'],
'TokyoTyrantQuery::metaSearch' => ['array', 'queries'=>'array', 'type'=>'int'],
'TokyoTyrantQuery::next' => ['array'],
'TokyoTyrantQuery::out' => ['TokyoTyrantQuery'],
'TokyoTyrantQuery::rewind' => ['bool'],
'TokyoTyrantQuery::search' => ['array'],
'TokyoTyrantQuery::setLimit' => ['mixed', 'max='=>'int', 'skip='=>'int'],
'TokyoTyrantQuery::setOrder' => ['mixed', 'name'=>'string', 'type'=>'int'],
'TokyoTyrantQuery::valid' => ['bool'],
'TokyoTyrantTable::add' => ['void', 'key'=>'string', 'increment'=>'mixed', 'type='=>'string'],
'TokyoTyrantTable::genUid' => ['int'],
'TokyoTyrantTable::get' => ['array', 'keys'=>'mixed'],
'TokyoTyrantTable::getIterator' => ['TokyoTyrantIterator'],
'TokyoTyrantTable::getQuery' => ['TokyoTyrantQuery'],
'TokyoTyrantTable::out' => ['void', 'keys'=>'mixed'],
'TokyoTyrantTable::put' => ['int', 'key'=>'string', 'columns'=>'array'],
'TokyoTyrantTable::putCat' => ['void', 'key'=>'string', 'columns'=>'array'],
'TokyoTyrantTable::putKeep' => ['void', 'key'=>'string', 'columns'=>'array'],
'TokyoTyrantTable::putNr' => ['void', 'keys'=>'mixed', 'value='=>'string'],
'TokyoTyrantTable::putShl' => ['void', 'key'=>'string', 'value'=>'string', 'width'=>'int'],
'TokyoTyrantTable::setIndex' => ['mixed', 'column'=>'string', 'type'=>'int'],
'touch' => ['bool', 'filename'=>'string', 'time='=>'int', 'atime='=>'int'],
'trader_acos' => ['array', 'real'=>'array'],
'trader_ad' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array'],
'trader_add' => ['array', 'real0'=>'array', 'real1'=>'array'],
'trader_adosc' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int'],
'trader_adx' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_adxr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_apo' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'mAType='=>'int'],
'trader_aroon' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'],
'trader_aroonosc' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'],
'trader_asin' => ['array', 'real'=>'array'],
'trader_atan' => ['array', 'real'=>'array'],
'trader_atr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_avgprice' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_bbands' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'nbDevUp='=>'float', 'nbDevDn='=>'float', 'mAType='=>'int'],
'trader_beta' => ['array', 'real0'=>'array', 'real1'=>'array', 'timePeriod='=>'int'],
'trader_bop' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cci' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_cdl2crows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdl3blackcrows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdl3inside' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdl3linestrike' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdl3outside' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdl3starsinsouth' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdl3whitesoldiers' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlabandonedbaby' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'],
'trader_cdladvanceblock' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlbelthold' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlbreakaway' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlclosingmarubozu' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlconcealbabyswall' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlcounterattack' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdldarkcloudcover' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'],
'trader_cdldoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdldojistar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdldragonflydoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlengulfing' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdleveningdojistar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'],
'trader_cdleveningstar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'],
'trader_cdlgapsidesidewhite' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlgravestonedoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlhammer' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlhangingman' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlharami' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlharamicross' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlhighwave' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlhikkake' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlhikkakemod' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlhomingpigeon' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlidentical3crows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlinneck' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlinvertedhammer' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlkicking' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlkickingbylength' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlladderbottom' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdllongleggeddoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdllongline' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlmarubozu' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlmatchinglow' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlmathold' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'],
'trader_cdlmorningdojistar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'],
'trader_cdlmorningstar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'],
'trader_cdlonneck' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlpiercing' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlrickshawman' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlrisefall3methods' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlseparatinglines' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlshootingstar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlshortline' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlspinningtop' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlstalledpattern' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlsticksandwich' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdltakuri' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdltasukigap' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlthrusting' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdltristar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlunique3river' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlupsidegap2crows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_cdlxsidegap3methods' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_ceil' => ['array', 'real'=>'array'],
'trader_cmo' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_correl' => ['array', 'real0'=>'array', 'real1'=>'array', 'timePeriod='=>'int'],
'trader_cos' => ['array', 'real'=>'array'],
'trader_cosh' => ['array', 'real'=>'array'],
'trader_dema' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_div' => ['array', 'real0'=>'array', 'real1'=>'array'],
'trader_dx' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_ema' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_errno' => ['int'],
'trader_exp' => ['array', 'real'=>'array'],
'trader_floor' => ['array', 'real'=>'array'],
'trader_get_compat' => ['int'],
'trader_get_unstable_period' => ['int', 'functionId'=>'int'],
'trader_ht_dcperiod' => ['array', 'real'=>'array'],
'trader_ht_dcphase' => ['array', 'real'=>'array'],
'trader_ht_phasor' => ['array', 'real'=>'array'],
'trader_ht_sine' => ['array', 'real'=>'array'],
'trader_ht_trendline' => ['array', 'real'=>'array'],
'trader_ht_trendmode' => ['array', 'real'=>'array'],
'trader_kama' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_linearreg' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_linearreg_angle' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_linearreg_intercept' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_linearreg_slope' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_ln' => ['array', 'real'=>'array'],
'trader_log10' => ['array', 'real'=>'array'],
'trader_ma' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'mAType='=>'int'],
'trader_macd' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'signalPeriod='=>'int'],
'trader_macdext' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'fastMAType='=>'int', 'slowPeriod='=>'int', 'slowMAType='=>'int', 'signalPeriod='=>'int', 'signalMAType='=>'int'],
'trader_macdfix' => ['array', 'real'=>'array', 'signalPeriod='=>'int'],
'trader_mama' => ['array', 'real'=>'array', 'fastLimit='=>'float', 'slowLimit='=>'float'],
'trader_mavp' => ['array', 'real'=>'array', 'periods'=>'array', 'minPeriod='=>'int', 'maxPeriod='=>'int', 'mAType='=>'int'],
'trader_max' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_maxindex' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_medprice' => ['array', 'high'=>'array', 'low'=>'array'],
'trader_mfi' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array', 'timePeriod='=>'int'],
'trader_midpoint' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_midprice' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'],
'trader_min' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_minindex' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_minmax' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_minmaxindex' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_minus_di' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_minus_dm' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'],
'trader_mom' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_mult' => ['array', 'real0'=>'array', 'real1'=>'array'],
'trader_natr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_obv' => ['array', 'real'=>'array', 'volume'=>'array'],
'trader_plus_di' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_plus_dm' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'],
'trader_ppo' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'mAType='=>'int'],
'trader_roc' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_rocp' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_rocr' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_rocr100' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_rsi' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_sar' => ['array', 'high'=>'array', 'low'=>'array', 'acceleration='=>'float', 'maximum='=>'float'],
'trader_sarext' => ['array', 'high'=>'array', 'low'=>'array', 'startValue='=>'float', 'offsetOnReverse='=>'float', 'accelerationInitLong='=>'float', 'accelerationLong='=>'float', 'accelerationMaxLong='=>'float', 'accelerationInitShort='=>'float', 'accelerationShort='=>'float', 'accelerationMaxShort='=>'float'],
'trader_set_compat' => ['void', 'compatId'=>'int'],
'trader_set_unstable_period' => ['void', 'functionId'=>'int', 'timePeriod'=>'int'],
'trader_sin' => ['array', 'real'=>'array'],
'trader_sinh' => ['array', 'real'=>'array'],
'trader_sma' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_sqrt' => ['array', 'real'=>'array'],
'trader_stddev' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'nbDev='=>'float'],
'trader_stoch' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'fastK_Period='=>'int', 'slowK_Period='=>'int', 'slowK_MAType='=>'int', 'slowD_Period='=>'int', 'slowD_MAType='=>'int'],
'trader_stochf' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'fastK_Period='=>'int', 'fastD_Period='=>'int', 'fastD_MAType='=>'int'],
'trader_stochrsi' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'fastK_Period='=>'int', 'fastD_Period='=>'int', 'fastD_MAType='=>'int'],
'trader_sub' => ['array', 'real0'=>'array', 'real1'=>'array'],
'trader_sum' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_t3' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'vFactor='=>'float'],
'trader_tan' => ['array', 'real'=>'array'],
'trader_tanh' => ['array', 'real'=>'array'],
'trader_tema' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_trange' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_trima' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_trix' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_tsf' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trader_typprice' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_ultosc' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod1='=>'int', 'timePeriod2='=>'int', 'timePeriod3='=>'int'],
'trader_var' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'nbDev='=>'float'],
'trader_wclprice' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'],
'trader_willr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'],
'trader_wma' => ['array', 'real'=>'array', 'timePeriod='=>'int'],
'trait_exists' => ['bool', 'traitname'=>'string', 'autoload='=>'bool'],
'Transliterator::create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'],
'Transliterator::createFromRules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'],
'Transliterator::createInverse' => ['Transliterator'],
'Transliterator::getErrorCode' => ['int'],
'Transliterator::getErrorMessage' => ['string'],
'Transliterator::listIDs' => ['array'],
'Transliterator::transliterate' => ['', 'subject'=>'string', 'start='=>'int', 'end='=>'int'],
'transliterator_create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'],
'transliterator_create_from_rules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'],
'transliterator_create_inverse' => ['Transliterator', 'obj'=>'Transliterator'],
'transliterator_get_error_code' => ['int', 'obj'=>'Transliterator'],
'transliterator_get_error_message' => ['string', 'obj'=>'Transliterator'],
'transliterator_list_ids' => ['array'],
'transliterator_transliterate' => ['', 'obj'=>'Transliterator', 'subject'=>'string', 'start='=>'int', 'end='=>'int'],
'trigger_error' => ['bool', 'message'=>'string', 'error_type='=>'int'],
'trim' => ['string', 'str'=>'string', 'character_mask='=>'string'],
'TypeError::__clone' => ['void'],
'TypeError::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?TypeError'],
'TypeError::__toString' => ['string'],
'TypeError::getCode' => ['int'],
'TypeError::getFile' => ['string'],
'TypeError::getLine' => ['int'],
'TypeError::getMessage' => ['string'],
'TypeError::getPrevious' => ['Throwable|TypeError'],
'TypeError::getTrace' => ['array'],
'TypeError::getTraceAsString' => ['string'],
'uasort' => ['bool', '&rw_array_arg'=>'array', 'cmp_function'=>'callable(mixed,mixed):int'],
'ucfirst' => ['string', 'str'=>'string'],
'UConverter::__construct' => ['void', 'destination_encoding'=>'string', 'source_encoding='=>'string'],
'UConverter::convert' => ['string', 'str'=>'string', 'reverse='=>'bool'],
'UConverter::fromUCallback' => ['mixed', 'reason'=>'int', 'source'=>'string', 'codePoint'=>'string', '&w_error'=>'int'],
'UConverter::getAliases' => ['array', 'name='=>'string'],
'UConverter::getAvailable' => ['array'],
'UConverter::getDestinationEncoding' => ['string'],
'UConverter::getDestinationType' => ['int'],
'UConverter::getErrorCode' => ['int'],
'UConverter::getErrorMessage' => ['string'],
'UConverter::getSourceEncoding' => ['string'],
'UConverter::getSourceType' => ['int'],
'UConverter::getStandards' => ['array'],
'UConverter::getSubstChars' => ['string'],
'UConverter::reasonText' => ['string', 'reason='=>'int'],
'UConverter::setDestinationEncoding' => ['bool', 'encoding'=>'string'],
'UConverter::setSourceEncoding' => ['bool', 'encoding'=>'string'],
'UConverter::setSubstChars' => ['bool', 'chars'=>'string'],
'UConverter::toUCallback' => ['mixed', 'reason'=>'int', 'source'=>'string', 'codeUnits'=>'string', '&w_error'=>'int'],
'UConverter::transcode' => ['string', 'str'=>'string', 'toEncoding'=>'string', 'fromEncoding'=>'string', 'options='=>'array'],
'ucwords' => ['string', 'str'=>'string', 'delims='=>'string'],
'udm_add_search_limit' => ['bool', 'agent'=>'resource', 'var'=>'int', 'val'=>'string'],
'udm_alloc_agent' => ['resource', 'dbaddr'=>'string', 'dbmode='=>'string'],
'udm_alloc_agent_array' => ['resource', 'databases'=>'array'],
'udm_api_version' => ['int'],
'udm_cat_list' => ['array', 'agent'=>'resource', 'category'=>'string'],
'udm_cat_path' => ['array', 'agent'=>'resource', 'category'=>'string'],
'udm_check_charset' => ['bool', 'agent'=>'resource', 'charset'=>'string'],
'udm_check_stored' => ['int', 'agent'=>'', 'link'=>'int', 'doc_id'=>'string'],
'udm_clear_search_limits' => ['bool', 'agent'=>'resource'],
'udm_close_stored' => ['int', 'agent'=>'', 'link'=>'int'],
'udm_crc32' => ['int', 'agent'=>'resource', 'str'=>'string'],
'udm_errno' => ['int', 'agent'=>'resource'],
'udm_error' => ['string', 'agent'=>'resource'],
'udm_find' => ['resource', 'agent'=>'resource', 'query'=>'string'],
'udm_free_agent' => ['int', 'agent'=>'resource'],
'udm_free_ispell_data' => ['bool', 'agent'=>'int'],
'udm_free_res' => ['bool', 'res'=>'resource'],
'udm_get_doc_count' => ['int', 'agent'=>'resource'],
'udm_get_res_field' => ['string', 'res'=>'resource', 'row'=>'int', 'field'=>'int'],
'udm_get_res_param' => ['string', 'res'=>'resource', 'param'=>'int'],
'udm_hash32' => ['int', 'agent'=>'resource', 'str'=>'string'],
'udm_load_ispell_data' => ['bool', 'agent'=>'resource', 'var'=>'int', 'val1'=>'string', 'val2'=>'string', 'flag'=>'int'],
'udm_open_stored' => ['int', 'agent'=>'', 'storedaddr'=>'string'],
'udm_set_agent_param' => ['bool', 'agent'=>'resource', 'var'=>'int', 'val'=>'string'],
'ui\draw\text\font\fontfamilies' => ['array'],
'ui\quit' => ['void'],
'ui\run' => ['void', 'flags='=>'int'],
'uksort' => ['bool', '&rw_array_arg'=>'array', 'cmp_function'=>'callable(mixed,mixed):int'],
'umask' => ['int', 'mask='=>'int'],
'UnderflowException::__clone' => ['void'],
'UnderflowException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?UnderflowException'],
'UnderflowException::__toString' => ['string'],
'UnderflowException::getCode' => ['int'],
'UnderflowException::getFile' => ['string'],
'UnderflowException::getLine' => ['int'],
'UnderflowException::getMessage' => ['string'],
'UnderflowException::getPrevious' => ['Throwable|UnderflowException'],
'UnderflowException::getTrace' => ['array'],
'UnderflowException::getTraceAsString' => ['string'],
'UnexpectedValueException::__clone' => ['void'],
'UnexpectedValueException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?Throwable|?UnexpectedValueException'],
'UnexpectedValueException::__toString' => ['string'],
'UnexpectedValueException::getCode' => ['int'],
'UnexpectedValueException::getFile' => ['string'],
'UnexpectedValueException::getLine' => ['int'],
'UnexpectedValueException::getMessage' => ['string'],
'UnexpectedValueException::getPrevious' => ['Throwable|UnexpectedValueException'],
'UnexpectedValueException::getTrace' => ['array'],
'UnexpectedValueException::getTraceAsString' => ['string'],
'uniqid' => ['string', 'prefix='=>'string', 'more_entropy='=>'bool'],
'unixtojd' => ['int', 'timestamp='=>'int'],
'unlink' => ['bool', 'filename'=>'string', 'context='=>'resource'],
'unpack' => ['array', 'format'=>'string', 'data'=>'string', 'offset='=>'int'],
'unregister_tick_function' => ['void', 'function_name'=>'string'],
'unserialize' => ['mixed', 'variable_representation'=>'string', 'allowed_classes='=>'array{allowed_classes?:string[]|bool}'],
'unset' => ['void', 'var='=>'mixed', '...args='=>'mixed'],
'untaint' => ['bool', '&rw_string'=>'string', '&...rw_strings='=>'string'],
'uopz_allow_exit' => ['void', 'allow'=>'bool'],
'uopz_backup' => ['void', 'class'=>'string', 'function'=>'string'],
'uopz_backup\'1' => ['void', 'function'=>'string'],
'uopz_compose' => ['void', 'name'=>'string', 'classes'=>'array', 'methods='=>'array', 'properties='=>'array', 'flags='=>'int'],
'uopz_copy' => ['Closure', 'class'=>'string', 'function'=>'string'],
'uopz_copy\'1' => ['Closure', 'function'=>'string'],
'uopz_delete' => ['void', 'class'=>'string', 'function'=>'string'],
'uopz_delete\'1' => ['void', 'function'=>'string'],
'uopz_extend' => ['void', 'class'=>'string', 'parent'=>'string'],
'uopz_flags' => ['int', 'class'=>'string', 'function'=>'string', 'flags'=>'int'],
'uopz_flags\'1' => ['int', 'function'=>'string', 'flags'=>'int'],
'uopz_function' => ['void', 'class'=>'string', 'function'=>'string', 'handler'=>'Closure', 'modifiers='=>'int'],
'uopz_function\'1' => ['void', 'function'=>'string', 'handler'=>'Closure', 'modifiers='=>'int'],
'uopz_get_exit_status' => ['mixed'],
'uopz_get_mock' => ['mixed', 'class'=>'string'],
'uopz_get_return' => ['mixed', 'class='=>'string', 'function'=>'string'],
'uopz_implement' => ['void', 'class'=>'string', 'interface'=>'string'],
'uopz_overload' => ['void', 'opcode'=>'int', 'callable'=>'Callable'],
'uopz_redefine' => ['void', 'class'=>'string', 'constant'=>'string', 'value'=>'mixed'],
'uopz_redefine\'1' => ['void', 'constant'=>'string', 'value'=>'mixed'],
'uopz_rename' => ['void', 'class'=>'string', 'function'=>'string', 'rename'=>'string'],
'uopz_rename\'1' => ['void', 'function'=>'string', 'rename'=>'string'],
'uopz_restore' => ['void', 'class'=>'string', 'function'=>'string'],
'uopz_restore\'1' => ['void', 'function'=>'string'],
'uopz_set_mock' => ['void', 'class'=>'string', 'mock'=>'object|string'],
'uopz_set_return' => ['bool', 'class='=>'string', 'function'=>'string', 'value'=>'mixed', 'execute='=>'bool'],
'uopz_undefine' => ['void', 'class'=>'string', 'constant'=>'string'],
'uopz_undefine\'1' => ['void', 'constant'=>'string'],
'uopz_unset_mock' => ['void', 'class'=>'string'],
'uopz_unset_return' => ['bool', 'class='=>'string', 'function'=>'string'],
'urldecode' => ['string', 'str'=>'string'],
'urlencode' => ['string', 'str'=>'string'],
'use_soap_error_handler' => ['bool', 'handler='=>'bool'],
'usleep' => ['void', 'micro_seconds'=>'int'],
'usort' => ['bool', '&rw_array_arg'=>'array', 'cmp_function'=>'callable(mixed,mixed):int'],
'utf8_decode' => ['string', 'data'=>'string'],
'utf8_encode' => ['string', 'data'=>'string'],
'V8Js::__construct' => ['void', 'object_name='=>'string', 'variables='=>'array', 'extensions='=>'array', 'report_uncaught_exceptions='=>'bool'],
'V8Js::clearPendingException' => [''],
'V8Js::compileString' => ['resource', 'script'=>'', 'identifier='=>'string'],
'V8Js::createSnapshot' => ['false|string', 'embed_source'=>'string'],
'V8Js::executeScript' => ['', 'script'=>'resource', 'flags='=>'int', 'time_limit='=>'int', 'memory_limit='=>'int'],
'V8Js::executeString' => ['mixed', 'script'=>'string', 'identifier='=>'string', 'flags='=>'int'],
'V8Js::getExtensions' => ['array'],
'V8Js::getPendingException' => ['V8JsException'],
'V8Js::registerExtension' => ['bool', 'extension_name'=>'string', 'script'=>'string', 'dependencies='=>'array', 'auto_enable='=>'bool'],
'V8Js::setAverageObjectSize' => ['', 'average_object_size'=>'int'],
'V8Js::setMemoryLimit' => ['', 'limit'=>'int'],
'V8Js::setModuleLoader' => ['', 'loader'=>'callable'],
'V8Js::setModuleNormaliser' => ['', 'normaliser'=>'callable'],
'V8Js::setTimeLimit' => ['', 'limit'=>'int'],
'V8JsException::getJsFileName' => ['string'],
'V8JsException::getJsLineNumber' => ['int'],
'V8JsException::getJsSourceLine' => ['int'],
'V8JsException::getJsTrace' => ['string'],
'V8JsScriptException::__clone' => ['void'],
'V8JsScriptException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'?\Exception|?\Throwable'],
'V8JsScriptException::__toString' => ['string'],
'V8JsScriptException::__wakeup' => ['void'],
'V8JsScriptException::getCode' => ['int'],
'V8JsScriptException::getFile' => ['string'],
'V8JsScriptException::getJsEndColumn' => ['int'],
'V8JsScriptException::getJsFileName' => ['string'],
'V8JsScriptException::getJsLineNumber' => ['int'],
'V8JsScriptException::getJsSourceLine' => ['string'],
'V8JsScriptException::getJsStartColumn' => ['int'],
'V8JsScriptException::getJsTrace' => ['string'],
'V8JsScriptException::getLine' => ['int'],
'V8JsScriptException::getMessage' => ['string'],
'V8JsScriptException::getPrevious' => ['Exception|Throwable'],
'V8JsScriptException::getTrace' => ['array'],
'V8JsScriptException::getTraceAsString' => ['string'],
'var_dump' => ['void', 'var'=>'mixed', '...args='=>'mixed'],
'var_export' => ['string|null', 'var'=>'mixed', 'return='=>'bool'],
'VARIANT::__construct' => ['void', 'value='=>'mixed', 'type='=>'int', 'codepage='=>'int'],
'variant_abs' => ['mixed', 'left'=>'mixed'],
'variant_add' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_and' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_cast' => ['object', 'variant'=>'object', 'type'=>'int'],
'variant_cat' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_cmp' => ['int', 'left'=>'mixed', 'right'=>'mixed', 'lcid='=>'int', 'flags='=>'int'],
'variant_date_from_timestamp' => ['object', 'timestamp'=>'int'],
'variant_date_to_timestamp' => ['int', 'variant'=>'object'],
'variant_div' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_eqv' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_fix' => ['mixed', 'left'=>'mixed'],
'variant_get_type' => ['int', 'variant'=>'object'],
'variant_idiv' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_imp' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_int' => ['mixed', 'left'=>'mixed'],
'variant_mod' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_mul' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_neg' => ['mixed', 'left'=>'mixed'],
'variant_not' => ['mixed', 'left'=>'mixed'],
'variant_or' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_pow' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_round' => ['mixed', 'left'=>'mixed', 'decimals'=>'int'],
'variant_set' => ['void', 'variant'=>'object', 'value'=>'mixed'],
'variant_set_type' => ['void', 'variant'=>'object', 'type'=>'int'],
'variant_sub' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'variant_xor' => ['mixed', 'left'=>'mixed', 'right'=>'mixed'],
'VarnishAdmin::__construct' => ['void', 'args='=>'array'],
'VarnishAdmin::auth' => ['bool'],
'VarnishAdmin::ban' => ['int', 'vcl_regex'=>'string'],
'VarnishAdmin::banUrl' => ['int', 'vcl_regex'=>'string'],
'VarnishAdmin::clearPanic' => ['int'],
'VarnishAdmin::connect' => ['bool'],
'VarnishAdmin::disconnect' => ['bool'],
'VarnishAdmin::getPanic' => ['string'],
'VarnishAdmin::getParams' => ['array'],
'VarnishAdmin::isRunning' => ['bool'],
'VarnishAdmin::setCompat' => ['void', 'compat'=>'int'],
'VarnishAdmin::setHost' => ['void', 'host'=>'string'],
'VarnishAdmin::setIdent' => ['void', 'ident'=>'string'],
'VarnishAdmin::setParam' => ['int', 'name'=>'string', 'value'=>'string|int'],
'VarnishAdmin::setPort' => ['void', 'port'=>'int'],
'VarnishAdmin::setSecret' => ['void', 'secret'=>'string'],
'VarnishAdmin::setTimeout' => ['void', 'timeout'=>'int'],
'VarnishAdmin::start' => ['int'],
'VarnishAdmin::stop' => ['int'],
'VarnishLog::__construct' => ['void', 'args='=>'array'],
'VarnishLog::getLine' => ['array'],
'VarnishLog::getTagName' => ['string', 'index'=>'int'],
'VarnishStat::__construct' => ['void', 'args='=>'array'],
'VarnishStat::getSnapshot' => ['array'],
'version_compare' => ['int|?bool', 'ver1'=>'string', 'ver2'=>'string', 'oper='=>'string'],
'vfprintf' => ['int', 'stream'=>'resource', 'format'=>'string', 'args'=>'array'],
'virtual' => ['bool', 'uri'=>'string'],
'vpopmail_add_alias_domain' => ['bool', 'domain'=>'string', 'aliasdomain'=>'string'],
'vpopmail_add_alias_domain_ex' => ['bool', 'olddomain'=>'string', 'newdomain'=>'string'],
'vpopmail_add_domain' => ['bool', 'domain'=>'string', 'dir'=>'string', 'uid'=>'int', 'gid'=>'int'],
'vpopmail_add_domain_ex' => ['bool', 'domain'=>'string', 'passwd'=>'string', 'quota='=>'string', 'bounce='=>'string', 'apop='=>'bool'],
'vpopmail_add_user' => ['bool', 'user'=>'string', 'domain'=>'string', 'password'=>'string', 'gecos='=>'string', 'apop='=>'bool'],
'vpopmail_alias_add' => ['bool', 'user'=>'string', 'domain'=>'string', 'alias'=>'string'],
'vpopmail_alias_del' => ['bool', 'user'=>'string', 'domain'=>'string'],
'vpopmail_alias_del_domain' => ['bool', 'domain'=>'string'],
'vpopmail_alias_get' => ['array', 'alias'=>'string', 'domain'=>'string'],
'vpopmail_alias_get_all' => ['array', 'domain'=>'string'],
'vpopmail_auth_user' => ['bool', 'user'=>'string', 'domain'=>'string', 'password'=>'string', 'apop='=>'string'],
'vpopmail_del_domain' => ['bool', 'domain'=>'string'],
'vpopmail_del_domain_ex' => ['bool', 'domain'=>'string'],
'vpopmail_del_user' => ['bool', 'user'=>'string', 'domain'=>'string'],
'vpopmail_error' => ['string'],
'vpopmail_passwd' => ['bool', 'user'=>'string', 'domain'=>'string', 'password'=>'string', 'apop='=>'bool'],
'vpopmail_set_user_quota' => ['bool', 'user'=>'string', 'domain'=>'string', 'quota'=>'string'],
'vprintf' => ['int', 'format'=>'string', 'args'=>'array'],
'vsprintf' => ['string', 'format'=>'string', 'args'=>'array'],
'w32api_deftype' => ['bool', 'typename'=>'string', 'member1_type'=>'string', 'member1_name'=>'string', '...args='=>'string'],
'w32api_init_dtype' => ['resource', 'typename'=>'string', 'value'=>'', '...args='=>''],
'w32api_invoke_function' => ['', 'funcname'=>'string', 'argument'=>'', '...args='=>''],
'w32api_register_function' => ['bool', 'library'=>'string', 'function_name'=>'string', 'return_type'=>'string'],
'w32api_set_call_method' => ['', 'method'=>'int'],
'wddx_add_vars' => ['bool', 'packet_id'=>'resource', 'var_names'=>'mixed', '...vars='=>'mixed'],
'wddx_deserialize' => ['mixed', 'packet'=>'string'],
'wddx_packet_end' => ['string', 'packet_id'=>'resource'],
'wddx_packet_start' => ['resource', 'comment='=>'string'],
'wddx_serialize_value' => ['string', 'var'=>'mixed', 'comment='=>'string'],
'wddx_serialize_vars' => ['string', 'var_name'=>'mixed', '...vars='=>'mixed'],
'WeakMap::__construct' => ['void'],
'WeakMap::count' => ['int'],
'WeakMap::current' => ['mixed'],
'WeakMap::key' => ['object'],
'WeakMap::next' => ['void'],
'WeakMap::offsetExists' => ['bool', 'object'=>'object'],
'WeakMap::offsetGet' => ['mixed', 'object'=>'object'],
'WeakMap::offsetSet' => ['void', 'object'=>'object', 'value'=>'mixed'],
'WeakMap::offsetUnset' => ['void', 'object'=>'object'],
'WeakMap::rewind' => ['void'],
'WeakMap::valid' => ['bool'],
'Weakref::acquire' => ['bool'],
'Weakref::get' => ['object'],
'Weakref::release' => ['bool'],
'Weakref::valid' => ['bool'],
'webObj::convertToString' => ['string'],
'webObj::free' => ['void'],
'webObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''],
'webObj::updateFromString' => ['int', 'snippet'=>'string'],
'win32_continue_service' => ['int', 'servicename'=>'string', 'machine='=>'string'],
'win32_create_service' => ['mixed', 'details'=>'array', 'machine='=>'string'],
'win32_delete_service' => ['mixed', 'servicename'=>'string', 'machine='=>'string'],
'win32_get_last_control_message' => ['int'],
'win32_pause_service' => ['int', 'servicename'=>'string', 'machine='=>'string'],
'win32_ps_list_procs' => ['array'],
'win32_ps_stat_mem' => ['array'],
'win32_ps_stat_proc' => ['array', 'pid='=>'int'],
'win32_query_service_status' => ['mixed', 'servicename'=>'string', 'machine='=>'string'],
'win32_set_service_status' => ['bool', 'status'=>'int', 'checkpoint='=>'int'],
'win32_start_service' => ['int', 'servicename'=>'string', 'machine='=>'string'],
'win32_start_service_ctrl_dispatcher' => ['mixed', 'name'=>'string'],
'win32_stop_service' => ['int', 'servicename'=>'string', 'machine='=>'string'],
'wincache_fcache_fileinfo' => ['array', 'summaryonly='=>'bool'],
'wincache_fcache_meminfo' => ['array'],
'wincache_lock' => ['bool', 'key'=>'string', 'isglobal='=>'bool'],
'wincache_ocache_fileinfo' => ['array', 'summaryonly='=>'bool'],
'wincache_ocache_meminfo' => ['array'],
'wincache_refresh_if_changed' => ['bool', 'files='=>'array'],
'wincache_rplist_fileinfo' => ['array', 'summaryonly='=>'bool'],
'wincache_rplist_meminfo' => ['array'],
'wincache_scache_info' => ['array', 'summaryonly='=>'bool'],
'wincache_scache_meminfo' => ['array'],
'wincache_ucache_add' => ['bool', 'key'=>'string', 'value'=>'', 'ttl='=>'int'],
'wincache_ucache_add\'1' => ['bool', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'],
'wincache_ucache_cas' => ['bool', 'key'=>'string', 'old_value'=>'int', 'new_value'=>'int'],
'wincache_ucache_clear' => ['bool'],
'wincache_ucache_dec' => ['mixed', 'key'=>'string', 'dec_by='=>'int', 'success='=>'bool'],
'wincache_ucache_delete' => ['bool', 'key'=>'mixed'],
'wincache_ucache_exists' => ['bool', 'key'=>'string'],
'wincache_ucache_get' => ['mixed', 'key'=>'mixed', '&w_success='=>'bool'],
'wincache_ucache_inc' => ['mixed', 'key'=>'string', 'inc_by='=>'int', 'success='=>'bool'],
'wincache_ucache_info' => ['array', 'summaryonly='=>'bool', 'key='=>'string'],
'wincache_ucache_meminfo' => ['array'],
'wincache_ucache_set' => ['bool', 'key'=>'', 'value'=>'', 'ttl='=>'int'],
'wincache_ucache_set\'1' => ['bool', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'],
'wincache_unlock' => ['bool', 'key'=>'string'],
'wordwrap' => ['string', 'str'=>'string', 'width='=>'int', 'break='=>'string', 'cut='=>'bool'],
'Worker::__construct' => ['void'],
'Worker::chunk' => ['array', 'size'=>'int', 'preserve'=>'bool'],
'Worker::collect' => ['int', 'collector='=>'Callable'],
'Worker::count' => ['int'],
'Worker::detach' => ['void'],
'Worker::getCreatorId' => ['int'],
'Worker::getCurrentThread' => ['Thread'],
'Worker::getCurrentThreadId' => ['int'],
'Worker::getStacked' => ['int'],
'Worker::getTerminationInfo' => ['array'],
'Worker::getThreadId' => ['int'],
'Worker::globally' => ['mixed'],
'Worker::isJoined' => ['bool'],
'Worker::isRunning' => ['bool'],
'Worker::isShutdown' => ['bool'],
'Worker::isStarted' => ['bool'],
'Worker::isTerminated' => ['bool'],
'Worker::isWaiting' => ['bool'],
'Worker::isWorking' => ['bool'],
'Worker::join' => ['bool'],
'Worker::kill' => ['bool'],
'Worker::lock' => ['bool'],
'Worker::merge' => ['bool', 'from'=>'', 'overwrite='=>'mixed'],
'Worker::notify' => ['bool'],
'Worker::offsetExists' => ['bool', 'offset'=>'mixed'],
'Worker::offsetGet' => ['mixed', 'offset'=>'mixed'],
'Worker::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'],
'Worker::offsetUnset' => ['void', 'offset'=>'mixed'],
'Worker::pop' => ['bool'],
'Worker::run' => ['void'],
'Worker::shift' => ['bool'],
'Worker::shutdown' => ['bool'],
'Worker::stack' => ['int', '&rw_work'=>'Threaded'],
'Worker::start' => ['bool', 'options='=>'int'],
'Worker::synchronized' => ['mixed', 'block'=>'Closure', '_='=>'mixed'],
'Worker::unlock' => ['bool'],
'Worker::unstack' => ['int', '&rw_work='=>'Threaded'],
'Worker::wait' => ['bool', 'timeout='=>'int'],
'xattr_get' => ['string', 'filename'=>'string', 'name'=>'string', 'flags='=>'int'],
'xattr_list' => ['array', 'filename'=>'string', 'flags='=>'int'],
'xattr_remove' => ['bool', 'filename'=>'string', 'name'=>'string', 'flags='=>'int'],
'xattr_set' => ['bool', 'filename'=>'string', 'name'=>'string', 'value'=>'string', 'flags='=>'int'],
'xattr_supported' => ['bool', 'filename'=>'string', 'flags='=>'int'],
'xcache_asm' => ['string', 'filename'=>'string'],
'xcache_clear_cache' => ['void', 'type'=>'int', 'id='=>'int'],
'xcache_coredump' => ['string', 'op_type'=>'int'],
'xcache_count' => ['int', 'type'=>'int'],
'xcache_coverager_decode' => ['array', 'data'=>'string'],
'xcache_coverager_get' => ['array', 'clean='=>'bool|false'],
'xcache_coverager_start' => ['void', 'clean='=>'bool|true'],
'xcache_coverager_stop' => ['void', 'clean='=>'bool|false'],
'xcache_dasm_file' => ['string', 'filename'=>'string'],
'xcache_dasm_string' => ['string', 'code'=>'string'],
'xcache_dec' => ['int', 'name'=>'string', 'value='=>'int|mixed', 'ttl='=>'int'],
'xcache_decode' => ['bool', 'filename'=>'string'],
'xcache_encode' => ['string', 'filename'=>'string'],
'xcache_get' => ['mixed', 'name'=>'string'],
'xcache_get_data_type' => ['string', 'type'=>'int'],
'xcache_get_op_spec' => ['string', 'op_type'=>'int'],
'xcache_get_op_type' => ['string', 'op_type'=>'int'],
'xcache_get_opcode' => ['string', 'opcode'=>'int'],
'xcache_get_opcode_spec' => ['string', 'opcode'=>'int'],
'xcache_inc' => ['int', 'name'=>'string', 'value='=>'int|mixed', 'ttl='=>'int'],
'xcache_info' => ['array', 'type'=>'int', 'id'=>'int'],
'xcache_is_autoglobal' => ['string', 'name'=>'string'],
'xcache_isset' => ['bool', 'name'=>'string'],
'xcache_list' => ['array', 'type'=>'int', 'id'=>'int'],
'xcache_set' => ['bool', 'name'=>'string', 'value'=>'mixed', 'ttl='=>'int'],
'xcache_unset' => ['bool', 'name'=>'string'],
'xcache_unset_by_prefix' => ['bool', 'prefix'=>'string'],
'Xcom::__construct' => ['void', 'fabric_url='=>'string', 'fabric_token='=>'string', 'capability_token='=>'string'],
'Xcom::decode' => ['object', 'avro_msg'=>'string', 'json_schema'=>'string'],
'Xcom::encode' => ['string', 'data'=>'stdClass', 'avro_schema'=>'string'],
'Xcom::getDebugOutput' => ['string'],
'Xcom::getLastResponse' => ['string'],
'Xcom::getLastResponseInfo' => ['array'],
'Xcom::getOnboardingURL' => ['string', 'capability_name'=>'string', 'agreement_url'=>'string'],
'Xcom::send' => ['int', 'topic'=>'string', 'data'=>'mixed', 'json_schema='=>'string', 'http_headers='=>'array'],
'Xcom::sendAsync' => ['int', 'topic'=>'string', 'data'=>'mixed', 'json_schema='=>'string', 'http_headers='=>'array'],
'xdebug_break' => ['bool'],
'xdebug_call_class' => ['string'],
'xdebug_call_file' => ['string'],
'xdebug_call_function' => ['string'],
'xdebug_call_line' => ['int'],
'xdebug_clear_aggr_profiling_data' => ['bool'],
'xdebug_code_coverage_started' => ['bool'],
'xdebug_debug_zval' => ['void', 'varName'=>'string'],
'xdebug_debug_zval_stdout' => ['void', 'varName'=>'string'],
'xdebug_disable' => ['void'],
'xdebug_dump_aggr_profiling_data' => ['bool'],
'xdebug_dump_superglobals' => ['void'],
'xdebug_enable' => ['void'],
'xdebug_get_code_coverage' => ['array'],
'xdebug_get_collected_errors' => ['string', 'clean='=>'bool|false'],
'xdebug_get_declared_vars' => ['array'],
'xdebug_get_formatted_function_stack' => [''],
'xdebug_get_function_count' => ['int'],
'xdebug_get_function_stack' => ['array', 'message='=>'string', 'options='=>'int'],
'xdebug_get_headers' => ['array'],
'xdebug_get_monitored_functions' => ['array'],
'xdebug_get_profiler_filename' => ['string'],
'xdebug_get_stack_depth' => ['int'],
'xdebug_get_tracefile_name' => ['string'],
'xdebug_is_enabled' => ['bool'],
'xdebug_memory_usage' => ['int'],
'xdebug_peak_memory_usage' => ['int'],
'xdebug_print_function_stack' => ['array'],
'xdebug_start_code_coverage' => ['void', 'options='=>'int'],
'xdebug_start_error_collection' => ['void'],
'xdebug_start_function_monitor' => ['void', 'list_of_functions_to_monitor'=>'string[]'],
'xdebug_start_trace' => ['void', 'trace_file'=>'', 'options='=>'int|mixed'],
'xdebug_stop_code_coverage' => ['void', 'cleanup='=>'bool|true'],
'xdebug_stop_error_collection' => ['void'],
'xdebug_stop_function_monitor' => ['void'],
'xdebug_stop_trace' => ['void'],
'xdebug_time_index' => ['float'],
'xdebug_var_dump' => ['void', 'var'=>''],
'xdiff_file_bdiff' => ['bool', 'old_file'=>'string', 'new_file'=>'string', 'dest'=>'string'],
'xdiff_file_bdiff_size' => ['int', 'file'=>'string'],
'xdiff_file_bpatch' => ['bool', 'file'=>'string', 'patch'=>'string', 'dest'=>'string'],
'xdiff_file_diff' => ['bool', 'old_file'=>'string', 'new_file'=>'string', 'dest'=>'string', 'context='=>'int', 'minimal='=>'bool'],
'xdiff_file_diff_binary' => ['bool', 'old_file'=>'string', 'new_file'=>'string', 'dest'=>'string'],
'xdiff_file_merge3' => ['mixed', 'old_file'=>'string', 'new_file1'=>'string', 'new_file2'=>'string', 'dest'=>'string'],
'xdiff_file_patch' => ['mixed', 'file'=>'string', 'patch'=>'string', 'dest'=>'string', 'flags='=>'int'],
'xdiff_file_patch_binary' => ['bool', 'file'=>'string', 'patch'=>'string', 'dest'=>'string'],
'xdiff_file_rabdiff' => ['bool', 'old_file'=>'string', 'new_file'=>'string', 'dest'=>'string'],
'xdiff_string_bdiff' => ['string', 'old_data'=>'string', 'new_data'=>'string'],
'xdiff_string_bdiff_size' => ['int', 'patch'=>'string'],
'xdiff_string_bpatch' => ['string', 'str'=>'string', 'patch'=>'string'],
'xdiff_string_diff' => ['string', 'old_data'=>'string', 'new_data'=>'string', 'context='=>'int', 'minimal='=>'bool'],
'xdiff_string_diff_binary' => ['string', 'old_data'=>'string', 'new_data'=>'string'],
'xdiff_string_merge3' => ['mixed', 'old_data'=>'string', 'new_data1'=>'string', 'new_data2'=>'string', 'error='=>'string'],
'xdiff_string_patch' => ['string', 'str'=>'string', 'patch'=>'string', 'flags='=>'int', 'error='=>'string'],
'xdiff_string_patch_binary' => ['string', 'str'=>'string', 'patch'=>'string'],
'xdiff_string_rabdiff' => ['string', 'old_data'=>'string', 'new_data'=>'string'],
'xhprof_disable' => ['array'],
'xhprof_enable' => ['void', 'flags='=>'int', 'options='=>'array'],
'xhprof_sample_disable' => ['array'],
'xhprof_sample_enable' => ['void'],
'xml_error_string' => ['string', 'code'=>'int'],
'xml_get_current_byte_index' => ['int', 'parser'=>'resource'],
'xml_get_current_column_number' => ['int', 'parser'=>'resource'],
'xml_get_current_line_number' => ['int', 'parser'=>'resource'],
'xml_get_error_code' => ['int', 'parser'=>'resource'],
'xml_parse' => ['int', 'parser'=>'resource', 'data'=>'string', 'isfinal='=>'bool'],
'xml_parse_into_struct' => ['int', 'parser'=>'resource', 'data'=>'string', '&w_values'=>'array', '&w_index='=>'array'],
'xml_parser_create' => ['resource', 'encoding='=>'string'],
'xml_parser_create_ns' => ['resource', 'encoding='=>'string', 'sep='=>'string'],
'xml_parser_free' => ['bool', 'parser'=>'resource'],
'xml_parser_get_option' => ['mixed', 'parser'=>'resource', 'option'=>'int'],
'xml_parser_set_option' => ['bool', 'parser'=>'resource', 'option'=>'int', 'value'=>'mixed'],
'xml_set_character_data_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'string'],
'xml_set_default_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'string'],
'xml_set_element_handler' => ['bool', 'parser'=>'resource', 'shdl'=>'string', 'ehdl'=>'string'],
'xml_set_end_namespace_decl_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'string'],
'xml_set_external_entity_ref_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'string'],
'xml_set_notation_decl_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'string'],
'xml_set_object' => ['bool', 'parser'=>'resource', 'obj'=>'object'],
'xml_set_processing_instruction_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'string'],
'xml_set_start_namespace_decl_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'string'],
'xml_set_unparsed_entity_decl_handler' => ['bool', 'parser'=>'resource', 'hdl'=>'string'],
'XMLDiff\Base::__construct' => ['void', 'nsname'=>'string'],
'XMLDiff\Base::diff' => ['mixed', 'from'=>'mixed', 'to'=>'mixed'],
'XMLDiff\Base::merge' => ['mixed', 'src'=>'mixed', 'diff'=>'mixed'],
'XMLDiff\DOM::diff' => ['DOMDocument', 'from'=>'DOMDocument', 'to'=>'DOMDocument'],
'XMLDiff\DOM::merge' => ['DOMDocument', 'src'=>'DOMDocument', 'diff'=>'DOMDocument'],
'XMLDiff\File::diff' => ['string', 'from'=>'string', 'to'=>'string'],
'XMLDiff\File::merge' => ['string', 'src'=>'string', 'diff'=>'string'],
'XMLDiff\Memory::diff' => ['string', 'from'=>'string', 'to'=>'string'],
'XMLDiff\Memory::merge' => ['string', 'src'=>'string', 'diff'=>'string'],
'XMLReader::close' => ['bool'],
'XMLReader::expand' => ['DOMNode'],
'XMLReader::getAttribute' => ['string', 'name'=>'string'],
'XMLReader::getAttributeNo' => ['string', 'index'=>'int'],
'XMLReader::getAttributeNs' => ['string', 'name'=>'string', 'namespaceuri'=>'string'],
'XMLReader::getParserProperty' => ['bool', 'property'=>'int'],
'XMLReader::isValid' => ['bool'],
'XMLReader::lookupNamespace' => ['?string', 'prefix'=>'string'],
'XMLReader::moveToAttribute' => ['bool', 'name'=>'string'],
'XMLReader::moveToAttributeNo' => ['bool', 'index'=>'int'],
'XMLReader::moveToAttributeNs' => ['bool', 'localname'=>'string', 'namespaceuri'=>'string'],
'XMLReader::moveToElement' => ['bool'],
'XMLReader::moveToFirstAttribute' => ['bool'],
'XMLReader::moveToNextAttribute' => ['bool'],
'XMLReader::next' => ['bool', 'localname='=>'string'],
'XMLReader::open' => ['bool', 'uri'=>'string', 'encoding='=>'?string', 'options='=>'int'],
'XMLReader::read' => ['bool'],
'XMLReader::readInnerXML' => ['string'],
'XMLReader::readOuterXML' => ['string'],
'XMLReader::readString' => ['string'],
'XMLReader::setParserProperty' => ['bool', 'property'=>'int', 'value'=>'bool'],
'XMLReader::setRelaxNGSchema' => ['bool', 'filename'=>'string'],
'XMLReader::setRelaxNGSchemaSource' => ['bool', 'source'=>'string'],
'XMLReader::setSchema' => ['bool', 'filename'=>'string'],
'XMLReader::XML' => ['bool', 'source'=>'string', 'encoding='=>'?string', 'options='=>'int'],
'xmlrpc_decode' => ['?array', 'xml'=>'string', 'encoding='=>'string'],
'xmlrpc_decode_request' => ['?array', 'xml'=>'string', '&w_method'=>'string', 'encoding='=>'string'],
'xmlrpc_encode' => ['string', 'value'=>'mixed'],
'xmlrpc_encode_request' => ['string', 'method'=>'string', 'params'=>'mixed', 'output_options='=>'array'],
'xmlrpc_get_type' => ['string', 'value'=>'mixed'],
'xmlrpc_is_fault' => ['bool', 'arg'=>'array'],
'xmlrpc_parse_method_descriptions' => ['array', 'xml'=>'string'],
'xmlrpc_server_add_introspection_data' => ['int', 'server'=>'resource', 'desc'=>'array'],
'xmlrpc_server_call_method' => ['string', 'server'=>'resource', 'xml'=>'string', 'user_data'=>'mixed', 'output_options='=>'array'],
'xmlrpc_server_create' => ['resource'],
'xmlrpc_server_destroy' => ['int', 'server'=>'resource'],
'xmlrpc_server_register_introspection_callback' => ['bool', 'server'=>'resource', 'function'=>'string'],
'xmlrpc_server_register_method' => ['bool', 'server'=>'resource', 'method_name'=>'string', 'function'=>'string'],
'xmlrpc_set_type' => ['bool', '&rw_value'=>'string|DateTime', 'type'=>'string'],
'XMLWriter::endAttribute' => ['bool'],
'XMLWriter::endCData' => ['bool'],
'XMLWriter::endComment' => ['bool'],
'XMLWriter::endDocument' => ['bool'],
'XMLWriter::endDTD' => ['bool', 'xmlwriter='=>''],
'XMLWriter::endDTDAttlist' => ['bool'],
'XMLWriter::endDTDElement' => ['bool'],
'XMLWriter::endDTDEntity' => ['bool'],
'XMLWriter::endElement' => ['bool'],
'XMLWriter::endPI' => ['bool'],
'XMLWriter::flush' => ['', 'empty='=>'bool', 'xmlwriter='=>''],
'XMLWriter::fullEndElement' => ['bool'],
'XMLWriter::openMemory' => ['resource'],
'XMLWriter::openURI' => ['resource', 'uri'=>'string'],
'XMLWriter::outputMemory' => ['string', 'flush='=>'bool', 'xmlwriter='=>''],
'XMLWriter::setIndent' => ['bool', 'indent'=>'bool'],
'XMLWriter::setIndentString' => ['bool', 'indentstring'=>'string'],
'XMLWriter::startAttribute' => ['bool', 'name'=>'string'],
'XMLWriter::startAttributeNS' => ['bool', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'],
'XMLWriter::startCData' => ['bool'],
'XMLWriter::startComment' => ['bool'],
'XMLWriter::startDocument' => ['bool', 'version='=>'string', 'encoding='=>'string', 'standalone='=>'string'],
'XMLWriter::startDTD' => ['bool', 'qualifiedname'=>'string', 'publicid='=>'string', 'systemid='=>'string'],
'XMLWriter::startDTDAttlist' => ['bool', 'name'=>'string'],
'XMLWriter::startDTDElement' => ['bool', 'qualifiedname'=>'string'],
'XMLWriter::startDTDEntity' => ['bool', 'name'=>'string', 'isparam'=>'bool'],
'XMLWriter::startElement' => ['bool', 'name'=>'string'],
'XMLWriter::startElementNS' => ['bool', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'],
'XMLWriter::startPI' => ['bool', 'target'=>'string'],
'XMLWriter::text' => ['bool', 'content'=>'string'],
'XMLWriter::writeAttribute' => ['bool', 'name'=>'string', 'value'=>'string'],
'XMLWriter::writeAttributeNS' => ['bool', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'],
'XMLWriter::writeCData' => ['bool', 'content'=>'string'],
'XMLWriter::writeComment' => ['bool', 'content'=>'string'],
'XMLWriter::writeDTD' => ['bool', 'name'=>'string', 'publicid='=>'string', 'systemid='=>'string', 'subset='=>'string'],
'XMLWriter::writeDTDAttlist' => ['bool', 'name'=>'string', 'content'=>'string'],
'XMLWriter::writeDTDElement' => ['bool', 'name'=>'string', 'content'=>'string'],
'XMLWriter::writeDTDEntity' => ['bool', 'name'=>'string', 'content'=>'string', 'pe'=>'bool', 'pubid'=>'string', 'sysid'=>'string', 'ndataid'=>'string'],
'XMLWriter::writeElement' => ['bool', 'name'=>'string', 'content='=>'string'],
'XMLWriter::writeElementNS' => ['bool', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content='=>'string'],
'XMLWriter::writePI' => ['bool', 'target'=>'string', 'content'=>'string'],
'XMLWriter::writeRaw' => ['bool', 'content'=>'string'],
'xmlwriter_end_attribute' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_end_cdata' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_end_comment' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_end_document' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_end_dtd' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_end_dtd_attlist' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_end_dtd_element' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_end_dtd_entity' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_end_element' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_end_pi' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_flush' => ['', 'xmlwriter'=>'resource', 'empty='=>'bool'],
'xmlwriter_full_end_element' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_open_memory' => ['resource'],
'xmlwriter_open_uri' => ['resource', 'source'=>'string'],
'xmlwriter_output_memory' => ['string', 'xmlwriter'=>'resource', 'flush='=>'bool'],
'xmlwriter_set_indent' => ['bool', 'xmlwriter'=>'resource', 'indent'=>'bool'],
'xmlwriter_set_indent_string' => ['bool', 'xmlwriter'=>'resource', 'indentstring'=>'string'],
'xmlwriter_start_attribute' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'],
'xmlwriter_start_attribute_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'],
'xmlwriter_start_cdata' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_start_comment' => ['bool', 'xmlwriter'=>'resource'],
'xmlwriter_start_document' => ['bool', 'xmlwriter'=>'resource', 'version'=>'string', 'encoding'=>'string', 'standalone'=>'string'],
'xmlwriter_start_dtd' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'pubid'=>'string', 'sysid'=>'string'],
'xmlwriter_start_dtd_attlist' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'],
'xmlwriter_start_dtd_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'],
'xmlwriter_start_dtd_entity' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'isparam'=>'bool'],
'xmlwriter_start_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string'],
'xmlwriter_start_element_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string'],
'xmlwriter_start_pi' => ['bool', 'xmlwriter'=>'resource', 'target'=>'string'],
'xmlwriter_text' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'],
'xmlwriter_write_attribute' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'],
'xmlwriter_write_attribute_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'],
'xmlwriter_write_cdata' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'],
'xmlwriter_write_comment' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'],
'xmlwriter_write_dtd' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'pubid'=>'string', 'sysid'=>'string', 'subset'=>'string'],
'xmlwriter_write_dtd_attlist' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'],
'xmlwriter_write_dtd_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'],
'xmlwriter_write_dtd_entity' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string', 'pe'=>'int', 'pubid'=>'string', 'sysid'=>'string', 'ndataid'=>'string'],
'xmlwriter_write_element' => ['bool', 'xmlwriter'=>'resource', 'name'=>'string', 'content'=>'string'],
'xmlwriter_write_element_ns' => ['bool', 'xmlwriter'=>'resource', 'prefix'=>'string', 'name'=>'string', 'uri'=>'string', 'content'=>'string'],
'xmlwriter_write_pi' => ['bool', 'xmlwriter'=>'resource', 'target'=>'string', 'content'=>'string'],
'xmlwriter_write_raw' => ['bool', 'xmlwriter'=>'resource', 'content'=>'string'],
'xpath_new_context' => ['XPathContext', 'dom_document'=>'DOMDocument'],
'xpath_register_ns' => ['bool', 'xpath_context'=>'xpathcontext', 'prefix'=>'string', 'uri'=>'string'],
'xpath_register_ns_auto' => ['bool', 'xpath_context'=>'xpathcontext', 'context_node='=>'object'],
'xptr_new_context' => ['XPathContext'],
'xsl_xsltprocessor_get_parameter' => ['string', 'namespace'=>'string', 'name'=>'string'],
'xsl_xsltprocessor_get_security_prefs' => ['int'],
'xsl_xsltprocessor_has_exslt_support' => ['bool'],
'xsl_xsltprocessor_register_php_functions' => ['', 'restrict'=>''],
'xsl_xsltprocessor_remove_parameter' => ['bool', 'namespace'=>'string', 'name'=>'string'],
'xsl_xsltprocessor_set_parameter' => ['bool', 'namespace'=>'string', 'name'=>'', 'value'=>'string'],
'xsl_xsltprocessor_set_profiling' => ['bool', 'filename'=>'string'],
'xsl_xsltprocessor_set_security_prefs' => ['int', 'securityprefs'=>'int'],
'xsl_xsltprocessor_transform_to_uri' => ['int', 'doc'=>'DOMDocument', 'uri'=>'string'],
'xsl_xsltprocessor_transform_to_xml' => ['string', 'doc'=>'DOMDocument'],
'xslt_backend_info' => ['string'],
'xslt_backend_name' => ['string'],
'xslt_backend_version' => ['string'],
'xslt_create' => ['resource'],
'xslt_errno' => ['int', 'xh'=>''],
'xslt_error' => ['string', 'xh'=>''],
'xslt_free' => ['', 'xh'=>''],
'xslt_getopt' => ['int', 'processor'=>''],
'xslt_process' => ['', 'xh'=>'', 'xmlcontainer'=>'string', 'xslcontainer'=>'string', 'resultcontainer='=>'string', 'arguments='=>'array', 'parameters='=>'array'],
'xslt_set_base' => ['', 'xh'=>'', 'uri'=>'string'],
'xslt_set_encoding' => ['', 'xh'=>'', 'encoding'=>'string'],
'xslt_set_error_handler' => ['', 'xh'=>'', 'handler'=>''],
'xslt_set_log' => ['', 'xh'=>'', 'log='=>''],
'xslt_set_object' => ['bool', 'processor'=>'', 'obj'=>'object'],
'xslt_set_sax_handler' => ['', 'xh'=>'', 'handlers'=>'array'],
'xslt_set_sax_handlers' => ['', 'processor'=>'', 'handlers'=>'array'],
'xslt_set_scheme_handler' => ['', 'xh'=>'', 'handlers'=>'array'],
'xslt_set_scheme_handlers' => ['', 'xh'=>'', 'handlers'=>'array'],
'xslt_setopt' => ['', 'processor'=>'', 'newmask'=>'int'],
'XSLTProcessor::getParameter' => ['string', 'namespaceuri'=>'string', 'localname'=>'string'],
'XsltProcessor::getSecurityPrefs' => ['int'],
'XSLTProcessor::hasExsltSupport' => ['bool'],
'XSLTProcessor::importStylesheet' => ['bool', 'stylesheet'=>'object'],
'XSLTProcessor::registerPHPFunctions' => ['void', 'restrict='=>'mixed'],
'XSLTProcessor::removeParameter' => ['bool', 'namespaceuri'=>'string', 'localname'=>'string'],
'XSLTProcessor::setParameter' => ['bool', 'namespace'=>'string', 'name'=>'string', 'value'=>'string'],
'XSLTProcessor::setParameter\'1' => ['bool', 'namespace'=>'string', 'options'=>'array'],
'XSLTProcessor::setProfiling' => ['bool', 'filename'=>'string'],
'XsltProcessor::setSecurityPrefs' => ['int', 'securityPrefs'=>'int'],
'XSLTProcessor::transformToDoc' => ['DOMDocument', 'doc'=>'DOMNode'],
'XSLTProcessor::transformToURI' => ['int', 'doc'=>'DOMDocument', 'uri'=>'string'],
'XSLTProcessor::transformToXML' => ['string', 'doc'=>'DOMDocument'],
'Yaconf::get' => ['mixed', 'name'=>'string', 'default_value='=>'mixed'],
'Yaconf::has' => ['bool', 'name'=>'string'],
'Yaf_Action_Abstract::__construct' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract', 'view'=>'Yaf_View_Interface', 'invokeArgs='=>'?array'],
'Yaf_Action_Abstract::display' => ['bool', 'tpl'=>'string', 'parameters='=>'?array'],
'Yaf_Action_Abstract::execute' => ['mixed', 'arg='=>'mixed', '...args='=>'mixed'],
'Yaf_Action_Abstract::forward' => ['bool', 'module'=>'string', 'controller='=>'string', 'action='=>'string', 'parameters='=>'?array'],
'Yaf_Action_Abstract::getController' => ['Yaf_Controller_Abstract'],
'Yaf_Action_Abstract::getInvokeArg' => ['mixed|null', 'name'=>'string'],
'Yaf_Action_Abstract::getInvokeArgs' => ['array'],
'Yaf_Action_Abstract::getModuleName' => ['string'],
'Yaf_Action_Abstract::getRequest' => ['Yaf_Request_Abstract'],
'Yaf_Action_Abstract::getResponse' => ['Yaf_Response_Abstract'],
'Yaf_Action_Abstract::getView' => ['Yaf_View_Interface'],
'Yaf_Action_Abstract::getViewpath' => ['string'],
'Yaf_Action_Abstract::init' => [''],
'Yaf_Action_Abstract::initView' => ['Yaf_Response_Abstract', 'options='=>'?array'],
'Yaf_Action_Abstract::redirect' => ['bool', 'url'=>'string'],
'Yaf_Action_Abstract::render' => ['string', 'tpl'=>'string', 'parameters='=>'?array'],
'Yaf_Action_Abstract::setViewpath' => ['bool', 'view_directory'=>'string'],
'Yaf_Application::__clone' => ['void'],
'Yaf_Application::__construct' => ['void', 'config'=>'mixed', 'envrion='=>'string'],
'Yaf_Application::__destruct' => ['void'],
'Yaf_Application::__sleep' => ['void'],
'Yaf_Application::__wakeup' => ['void'],
'Yaf_Application::app' => ['void'],
'Yaf_Application::bootstrap' => ['void', 'bootstrap='=>'Yaf_Bootstrap_Abstract'],
'Yaf_Application::clearLastError' => ['Yaf_Application'],
'Yaf_Application::environ' => ['void'],
'Yaf_Application::execute' => ['void', 'entry'=>'callable', '...args'=>'string'],
'Yaf_Application::getAppDirectory' => ['Yaf_Application'],
'Yaf_Application::getConfig' => ['Yaf_Config_Abstract'],
'Yaf_Application::getDispatcher' => ['Yaf_Dispatcher'],
'Yaf_Application::getLastErrorMsg' => ['string'],
'Yaf_Application::getLastErrorNo' => ['int'],
'Yaf_Application::getModules' => ['array'],
'Yaf_Application::run' => ['void'],
'Yaf_Application::setAppDirectory' => ['Yaf_Application', 'directory'=>'string'],
'Yaf_Config_Abstract::get' => ['mixed', 'name'=>'string', 'value'=>'mixed'],
'Yaf_Config_Abstract::readonly' => ['bool'],
'Yaf_Config_Abstract::set' => ['Yaf_Config_Abstract'],
'Yaf_Config_Abstract::toArray' => ['array'],
'Yaf_Config_Ini::__construct' => ['void', 'config_file'=>'string', 'section='=>'string'],
'Yaf_Config_Ini::__get' => ['void', 'name='=>'string'],
'Yaf_Config_Ini::__isset' => ['void', 'name'=>'string'],
'Yaf_Config_Ini::__set' => ['void', 'name'=>'string', 'value'=>'mixed'],
'Yaf_Config_Ini::count' => ['void'],
'Yaf_Config_Ini::current' => ['void'],
'Yaf_Config_Ini::get' => ['mixed', 'name='=>'mixed'],
'Yaf_Config_Ini::key' => ['void'],
'Yaf_Config_Ini::next' => ['void'],
'Yaf_Config_Ini::offsetExists' => ['void', 'name'=>'string'],
'Yaf_Config_Ini::offsetGet' => ['void', 'name'=>'string'],
'Yaf_Config_Ini::offsetSet' => ['void', 'name'=>'string', 'value'=>'string'],
'Yaf_Config_Ini::offsetUnset' => ['void', 'name'=>'string'],
'Yaf_Config_Ini::readonly' => ['void'],
'Yaf_Config_Ini::rewind' => ['void'],
'Yaf_Config_Ini::set' => ['Yaf_Config_Abstract', 'name'=>'string', 'value'=>'mixed'],
'Yaf_Config_Ini::toArray' => ['array'],
'Yaf_Config_Ini::valid' => ['void'],
'Yaf_Config_Simple::__construct' => ['void', 'config_file'=>'string', 'section='=>'string'],
'Yaf_Config_Simple::__get' => ['void', 'name='=>'string'],
'Yaf_Config_Simple::__isset' => ['void', 'name'=>'string'],
'Yaf_Config_Simple::__set' => ['void', 'name'=>'string', 'value'=>'string'],
'Yaf_Config_Simple::count' => ['void'],
'Yaf_Config_Simple::current' => ['void'],
'Yaf_Config_Simple::get' => ['mixed', 'name='=>'mixed'],
'Yaf_Config_Simple::key' => ['void'],
'Yaf_Config_Simple::next' => ['void'],
'Yaf_Config_Simple::offsetExists' => ['void', 'name'=>'string'],
'Yaf_Config_Simple::offsetGet' => ['void', 'name'=>'string'],
'Yaf_Config_Simple::offsetSet' => ['void', 'name'=>'string', 'value'=>'string'],
'Yaf_Config_Simple::offsetUnset' => ['void', 'name'=>'string'],
'Yaf_Config_Simple::readonly' => ['void'],
'Yaf_Config_Simple::rewind' => ['void'],
'Yaf_Config_Simple::set' => ['Yaf_Config_Abstract', 'name'=>'string', 'value'=>'mixed'],
'Yaf_Config_Simple::toArray' => ['array'],
'Yaf_Config_Simple::valid' => ['void'],
'Yaf_Controller_Abstract::__clone' => ['void'],
'Yaf_Controller_Abstract::__construct' => ['void'],
'Yaf_Controller_Abstract::display' => ['bool', 'tpl'=>'string', 'parameters='=>'array'],
'Yaf_Controller_Abstract::forward' => ['void', 'action'=>'string', 'parameters='=>'array'],
'Yaf_Controller_Abstract::forward\'1' => ['void', 'controller'=>'string', 'action'=>'string', 'parameters='=>'array'],
'Yaf_Controller_Abstract::forward\'2' => ['void', 'module'=>'string', 'controller'=>'string', 'action'=>'string', 'parameters='=>'array'],
'Yaf_Controller_Abstract::getInvokeArg' => ['void', 'name'=>'string'],
'Yaf_Controller_Abstract::getInvokeArgs' => ['void'],
'Yaf_Controller_Abstract::getModuleName' => ['string'],
'Yaf_Controller_Abstract::getRequest' => ['Yaf_Request_Abstract'],
'Yaf_Controller_Abstract::getResponse' => ['Yaf_Response_Abstract'],
'Yaf_Controller_Abstract::getView' => ['Yaf_View_Interface'],
'Yaf_Controller_Abstract::getViewpath' => ['void'],
'Yaf_Controller_Abstract::init' => ['void'],
'Yaf_Controller_Abstract::initView' => ['void', 'options='=>'array'],
'Yaf_Controller_Abstract::redirect' => ['bool', 'url'=>'string'],
'Yaf_Controller_Abstract::render' => ['string', 'tpl'=>'string', 'parameters='=>'array'],
'Yaf_Controller_Abstract::setViewpath' => ['void', 'view_directory'=>'string'],
'Yaf_Dispatcher::__clone' => ['void'],
'Yaf_Dispatcher::__construct' => ['void'],
'Yaf_Dispatcher::__sleep' => ['void'],
'Yaf_Dispatcher::__wakeup' => ['void'],
'Yaf_Dispatcher::autoRender' => ['Yaf_Dispatcher', 'flag='=>'bool'],
'Yaf_Dispatcher::catchException' => ['Yaf_Dispatcher', 'flag='=>'bool'],
'Yaf_Dispatcher::disableView' => ['bool'],
'Yaf_Dispatcher::dispatch' => ['Yaf_Response_Abstract', 'request'=>'Yaf_Request_Abstract'],
'Yaf_Dispatcher::enableView' => ['Yaf_Dispatcher'],
'Yaf_Dispatcher::flushInstantly' => ['Yaf_Dispatcher', 'flag='=>'bool'],
'Yaf_Dispatcher::getApplication' => ['Yaf_Application'],
'Yaf_Dispatcher::getInstance' => ['Yaf_Dispatcher'],
'Yaf_Dispatcher::getRequest' => ['Yaf_Request_Abstract'],
'Yaf_Dispatcher::getRouter' => ['Yaf_Router'],
'Yaf_Dispatcher::initView' => ['Yaf_View_Interface', 'templates_dir'=>'string', 'options='=>'array'],
'Yaf_Dispatcher::registerPlugin' => ['Yaf_Dispatcher', 'plugin'=>'Yaf_Plugin_Abstract'],
'Yaf_Dispatcher::returnResponse' => ['Yaf_Dispatcher', 'flag'=>'bool'],
'Yaf_Dispatcher::setDefaultAction' => ['Yaf_Dispatcher', 'action'=>'string'],
'Yaf_Dispatcher::setDefaultController' => ['Yaf_Dispatcher', 'controller'=>'string'],
'Yaf_Dispatcher::setDefaultModule' => ['Yaf_Dispatcher', 'module'=>'string'],
'Yaf_Dispatcher::setErrorHandler' => ['Yaf_Dispatcher', 'callback'=>'call', 'error_types'=>'int'],
'Yaf_Dispatcher::setRequest' => ['Yaf_Dispatcher', 'request'=>'Yaf_Request_Abstract'],
'Yaf_Dispatcher::setView' => ['Yaf_Dispatcher', 'view'=>'Yaf_View_Interface'],
'Yaf_Dispatcher::throwException' => ['Yaf_Dispatcher', 'flag='=>'bool'],
'Yaf_Exception::__construct' => ['void'],
'Yaf_Exception::getPrevious' => ['void'],
'Yaf_Loader::__clone' => ['void'],
'Yaf_Loader::__construct' => ['void'],
'Yaf_Loader::__sleep' => ['void'],
'Yaf_Loader::__wakeup' => ['void'],
'Yaf_Loader::autoload' => ['void'],
'Yaf_Loader::clearLocalNamespace' => ['void'],
'Yaf_Loader::getInstance' => ['void'],
'Yaf_Loader::getLibraryPath' => ['Yaf_Loader', 'is_global='=>'bool'],
'Yaf_Loader::getLocalNamespace' => ['void'],
'Yaf_Loader::import' => ['void'],
'Yaf_Loader::isLocalName' => ['void'],
'Yaf_Loader::registerLocalNamespace' => ['void', 'prefix'=>'mixed'],
'Yaf_Loader::setLibraryPath' => ['Yaf_Loader', 'directory'=>'string', 'is_global='=>'bool'],
'Yaf_Plugin_Abstract::dispatchLoopShutdown' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'],
'Yaf_Plugin_Abstract::dispatchLoopStartup' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'],
'Yaf_Plugin_Abstract::postDispatch' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'],
'Yaf_Plugin_Abstract::preDispatch' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'],
'Yaf_Plugin_Abstract::preResponse' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'],
'Yaf_Plugin_Abstract::routerShutdown' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'],
'Yaf_Plugin_Abstract::routerStartup' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract'],
'Yaf_Registry::__clone' => ['void'],
'Yaf_Registry::__construct' => ['void'],
'Yaf_Registry::del' => ['void', 'name'=>'string'],
'Yaf_Registry::get' => ['mixed', 'name'=>'string'],
'Yaf_Registry::has' => ['bool', 'name'=>'string'],
'Yaf_Registry::set' => ['bool', 'name'=>'string', 'value'=>'string'],
'Yaf_Request_Abstract::getActionName' => ['void'],
'Yaf_Request_Abstract::getBaseUri' => ['void'],
'Yaf_Request_Abstract::getControllerName' => ['void'],
'Yaf_Request_Abstract::getEnv' => ['void', 'name'=>'string', 'default='=>'string'],
'Yaf_Request_Abstract::getException' => ['void'],
'Yaf_Request_Abstract::getLanguage' => ['void'],
'Yaf_Request_Abstract::getMethod' => ['void'],
'Yaf_Request_Abstract::getModuleName' => ['void'],
'Yaf_Request_Abstract::getParam' => ['void', 'name'=>'string', 'default='=>'string'],
'Yaf_Request_Abstract::getParams' => ['void'],
'Yaf_Request_Abstract::getRequestUri' => ['void'],
'Yaf_Request_Abstract::getServer' => ['void', 'name'=>'string', 'default='=>'string'],
'Yaf_Request_Abstract::isCli' => ['void'],
'Yaf_Request_Abstract::isDispatched' => ['void'],
'Yaf_Request_Abstract::isGet' => ['void'],
'Yaf_Request_Abstract::isHead' => ['void'],
'Yaf_Request_Abstract::isOptions' => ['void'],
'Yaf_Request_Abstract::isPost' => ['void'],
'Yaf_Request_Abstract::isPut' => ['void'],
'Yaf_Request_Abstract::isRouted' => ['void'],
'Yaf_Request_Abstract::isXmlHttpRequest' => ['void'],
'Yaf_Request_Abstract::setActionName' => ['void', 'action'=>'string'],
'Yaf_Request_Abstract::setBaseUri' => ['bool', 'uir'=>'string'],
'Yaf_Request_Abstract::setControllerName' => ['void', 'controller'=>'string'],
'Yaf_Request_Abstract::setDispatched' => ['void'],
'Yaf_Request_Abstract::setModuleName' => ['void', 'module'=>'string'],
'Yaf_Request_Abstract::setParam' => ['void', 'name'=>'string', 'value='=>'string'],
'Yaf_Request_Abstract::setRequestUri' => ['void', 'uir'=>'string'],
'Yaf_Request_Abstract::setRouted' => ['void', 'flag='=>'string'],
'Yaf_Request_Http::__clone' => ['void'],
'Yaf_Request_Http::__construct' => ['void'],
'Yaf_Request_Http::get' => ['mixed', 'name'=>'string', 'default='=>'string'],
'Yaf_Request_Http::getActionName' => ['string'],
'Yaf_Request_Http::getBaseUri' => ['string'],
'Yaf_Request_Http::getControllerName' => ['string'],
'Yaf_Request_Http::getCookie' => ['mixed', 'name'=>'string', 'default='=>'string'],
'Yaf_Request_Http::getEnv' => ['mixed', 'name='=>'string', 'default='=>'mixed'],
'Yaf_Request_Http::getException' => ['Yaf_Exception'],
'Yaf_Request_Http::getFiles' => ['void'],
'Yaf_Request_Http::getLanguage' => ['string'],
'Yaf_Request_Http::getMethod' => ['string'],
'Yaf_Request_Http::getModuleName' => ['string'],
'Yaf_Request_Http::getParam' => ['mixed', 'name'=>'string', 'default='=>'mixed'],
'Yaf_Request_Http::getParams' => ['array'],
'Yaf_Request_Http::getPost' => ['mixed', 'name'=>'string', 'default='=>'string'],
'Yaf_Request_Http::getQuery' => ['mixed', 'name'=>'string', 'default='=>'string'],
'Yaf_Request_Http::getRaw' => ['mixed'],
'Yaf_Request_Http::getRequest' => ['void'],
'Yaf_Request_Http::getRequestUri' => ['string'],
'Yaf_Request_Http::getServer' => ['mixed', 'name='=>'string', 'default='=>'mixed'],
'Yaf_Request_Http::isCli' => ['bool'],
'Yaf_Request_Http::isDispatched' => ['bool'],
'Yaf_Request_Http::isGet' => ['bool'],
'Yaf_Request_Http::isHead' => ['bool'],
'Yaf_Request_Http::isOptions' => ['bool'],
'Yaf_Request_Http::isPost' => ['bool'],
'Yaf_Request_Http::isPut' => ['bool'],
'Yaf_Request_Http::isRouted' => ['bool'],
'Yaf_Request_Http::isXmlHttpRequest' => ['bool'],
'Yaf_Request_Http::setActionName' => ['Yaf_Request_Abstract|bool', 'action'=>'string'],
'Yaf_Request_Http::setBaseUri' => ['bool', 'uri'=>'string'],
'Yaf_Request_Http::setControllerName' => ['Yaf_Request_Abstract|bool', 'controller'=>'string'],
'Yaf_Request_Http::setDispatched' => ['bool'],
'Yaf_Request_Http::setModuleName' => ['Yaf_Request_Abstract|bool', 'module'=>'string'],
'Yaf_Request_Http::setParam' => ['Yaf_Request_Abstract|bool', 'name'=>'array|string', 'value='=>'string'],
'Yaf_Request_Http::setRequestUri' => ['', 'uri'=>'string'],
'Yaf_Request_Http::setRouted' => ['Yaf_Request_Abstract|bool'],
'Yaf_Request_Simple::__clone' => ['void'],
'Yaf_Request_Simple::__construct' => ['void'],
'Yaf_Request_Simple::get' => ['void'],
'Yaf_Request_Simple::getActionName' => ['string'],
'Yaf_Request_Simple::getBaseUri' => ['string'],
'Yaf_Request_Simple::getControllerName' => ['string'],
'Yaf_Request_Simple::getCookie' => ['void'],
'Yaf_Request_Simple::getEnv' => ['mixed', 'name='=>'string', 'default='=>'mixed'],
'Yaf_Request_Simple::getException' => ['Yaf_Exception'],
'Yaf_Request_Simple::getFiles' => ['void'],
'Yaf_Request_Simple::getLanguage' => ['string'],
'Yaf_Request_Simple::getMethod' => ['string'],
'Yaf_Request_Simple::getModuleName' => ['string'],
'Yaf_Request_Simple::getParam' => ['mixed', 'name'=>'string', 'default='=>'mixed'],
'Yaf_Request_Simple::getParams' => ['array'],
'Yaf_Request_Simple::getPost' => ['void'],
'Yaf_Request_Simple::getQuery' => ['void'],
'Yaf_Request_Simple::getRequest' => ['void'],
'Yaf_Request_Simple::getRequestUri' => ['string'],
'Yaf_Request_Simple::getServer' => ['mixed', 'name='=>'string', 'default='=>'mixed'],
'Yaf_Request_Simple::isCli' => ['bool'],
'Yaf_Request_Simple::isDispatched' => ['bool'],
'Yaf_Request_Simple::isGet' => ['bool'],
'Yaf_Request_Simple::isHead' => ['bool'],
'Yaf_Request_Simple::isOptions' => ['bool'],
'Yaf_Request_Simple::isPost' => ['bool'],
'Yaf_Request_Simple::isPut' => ['bool'],
'Yaf_Request_Simple::isRouted' => ['bool'],
'Yaf_Request_Simple::isXmlHttpRequest' => ['void'],
'Yaf_Request_Simple::setActionName' => ['Yaf_Request_Abstract|bool', 'action'=>'string'],
'Yaf_Request_Simple::setBaseUri' => ['bool', 'uri'=>'string'],
'Yaf_Request_Simple::setControllerName' => ['Yaf_Request_Abstract|bool', 'controller'=>'string'],
'Yaf_Request_Simple::setDispatched' => ['bool'],
'Yaf_Request_Simple::setModuleName' => ['Yaf_Request_Abstract|bool', 'module'=>'string'],
'Yaf_Request_Simple::setParam' => ['Yaf_Request_Abstract|bool', 'name'=>'array|string', 'value='=>'string'],
'Yaf_Request_Simple::setRequestUri' => ['', 'uri'=>'string'],
'Yaf_Request_Simple::setRouted' => ['Yaf_Request_Abstract|bool'],
'Yaf_Response_Abstract::__clone' => ['void'],
'Yaf_Response_Abstract::__construct' => ['void'],
'Yaf_Response_Abstract::__destruct' => ['void'],
'Yaf_Response_Abstract::__toString' => ['string'],
'Yaf_Response_Abstract::appendBody' => ['bool', 'content'=>'string', 'key='=>'string'],
'Yaf_Response_Abstract::clearBody' => ['bool', 'key='=>'string'],
'Yaf_Response_Abstract::clearHeaders' => ['void'],
'Yaf_Response_Abstract::getBody' => ['mixed', 'key='=>'string'],
'Yaf_Response_Abstract::getHeader' => ['void'],
'Yaf_Response_Abstract::prependBody' => ['bool', 'content'=>'string', 'key='=>'string'],
'Yaf_Response_Abstract::response' => ['void'],
'Yaf_Response_Abstract::setAllHeaders' => ['void'],
'Yaf_Response_Abstract::setBody' => ['bool', 'content'=>'string', 'key='=>'string'],
'Yaf_Response_Abstract::setHeader' => ['void'],
'Yaf_Response_Abstract::setRedirect' => ['void'],
'Yaf_Response_Cli::__clone' => [''],
'Yaf_Response_Cli::__construct' => ['void'],
'Yaf_Response_Cli::__destruct' => [''],
'Yaf_Response_Cli::__toString' => ['string'],
'Yaf_Response_Cli::appendBody' => ['bool', 'content'=>'string', 'key='=>'string'],
'Yaf_Response_Cli::clearBody' => ['bool', 'key='=>'string'],
'Yaf_Response_Cli::getBody' => ['mixed', 'key='=>'null|string'],
'Yaf_Response_Cli::prependBody' => ['bool', 'content'=>'string', 'key='=>'string'],
'Yaf_Response_Cli::setBody' => ['bool', 'content'=>'string', 'key='=>'string'],
'Yaf_Response_Http::__clone' => [''],
'Yaf_Response_Http::__construct' => ['void'],
'Yaf_Response_Http::__destruct' => [''],
'Yaf_Response_Http::__toString' => ['string'],
'Yaf_Response_Http::appendBody' => ['bool', 'content'=>'string', 'key='=>'string'],
'Yaf_Response_Http::clearBody' => ['bool', 'key='=>'string'],
'Yaf_Response_Http::clearHeaders' => ['Yaf_Response_Abstract|false', 'name='=>'string'],
'Yaf_Response_Http::getBody' => ['mixed', 'key='=>'null|string'],
'Yaf_Response_Http::getHeader' => ['mixed', 'name='=>'string'],
'Yaf_Response_Http::prependBody' => ['bool', 'content'=>'string', 'key='=>'string'],
'Yaf_Response_Http::response' => ['bool'],
'Yaf_Response_Http::setAllHeaders' => ['bool', 'headers'=>'array'],
'Yaf_Response_Http::setBody' => ['bool', 'content'=>'string', 'key='=>'string'],
'Yaf_Response_Http::setHeader' => ['bool', 'name'=>'string', 'value'=>'string', 'replace='=>'bool|false', 'response_code='=>'int'],
'Yaf_Response_Http::setRedirect' => ['bool', 'url'=>'string'],
'Yaf_Route_Interface::__construct' => ['void'],
'Yaf_Route_Interface::assemble' => ['string', 'info'=>'array', 'query='=>'array'],
'Yaf_Route_Interface::route' => ['bool', 'request'=>'Yaf_Request_Abstract'],
'Yaf_Route_Map::__construct' => ['void', 'controller_prefer='=>'string', 'delimiter='=>'string'],
'Yaf_Route_Map::assemble' => ['string', 'info'=>'array', 'query='=>'array'],
'Yaf_Route_Map::route' => ['bool', 'request'=>'Yaf_Request_Abstract'],
'Yaf_Route_Regex::__construct' => ['void', 'match'=>'string', 'route'=>'array', 'map='=>'array', 'verify='=>'array', 'reverse='=>'string'],
'Yaf_Route_Regex::addConfig' => ['Yaf_Router|bool', 'config'=>'Yaf_Config_Abstract'],
'Yaf_Route_Regex::addRoute' => ['Yaf_Router|bool', 'name'=>'string', 'route'=>'Yaf_Route_Interface'],
'Yaf_Route_Regex::assemble' => ['string', 'info'=>'array', 'query='=>'array'],
'Yaf_Route_Regex::getCurrentRoute' => ['string'],
'Yaf_Route_Regex::getRoute' => ['Yaf_Route_Interface', 'name'=>'string'],
'Yaf_Route_Regex::getRoutes' => ['Yaf_Route_Interface[]'],
'Yaf_Route_Regex::route' => ['bool', 'request'=>'Yaf_Request_Abstract'],
'Yaf_Route_Rewrite::__construct' => ['void', 'match'=>'string', 'route'=>'array', 'verify='=>'array'],
'Yaf_Route_Rewrite::addConfig' => ['Yaf_Router|bool', 'config'=>'Yaf_Config_Abstract'],
'Yaf_Route_Rewrite::addRoute' => ['Yaf_Router|bool', 'name'=>'string', 'route'=>'Yaf_Route_Interface'],
'Yaf_Route_Rewrite::assemble' => ['string', 'info'=>'array', 'query='=>'array'],
'Yaf_Route_Rewrite::getCurrentRoute' => ['string'],
'Yaf_Route_Rewrite::getRoute' => ['Yaf_Route_Interface', 'name'=>'string'],
'Yaf_Route_Rewrite::getRoutes' => ['Yaf_Route_Interface[]'],
'Yaf_Route_Rewrite::route' => ['bool', 'request'=>'Yaf_Request_Abstract'],
'Yaf_Route_Simple::__construct' => ['void', 'module_name'=>'string', 'controller_name'=>'string', 'action_name'=>'string'],
'Yaf_Route_Simple::assemble' => ['string', 'info'=>'array', 'query='=>'array'],
'Yaf_Route_Simple::route' => ['bool', 'request'=>'Yaf_Request_Abstract'],
'Yaf_Route_Static::assemble' => ['string', 'info'=>'array', 'query='=>'array'],
'Yaf_Route_Static::match' => ['void', 'uri'=>'string'],
'Yaf_Route_Static::route' => ['bool', 'request'=>'Yaf_Request_Abstract'],
'Yaf_Route_Supervar::__construct' => ['void', 'supervar_name'=>'string'],
'Yaf_Route_Supervar::assemble' => ['string', 'info'=>'array', 'query='=>'array'],
'Yaf_Route_Supervar::route' => ['bool', 'request'=>'Yaf_Request_Abstract'],
'Yaf_Router::__construct' => ['void'],
'Yaf_Router::addConfig' => ['bool', 'config'=>'Yaf_Config_Abstract'],
'Yaf_Router::addRoute' => ['bool', 'name'=>'string', 'route'=>'Yaf_Route_Abstract'],
'Yaf_Router::getCurrentRoute' => ['string'],
'Yaf_Router::getRoute' => ['Yaf_Route_Interface', 'name'=>'string'],
'Yaf_Router::getRoutes' => ['mixed'],
'Yaf_Router::route' => ['bool', 'request'=>'Yaf_Request_Abstract'],
'Yaf_Session::__clone' => ['void'],
'Yaf_Session::__construct' => ['void'],
'Yaf_Session::__get' => ['void', 'name'=>'string'],
'Yaf_Session::__isset' => ['void', 'name'=>'string'],
'Yaf_Session::__set' => ['void', 'name'=>'string', 'value'=>'string'],
'Yaf_Session::__sleep' => ['void'],
'Yaf_Session::__unset' => ['void', 'name'=>'string'],
'Yaf_Session::__wakeup' => ['void'],
'Yaf_Session::count' => ['void'],
'Yaf_Session::current' => ['void'],
'Yaf_Session::del' => ['void', 'name'=>'string'],
'Yaf_Session::get' => ['mixed', 'name'=>'string'],
'Yaf_Session::getInstance' => ['void'],
'Yaf_Session::has' => ['void', 'name'=>'string'],
'Yaf_Session::key' => ['void'],
'Yaf_Session::next' => ['void'],
'Yaf_Session::offsetExists' => ['void', 'name'=>'string'],
'Yaf_Session::offsetGet' => ['void', 'name'=>'string'],
'Yaf_Session::offsetSet' => ['void', 'name'=>'string', 'value'=>'string'],
'Yaf_Session::offsetUnset' => ['void', 'name'=>'string'],
'Yaf_Session::rewind' => ['void'],
'Yaf_Session::set' => ['Yaf_Session|bool', 'name'=>'string', 'value'=>'mixed'],
'Yaf_Session::start' => ['void'],
'Yaf_Session::valid' => ['void'],
'Yaf_View_Interface::assign' => ['bool', 'name'=>'string', 'value='=>'string'],
'Yaf_View_Interface::display' => ['bool', 'tpl'=>'string', 'tpl_vars='=>'array'],
'Yaf_View_Interface::getScriptPath' => ['void'],
'Yaf_View_Interface::render' => ['string', 'tpl'=>'string', 'tpl_vars='=>'array'],
'Yaf_View_Interface::setScriptPath' => ['void', 'template_dir'=>'string'],
'Yaf_View_Simple::__construct' => ['void', 'tempalte_dir'=>'string', 'options='=>'array'],
'Yaf_View_Simple::__get' => ['void', 'name='=>'string'],
'Yaf_View_Simple::__isset' => ['void', 'name'=>'string'],
'Yaf_View_Simple::__set' => ['void', 'name'=>'string', 'value'=>'mixed'],
'Yaf_View_Simple::assign' => ['bool', 'name'=>'string', 'value='=>'mixed'],
'Yaf_View_Simple::assignRef' => ['bool', 'name'=>'string', '&rw_value'=>'mixed'],
'Yaf_View_Simple::clear' => ['bool', 'name='=>'string'],
'Yaf_View_Simple::display' => ['bool', 'tpl'=>'string', 'tpl_vars='=>'array'],
'Yaf_View_Simple::eval' => ['string', 'tpl_content'=>'string', 'tpl_vars='=>'array'],
'Yaf_View_Simple::getScriptPath' => ['string'],
'Yaf_View_Simple::render' => ['string', 'tpl'=>'string', 'tpl_vars='=>'array'],
'Yaf_View_Simple::setScriptPath' => ['bool', 'template_dir'=>'string'],
'yaml_emit' => ['string', 'data'=>'mixed', 'encoding='=>'int', 'linebreak='=>'int'],
'yaml_emit_file' => ['bool', 'filename'=>'string', 'data'=>'mixed', 'encoding='=>'int', 'linebreak='=>'int'],
'yaml_parse' => ['mixed', 'input'=>'string', 'pos='=>'int', 'ndocs='=>'int', 'callbacks='=>'array'],
'yaml_parse_file' => ['mixed', 'filename'=>'string', 'pos='=>'int', 'ndocs='=>'int', 'callbacks='=>'array'],
'yaml_parse_url' => ['mixed', 'url'=>'string', 'pos='=>'int', 'ndocs='=>'int', 'callbacks='=>'array'],
'Yar_Client::__call' => ['void', 'method'=>'string', 'parameters'=>'array'],
'Yar_Client::__construct' => ['void', 'url'=>'string'],
'Yar_Client::setOpt' => ['bool', 'name'=>'int', 'value'=>'mixed'],
'Yar_Client_Exception::getType' => ['void'],
'Yar_Concurrent_Client::call' => ['int', 'uri'=>'string', 'method'=>'string', 'parameters'=>'array', 'callback='=>'callable'],
'Yar_Concurrent_Client::loop' => ['bool', 'callback='=>'callable', 'error_callback='=>'callable'],
'Yar_Concurrent_Client::reset' => ['bool'],
'Yar_Server::__construct' => ['void', 'obj'=>'Object'],
'Yar_Server::handle' => ['bool'],
'Yar_Server_Exception::getType' => ['string'],
'yaz_addinfo' => ['string', 'id'=>'resource'],
'yaz_ccl_conf' => ['void', 'id'=>'resource', 'config'=>'array'],
'yaz_ccl_parse' => ['bool', 'id'=>'resource', 'query'=>'string', 'result'=>'array'],
'yaz_close' => ['bool', 'id'=>'resource'],
'yaz_connect' => ['mixed', 'zurl'=>'string', 'options='=>'mixed'],
'yaz_database' => ['bool', 'id'=>'resource', 'databases'=>'string'],
'yaz_element' => ['bool', 'id'=>'resource', 'elementset'=>'string'],
'yaz_errno' => ['int', 'id'=>'resource'],
'yaz_error' => ['string', 'id'=>'resource'],
'yaz_es' => ['void', 'id'=>'resource', 'type'=>'string', 'args'=>'array'],
'yaz_es_result' => ['array', 'id'=>'resource'],
'yaz_get_option' => ['string', 'id'=>'resource', 'name'=>'string'],
'yaz_hits' => ['int', 'id'=>'resource', 'searchresult='=>'array'],
'yaz_itemorder' => ['void', 'id'=>'resource', 'args'=>'array'],
'yaz_present' => ['bool', 'id'=>'resource'],
'yaz_range' => ['void', 'id'=>'resource', 'start'=>'int', 'number'=>'int'],
'yaz_record' => ['string', 'id'=>'resource', 'pos'=>'int', 'type'=>'string'],
'yaz_scan' => ['void', 'id'=>'resource', 'type'=>'string', 'startterm'=>'string', 'flags='=>'array'],
'yaz_scan_result' => ['array', 'id'=>'resource', 'result='=>'array'],
'yaz_schema' => ['void', 'id'=>'resource', 'schema'=>'string'],
'yaz_search' => ['bool', 'id'=>'resource', 'type'=>'string', 'query'=>'string'],
'yaz_set_option' => ['', 'id'=>'', 'name'=>'string', 'value'=>'string', 'options'=>'array'],
'yaz_sort' => ['void', 'id'=>'resource', 'criteria'=>'string'],
'yaz_syntax' => ['void', 'id'=>'resource', 'syntax'=>'string'],
'yaz_wait' => ['mixed', 'options='=>'array'],
'yp_all' => ['void', 'domain'=>'string', 'map'=>'string', 'callback'=>'string'],
'yp_cat' => ['array', 'domain'=>'string', 'map'=>'string'],
'yp_err_string' => ['string', 'errorcode'=>'int'],
'yp_errno' => ['int'],
'yp_first' => ['array', 'domain'=>'string', 'map'=>'string'],
'yp_get_default_domain' => ['string'],
'yp_master' => ['string', 'domain'=>'string', 'map'=>'string'],
'yp_match' => ['string', 'domain'=>'string', 'map'=>'string', 'key'=>'string'],
'yp_next' => ['array', 'domain'=>'string', 'map'=>'string', 'key'=>'string'],
'yp_order' => ['int', 'domain'=>'string', 'map'=>'string'],
'zem_get_extension_info_by_id' => [''],
'zem_get_extension_info_by_name' => [''],
'zem_get_extensions_info' => [''],
'zem_get_license_info' => [''],
'zend_current_obfuscation_level' => ['int'],
'zend_disk_cache_clear' => ['bool', 'namespace='=>'mixed|string'],
'zend_disk_cache_delete' => ['mixed|null', 'key'=>''],
'zend_disk_cache_fetch' => ['mixed|null', 'key'=>''],
'zend_disk_cache_store' => ['bool', 'key'=>'', 'value'=>'', 'ttl='=>'int|mixed'],
'zend_get_id' => ['array', 'all_ids='=>'all_ids|false'],
'zend_is_configuration_changed' => [''],
'zend_loader_current_file' => ['string'],
'zend_loader_enabled' => ['bool'],
'zend_loader_file_encoded' => ['bool'],
'zend_loader_file_licensed' => ['array'],
'zend_loader_install_license' => ['bool', 'license_file'=>'license_file', 'override'=>'override'],
'zend_logo_guid' => ['string'],
'zend_obfuscate_class_name' => ['string', 'class_name'=>'class_name'],
'zend_obfuscate_function_name' => ['string', 'function_name'=>'function_name'],
'zend_optimizer_version' => ['string'],
'zend_runtime_obfuscate' => ['void'],
'zend_send_buffer' => ['bool', 'buffer'=>'buffer', 'mime_type'=>'mime_type', 'custom_headers'=>'custom_headers'],
'zend_send_file' => ['bool', 'filename'=>'filename', 'mime_type'=>'mime_type', 'custom_headers'=>'custom_headers'],
'zend_set_configuration_changed' => [''],
'zend_shm_cache_clear' => ['bool', 'namespace='=>'mixed|string'],
'zend_shm_cache_delete' => ['mixed|null', 'key'=>''],
'zend_shm_cache_fetch' => ['mixed|null', 'key'=>''],
'zend_shm_cache_store' => ['bool', 'key'=>'', 'value'=>'', 'ttl='=>'int|mixed'],
'zend_thread_id' => ['int'],
'zend_version' => ['string'],
'ZendAPI_Job::addJobToQueue' => ['int', 'jobqueue_url'=>'string', 'password'=>'string'],
'ZendAPI_Job::getApplicationID' => [''],
'ZendAPI_Job::getEndTime' => [''],
'ZendAPI_Job::getGlobalVariables' => [''],
'ZendAPI_Job::getHost' => [''],
'ZendAPI_Job::getID' => [''],
'ZendAPI_Job::getInterval' => [''],
'ZendAPI_Job::getJobDependency' => [''],
'ZendAPI_Job::getJobName' => [''],
'ZendAPI_Job::getJobPriority' => [''],
'ZendAPI_Job::getJobStatus' => ['int'],
'ZendAPI_Job::getLastPerformedStatus' => ['int'],
'ZendAPI_Job::getOutput' => ['An'],
'ZendAPI_Job::getPreserved' => [''],
'ZendAPI_Job::getProperties' => ['array'],
'ZendAPI_Job::getScheduledTime' => [''],
'ZendAPI_Job::getScript' => [''],
'ZendAPI_Job::getTimeToNextRepeat' => ['int'],
'ZendAPI_Job::getUserVariables' => [''],
'ZendAPI_Job::setApplicationID' => ['', 'app_id'=>''],
'ZendAPI_Job::setGlobalVariables' => ['', 'vars'=>''],
'ZendAPI_Job::setJobDependency' => ['', 'job_id'=>''],
'ZendAPI_Job::setJobName' => ['', 'name'=>''],
'ZendAPI_Job::setJobPriority' => ['', 'priority'=>'int'],
'ZendAPI_Job::setPreserved' => ['', 'preserved'=>''],
'ZendAPI_Job::setRecurrenceData' => ['', 'interval'=>'', 'end_time='=>'mixed'],
'ZendAPI_Job::setScheduledTime' => ['', 'timestamp'=>''],
'ZendAPI_Job::setScript' => ['', 'script'=>''],
'ZendAPI_Job::setUserVariables' => ['', 'vars'=>''],
'ZendAPI_Job::ZendAPI_Job' => ['Job', 'script'=>'script'],
'ZendAPI_Queue::addJob' => ['int', '&job'=>'Job'],
'ZendAPI_Queue::getAllApplicationIDs' => ['array'],
'ZendAPI_Queue::getAllhosts' => ['array'],
'ZendAPI_Queue::getHistoricJobs' => ['array', 'status'=>'int', 'start_time'=>'', 'end_time'=>'', 'index'=>'int', 'count'=>'int', '&total'=>'int'],
'ZendAPI_Queue::getJob' => ['Job', 'job_id'=>'int'],
'ZendAPI_Queue::getJobsInQueue' => ['array', 'filter_options='=>'array', 'max_jobs='=>'int', 'with_globals_and_output='=>'bool|false'],
'ZendAPI_Queue::getLastError' => ['string'],
'ZendAPI_Queue::getNumOfJobsInQueue' => ['int', 'filter_options='=>'array'],
'ZendAPI_Queue::getStatistics' => ['array'],
'ZendAPI_Queue::isScriptExists' => ['bool', 'path'=>'string'],
'ZendAPI_Queue::isSuspend' => ['bool'],
'ZendAPI_Queue::login' => ['bool', 'password'=>'string', 'application_id='=>'int'],
'ZendAPI_Queue::removeJob' => ['bool', 'job_id'=>'array|int'],
'ZendAPI_Queue::requeueJob' => ['bool', 'job'=>'Job'],
'ZendAPI_Queue::resumeJob' => ['bool', 'job_id'=>'array|int'],
'ZendAPI_Queue::resumeQueue' => ['bool'],
'ZendAPI_Queue::setMaxHistoryTime' => ['bool'],
'ZendAPI_Queue::suspendJob' => ['bool', 'job_id'=>'array|int'],
'ZendAPI_Queue::suspendQueue' => ['bool'],
'ZendAPI_Queue::updateJob' => ['int', '&job'=>'Job'],
'ZendAPI_Queue::zendapi_queue' => ['ZendAPI_Queue', 'queue_url'=>'string'],
'zip_close' => ['void', 'zip'=>'resource'],
'zip_entry_close' => ['bool', 'zip_ent'=>'resource'],
'zip_entry_compressedsize' => ['int', 'zip_entry'=>'resource'],
'zip_entry_compressionmethod' => ['string', 'zip_entry'=>'resource'],
'zip_entry_filesize' => ['int', 'zip_entry'=>'resource'],
'zip_entry_name' => ['string', 'zip_entry'=>'resource'],
'zip_entry_open' => ['bool', 'zip_dp'=>'resource', 'zip_entry'=>'resource', 'mode='=>'string'],
'zip_entry_read' => ['string', 'zip_entry'=>'resource', 'len='=>'int'],
'zip_open' => ['resource', 'filename'=>'string'],
'zip_read' => ['resource', 'zip'=>'resource'],
'ZipArchive::addEmptyDir' => ['bool', 'dirname'=>'string'],
'ZipArchive::addFile' => ['bool', 'filepath'=>'string', 'entryname='=>'string', 'start='=>'int', 'length='=>'int'],
'ZipArchive::addFromString' => ['bool', 'entryname'=>'string', 'content'=>'string'],
'ZipArchive::addGlob' => ['bool', 'pattern'=>'string', 'flags='=>'int', 'options='=>'array'],
'ZipArchive::addPattern' => ['bool', 'pattern'=>'string', 'path='=>'string', 'options='=>'array'],
'ZipArchive::close' => ['bool'],
'ZipArchive::count' => ['int'],
'ZipArchive::createEmptyDir' => ['bool', 'dirname'=>'string'],
'ZipArchive::deleteIndex' => ['bool', 'index'=>'int'],
'ZipArchive::deleteName' => ['bool', 'name'=>'string'],
'ZipArchive::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string[]|string'],
'ZipArchive::getArchiveComment' => ['string', 'flags='=>'int'],
'ZipArchive::getCommentIndex' => ['string', 'index'=>'int', 'flags='=>'int'],
'ZipArchive::getCommentName' => ['string', 'name'=>'string', 'flags='=>'int'],
'ZipArchive::getExternalAttributesIndex' => ['bool', 'index'=>'int', '&w_opsys'=>'int', '&w_attr'=>'int', 'flags='=>'int'],
'ZipArchive::getExternalAttributesName' => ['bool', 'name'=>'string', '&w_opsys'=>'int', '&w_attr'=>'int', 'flags='=>'int'],
'ZipArchive::getFromIndex' => ['string', 'index'=>'int', 'len='=>'int', 'flags='=>'int'],
'ZipArchive::getFromName' => ['string', 'entryname'=>'string', 'len='=>'int', 'flags='=>'int'],
'ZipArchive::getNameIndex' => ['string', 'index'=>'int', 'flags='=>'int'],
'ZipArchive::getStatusString' => ['string'],
'ZipArchive::getStream' => ['resource', 'entryname'=>'string'],
'ZipArchive::locateName' => ['int', 'filename'=>'string', 'flags='=>'int'],
'ZipArchive::open' => ['mixed', 'source'=>'string', 'flags='=>'int'],
'ZipArchive::renameIndex' => ['bool', 'index'=>'int', 'new_name'=>'string'],
'ZipArchive::renameName' => ['bool', 'name'=>'string', 'new_name'=>'string'],
'ZipArchive::setArchiveComment' => ['bool', 'comment'=>'string'],
'ZipArchive::setCommentIndex' => ['bool', 'index'=>'int', 'comment'=>'string'],
'ZipArchive::setCommentName' => ['bool', 'name'=>'string', 'comment'=>'string'],
'ZipArchive::setCompressionIndex' => ['bool', 'index'=>'int', 'comp_method'=>'int', 'comp_flags='=>'int'],
'ZipArchive::setCompressionName' => ['bool', 'name'=>'string', 'comp_method'=>'int', 'comp_flags='=>'int'],
'ZipArchive::setEncryptionIndex' => ['bool', 'index'=>'int', 'method'=>'string', 'password='=>'string'],
'ZipArchive::setEncryptionName' => ['bool', 'name'=>'string', 'method'=>'int', 'password='=>'string'],
'ZipArchive::setExternalAttributesIndex' => ['bool', 'index'=>'int', 'opsys'=>'int', 'attr'=>'int', 'flags='=>'int'],
'ZipArchive::setExternalAttributesName' => ['bool', 'name'=>'string', 'opsys'=>'int', 'attr'=>'int', 'flags='=>'int'],
'ZipArchive::setPassword' => ['bool', 'password'=>'string'],
'ZipArchive::statIndex' => ['array|false', 'index'=>'int', 'flags='=>'int'],
'ZipArchive::statName' => ['array|false', 'filename'=>'string', 'flags='=>'int'],
'ZipArchive::unchangeAll' => ['bool'],
'ZipArchive::unchangeArchive' => ['bool'],
'ZipArchive::unchangeIndex' => ['bool', 'index'=>'int'],
'ZipArchive::unchangeName' => ['bool', 'name'=>'string'],
'zlib_decode' => ['string', 'data'=>'string', 'max_decoded_len='=>'int'],
'zlib_encode' => ['string', 'data'=>'string', 'encoding'=>'int', 'level='=>'string|int'],
'zlib_get_coding_type' => ['string|false'],
'ZMQ::__construct' => ['void'],
'ZMQContext::__construct' => ['void', 'io_threads='=>'int', 'is_persistent='=>'bool'],
'ZMQContext::getOpt' => ['mixed', 'key'=>'string'],
'ZMQContext::getSocket' => ['ZMQSocket', 'type'=>'int', 'persistent_id='=>'string', 'on_new_socket='=>'callable'],
'ZMQContext::isPersistent' => ['bool'],
'ZMQContext::setOpt' => ['ZMQContext', 'key'=>'int', 'value'=>'mixed'],
'ZMQDevice::__construct' => ['void', 'frontend'=>'ZMQSocket', 'backend'=>'ZMQSocket', 'listener='=>'ZMQSocket'],
'ZMQDevice::getIdleTimeout' => ['ZMQDevice'],
'ZMQDevice::getTimerTimeout' => ['ZMQDevice'],
'ZMQDevice::run' => ['void'],
'ZMQDevice::setIdleCallback' => ['ZMQDevice', 'cb_func'=>'callable', 'timeout'=>'int', 'user_data='=>'mixed'],
'ZMQDevice::setIdleTimeout' => ['ZMQDevice', 'timeout'=>'int'],
'ZMQDevice::setTimerCallback' => ['ZMQDevice', 'cb_func'=>'callable', 'timeout'=>'int', 'user_data='=>'mixed'],
'ZMQDevice::setTimerTimeout' => ['ZMQDevice', 'timeout'=>'int'],
'ZMQPoll::add' => ['string', 'entry'=>'mixed', 'type'=>'int'],
'ZMQPoll::clear' => ['ZMQPoll'],
'ZMQPoll::count' => ['int'],
'ZMQPoll::getLastErrors' => ['array'],
'ZMQPoll::poll' => ['int', '&w_readable'=>'array', '&w_writable'=>'array', 'timeout='=>'int'],
'ZMQPoll::remove' => ['bool', 'item'=>'mixed'],
'ZMQSocket::__construct' => ['void', 'context'=>'ZMQContext', 'type'=>'int', 'persistent_id='=>'string', 'on_new_socket='=>'callable'],
'ZMQSocket::bind' => ['ZMQSocket', 'dsn'=>'string', 'force='=>'bool'],
'ZMQSocket::connect' => ['ZMQSocket', 'dsn'=>'string', 'force='=>'bool'],
'ZMQSocket::disconnect' => ['ZMQSocket', 'dsn'=>'string'],
'ZMQSocket::getEndpoints' => ['array'],
'ZMQSocket::getPersistentId' => ['string'],
'ZMQSocket::getSocketType' => ['int'],
'ZMQSocket::getSockOpt' => ['mixed', 'key'=>'string'],
'ZMQSocket::isPersistent' => ['bool'],
'ZMQSocket::recv' => ['string', 'mode='=>'int'],
'ZMQSocket::recvMulti' => ['string', 'mode='=>'int'],
'ZMQSocket::send' => ['ZMQSocket', 'message'=>'array', 'mode='=>'int'],
'ZMQSocket::send\'1' => ['ZMQSocket', 'message'=>'string', 'mode='=>'int'],
'ZMQSocket::sendmulti' => ['ZMQSocket', 'message'=>'array', 'mode='=>'int'],
'ZMQSocket::setSockOpt' => ['ZMQSocket', 'key'=>'int', 'value'=>'mixed'],
'ZMQSocket::unbind' => ['ZMQSocket', 'dsn'=>'string'],
'Zookeeper::addAuth' => ['bool', 'scheme'=>'string', 'cert'=>'string', 'completion_cb='=>'callable'],
'Zookeeper::connect' => ['void', 'host'=>'string', 'watcher_cb='=>'callable', 'recv_timeout='=>'int'],
'Zookeeper::create' => ['string', 'path'=>'string', 'value'=>'string', 'acls'=>'array', 'flags='=>'int'],
'Zookeeper::delete' => ['bool', 'path'=>'string', 'version='=>'int'],
'Zookeeper::exists' => ['bool', 'path'=>'string', 'watcher_cb='=>'callable'],
'Zookeeper::get' => ['string', 'path'=>'string', 'watcher_cb='=>'callable', 'stat='=>'array', 'max_size='=>'int'],
'Zookeeper::getAcl' => ['array', 'path'=>'string'],
'Zookeeper::getChildren' => ['array', 'path'=>'string', 'watcher_cb='=>'callable'],
'Zookeeper::getClientId' => ['int'],
'Zookeeper::getRecvTimeout' => ['int'],
'Zookeeper::getState' => ['int'],
'Zookeeper::isRecoverable' => ['bool'],
'Zookeeper::set' => ['bool', 'path'=>'string', 'value'=>'string', 'version='=>'int', 'stat='=>'array'],
'Zookeeper::setAcl' => ['bool', 'path'=>'string', 'version'=>'int', 'acl'=>'array'],
'Zookeeper::setDebugLevel' => ['bool', 'logLevel'=>'int'],
'Zookeeper::setDeterministicConnOrder' => ['bool', 'yesOrNo'=>'bool'],
'Zookeeper::setLogStream' => ['bool', 'stream'=>'resource'],
'Zookeeper::setWatcher' => ['bool', 'watcher_cb'=>'callable'],
'zookeeper_dispatch' => ['void'],
];
<?php declare(strict_types=1);
namespace Phan\Language\Internal;

$ordinary_ast_node = 'ast\Node|float|int|string';
$ast_node_shape_inner = \implode(',', [
    "args?:ast\Node",
    "class?:ast\Node",
    "cond?:$ordinary_ast_node",
    "const?:string",
    "dim?:$ordinary_ast_node",
    "docComment?:?string",
    "expr?:$ordinary_ast_node",
    "key?:$ordinary_ast_node",
    "left?:$ordinary_ast_node",
    "name?:$ordinary_ast_node",
    "right?:$ordinary_ast_node",
    "var?:ast\Node",
    "value?:$ordinary_ast_node",
    "stmts?:?ast\Node",
]);

$ast_node_children_types = 'array{' . $ast_node_shape_inner . '}|ast\Node[]|array[]|int[]|string[]|float[]|bool[]|null[]';

/**
 * A mapping from class name to property name to property type.
 *
 * # Format
 *
 * This structure is of the form
 * [ class_name => [ property_name => type ] ]
 *
 * # How to Generate
 *
 * ```sh
 * svn checkout https://svn.php.net/repository/phpdoc/en/trunk phpdoc;
 * cd phpdoc;
 * find . -type f -path '*.xml -exec cat {} \; | tr "\n" " " | grep -o "<type>[^<]*<\/type>\s*<varname linkend=\"[^\.]*\.props.[^\"]*\"" | while read l; do T=`echo $l | cut -d ">" -f2 | cut -d "<" -f1`; N=`echo $l | cut -d "\"" -f2 | cut -d "." -f1,3`; printf "$T $N\n"; done | tee types
 * ```
 *
 * and then pipe that through
 *
 * ```perl
 * #!/usr/bin/perl
 * my %classes = {};
 * while(<>) {
 *     chomp;
 *     if (/^([^ ]*) ([^\.]*)\.(.*)$/) {
 *         my $type = $1;
 *         my $class = $2;
 *         my $property = $3;
 *         push(@{ $classes{$class} }, "'$property' => '$type'");
 *     } else {
 *         print STDERR "FAIL\n";
 *     }
 * }
 * while (($class, $details) = each %classes) {
 *     print "    '$class' => [\n        ";
 *     print join(",\n        ", @{ $details });
 *     print "\n    ],\n";
 * }
 * ```
 */
return [
    'arrayobject' => [
        'name' => 'string'
    ],
    'limititerator' => [
        'name' => 'string'
    ],
    'solrdocumentfield' => [
        'name' => 'string',
        'boost' => 'float',
        'values' => 'array'
    ],
    'domprocessinginstruction' => [
        'target' => 'string',
        'data' => 'string'
    ],
    'recursivearrayiterator' => [
        'name' => 'string'
    ],
    'eventbuffer' => [
        'length' => 'int',
        'contiguous_space' => 'int'
    ],
    'mongocursor' => [
        'slaveokay' => 'bool',
        'timeout' => 'int'
    ],
    'domxpath' => [
        'document' => 'DOMDocument'
    ],
    'domentity' => [
        'publicId' => 'string',
        'systemId' => 'string',
        'notationName' => 'string',
        'actualEncoding' => 'string',
        'encoding' => 'string',
        'version' => 'string'
    ],
    'splminheap' => [
        'name' => 'string'
    ],
    'mongodb\driver\exception\writeexception' => [
        'writeresult' => 'MongoDBDriverWriteResult'
    ],
    'ziparchive' => [
        'status' => 'int',
        'statussys' => 'int',
        'numfiles' => 'int',
        'filename' => 'string',
        'comment' => 'string'
    ],
    'solrexception' => [
        'sourceline' => 'int',
        'sourcefile' => 'string',
        'zif_name' => 'string'
    ],
    'arrayiterator' => [
        'name' => 'string'
    ],
    'mongoid' => [
        'id' => 'string'
    ],
    'dateinterval' => [
        'y' => 'int',
        'm' => 'int',
        'd' => 'int',
        'h' => 'int',
        'i' => 'int',
        's' => 'int',
        'invert' => 'int',
        'days' => 'mixed'
    ],
    'tokyotyrantexception' => [
        'code' => 'int'
    ],
    'tidy' => [
        'errorBuffer' => 'string'
    ],
    'filteriterator' => [
        'name' => 'string'
    ],
    'parentiterator' => [
        'name' => 'string'
    ],
    'recursiveregexiterator' => [
        'name' => 'string'
    ],
    'error' => [
        'message' => 'string',
        'code' => 'int',
        'file' => 'string',
        'line' => 'int'
    ],
    'domexception' => [
        'code' => 'int'
    ],
    'domentityreference' => [
        'name' => 'string'
    ],
    'spldoublylinkedlist' => [
        'name' => 'string'
    ],
    'domdocumentfragment' => [
        'name' => 'string'
    ],
    'collator' => [
        'name' => 'string'
    ],
    'streamwrapper' => [
        'context' => 'resource'
    ],
    'pdostatement' => [
        'querystring' => 'string'
    ],
    'domnotation' => [
        'publicId' => 'string',
        'systemId' => 'string'
    ],
    'snmpexception' => [
        'code' => 'string'
    ],
    'directoryiterator' => [
        'name' => 'string'
    ],
    'splqueue' => [
        'name' => 'string'
    ],
    'locale' => [
        'name' => 'string'
    ],
    'directory' => [
        'path' => 'string',
        'handle' => 'resource'
    ],
    'splheap' => [
        'name' => 'string'
    ],
    'domnodelist' => [
        'length' => 'int'
    ],
    'mongodb' => [
        'w' => 'int',
        'wtimeout' => 'int'
    ],
    'splpriorityqueue' => [
        'name' => 'string'
    ],
    'mongoclient' => [
        'connected' => 'bool',
        'status' => 'string'
    ],
    'domdocument' => [
        'actualEncoding' => 'string',
        'config' => 'DOMConfiguration',
        'doctype' => 'DOMDocumentType',
        'documentElement' => 'DOMElement',
        'documentURI' => 'string',
        'encoding' => 'string',
        'formatOutput' => 'bool',
        'implementation' => 'DOMImplementation',
        'preserveWhiteSpace' => 'bool',
        'recover' => 'bool',
        'resolveExternals' => 'bool',
        'standalone' => 'bool',
        'strictErrorChecking' => 'bool',
        'substituteEntities' => 'bool',
        'validateOnParse' => 'bool',
        'version' => 'string',
        'xmlEncoding' => 'string',
        'xmlStandalone' => 'bool',
        'xmlVersion' => 'string'
    ],
    'libxmlerror' => [
        'level' => 'int',
        'code' => 'int',
        'column' => 'int',
        'message' => 'string',
        'file' => 'string',
        'line' => 'int'
    ],
    'domimplementation' => [
        'name' => 'string'
    ],
    'normalizer' => [
        'name' => 'string'
    ],
    'mysqli_driver' => [
        'client_info' => 'string',
        'client_version' => 'string',
        'driver_version' => 'string',
        'embedded' => 'string',
        'reconnect' => 'bool',
        'report_mode' => 'int'
    ],
    'norewinditerator' => [
        'name' => 'string'
    ],
    'event' => [
        'pending' => 'bool'
    ],
    'domdocumenttype' => [
        'publicId' => 'string',
        'systemId' => 'string',
        'name' => 'string',
        'entities' => 'DOMNamedNodeMap',
        'notations' => 'DOMNamedNodeMap',
        'internalSubset' => 'string'
    ],
    'errorexception' => [
        'severity' => 'int'
    ],
    'recursivedirectoryiterator' => [
        'name' => 'string'
    ],
    'domcharacterdata' => [
        'data' => 'string',
        'length' => 'int'
    ],
    'mongocollection' => [
        'db' => 'MongoDB',
        'w' => 'int',
        'wtimeout' => 'int'
    ],
    'mongoint64' => [
        'value' => 'string'
    ],
    'eventlistener' => [
        'fd' => 'int'
    ],
    'splmaxheap' => [
        'name' => 'string'
    ],
    'regexiterator' => [
        'name' => 'string'
    ],
    'domelement' => [
        'schemaTypeInfo' => 'bool',
        'tagName' => 'string'
    ],
    'tidynode' => [
        'value' => 'string',
        'name' => 'string',
        'type' => 'int',
        'line' => 'int',
        'column' => 'int',
        'proprietary' => 'bool',
        'id' => 'int',
        'attribute' => 'array',
        'child' => 'array'
    ],
    'recursivecachingiterator' => [
        'name' => 'string'
    ],
    'solrresponse' => [
        'http_status' => 'int',
        'parser_mode' => 'int',
        'success' => 'bool',
        'http_status_message' => 'string',
        'http_request_url' => 'string',
        'http_raw_request_headers' => 'string',
        'http_raw_request' => 'string',
        'http_raw_response_headers' => 'string',
        'http_raw_response' => 'string',
        'http_digested_response' => 'string'
    ],
    'domnamednodemap' => [
        'length' => 'int'
    ],
    'mysqli_sql_exception' => [
        'sqlstate' => 'string'
    ],
    'splstack' => [
        'name' => 'string'
    ],
    'numberformatter' => [
        'name' => 'string'
    ],
    'eventsslcontext' => [
        'local_cert' => 'string',
        'local_pk' => 'string'
    ],
    'pdoexception' => [
        'errorinfo' => 'array',
        'code' => 'string'
    ],
    'domnode' => [
        'nodeName' => 'string',
        'nodeValue' => 'string',
        'nodeType' => 'int',
        'parentNode' => 'DOMNode',
        'childNodes' => 'DOMNodeList',
        'firstChild' => 'DOMNode',
        'lastChild' => 'DOMNode',
        'previousSibling' => 'DOMNode',
        'nextSibling' => 'DOMNode',
        'attributes' => 'DOMNamedNodeMap',
        'ownerDocument' => 'DOMDocument',
        'namespaceURI' => 'string',
        'prefix' => 'string',
        'localName' => 'string',
        'baseURI' => 'string',
        'textContent' => 'string'
    ],
    'domattr' => [
        'name' => 'string',
        'ownerElement' => 'DOMElement',
        'schemaTypeInfo' => 'bool',
        'specified' => 'bool',
        'value' => 'string'
    ],
    'simplexmliterator' => [
        'name' => 'string'
    ],
    'snmp' => [
        'max_oids' => 'int',
        'valueretrieval' => 'int',
        'quick_print' => 'bool',
        'enum_print' => 'bool',
        'oid_output_format' => 'int',
        'oid_increasing_check' => 'bool',
        'exceptions_enabled' => 'int',
        'info' => 'array'
    ],
    'mongoint32' => [
        'value' => 'string'
    ],
    'xmlreader' => [
        'attributeCount' => 'int',
        'baseURI' => 'string',
        'depth' => 'int',
        'hasAttributes' => 'bool',
        'hasValue' => 'bool',
        'isDefault' => 'bool',
        'isEmptyElement' => 'bool',
        'localName' => 'string',
        'name' => 'string',
        'namespaceURI' => 'string',
        'nodeType' => 'int',
        'prefix' => 'string',
        'value' => 'string',
        'xmlLang' => 'string'
    ],
    'eventbufferevent' => [
        'fd' => 'int',
        'priority' => 'int',
        'input' => 'EventBuffer',
        'output' => 'EventBuffer'
    ],
    'domtext' => [
        'wholeText' => 'string'
    ],
    'exception' => [
        'message' => 'string',
        'code' => 'int',
        'file' => 'string',
        'line' => 'int'
    ],
    // TODO: Could use array shape types to document ast\Node->children in more detail.
    'ast\node' => [
        'kind' => 'int',
        'flags' => 'int',
        'lineno' => 'int',
        'children' => $ast_node_children_types,  // NOTE: in the latest version, this is consistently an array, even for edge cases like statement lists of single statements.
        'endLineno' => 'int',
    ],
    'ast\node\decl' => [
        'kind' => 'int',
        'flags' => 'int',
        'lineno' => 'int',
        'children' => $ast_node_children_types,  // NOTE: in the latest version, this is consistently an array, even for edge cases like statement lists of single statements.
        'endLineno' => 'int',
        'name' => '?string',
        'docComment' => '?string',
    ],
    'ast\metadata' => [
        'kind' => 'int',
        'name' => 'string',
        'flags' => 'array<int,string>',
        'flagsCombinable' => 'bool',
    ],
];
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Element\Parameter;
use Phan\Language\UnionType;
use Phan\Language\FileRef;

/**
 * Not a type, but used by ClosureDeclarationType
 */
final class ClosureDeclarationParameter
{
    /** @var UnionType */
    private $type;

    /** @var bool */
    private $is_variadic;

    /** @var bool */
    private $is_reference;

    /** @var bool */
    private $is_optional;

    public function __construct(UnionType $type, bool $is_variadic, bool $is_reference, bool $is_optional)
    {
        $this->type = $type;
        $this->is_variadic = $is_variadic;
        $this->is_reference = $is_reference;
        $this->is_optional = $is_optional || $is_variadic;
    }

    /**
     * @suppress PhanUnreferencedPublicMethod
     */
    public function getNonVariadicUnionType() : UnionType
    {
        return $this->type;
    }

    public function isVariadic() : bool
    {
        return $this->is_variadic;
    }

    public function isPassByReference() : bool
    {
        return $this->is_reference;
    }

    public function isOptional() : bool
    {
        return $this->is_optional;
    }

    // for debugging
    public function __toString() : string
    {
        $repr = $this->type->__toString();
        if ($this->is_reference) {
            $repr .= '&';
        }
        if ($this->is_variadic) {
            $repr .= '...';
        }
        if ($this->is_optional && !$this->is_variadic) {
            $repr .= '=';
        }
        return $repr;
    }

    /**
     * @see \Phan\Analysis\ParameterTypesAnalyzer::analyzeOverrideSignatureForOverriddenMethod() - Similar logic using LSP
     */
    public function canCastToParameterIgnoringVariadic(ClosureDeclarationParameter $other) : bool
    {
        if ($this->is_reference !== $other->is_reference) {
            return false;
        }
        if (!$this->is_optional && $other->is_optional) {
            // We should have already checked this
            return false;
        }
        // TODO: stricter? (E.g. shouldn't allow int|string to cast to int)
        return $this->type->canCastToUnionType($other->type);
    }

    // TODO: Memoize?
    public function asNonVariadicRegularParameter(int $i) : Parameter
    {
        $flags = 0;
        // Skip variadic
        if ($this->is_reference) {
            $flags |= \ast\flags\PARAM_REF;
        }
        $result = Parameter::create(
            (new FileRef())->withFile('phpdoc'),
            "p$i",
            $this->type,
            $flags
        );
        if ($this->is_optional && !$this->is_variadic) {
            $result->setDefaultValueType($this->type);
        }
        return $result;
    }

    public function asRegularParameter(int $i) : Parameter
    {
        $flags = 0;
        if ($this->is_variadic) {
            $flags |= \ast\flags\PARAM_VARIADIC;
        }
        if ($this->is_reference) {
            $flags |= \ast\flags\PARAM_REF;
        }
        $result = Parameter::create(
            (new FileRef())->withFile('phpdoc'),
            "p$i",
            $this->type,
            $flags
        );
        if ($this->is_optional && !$this->is_variadic) {
            $result->setDefaultValueType(MixedType::instance(false)->asUnionType());
        }
        return $result;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Element\FunctionInterface;
use Phan\Language\FQSEN;
use Phan\Language\Type;

final class ClosureType extends Type
{
    /** Not an override */
    const NAME = 'Closure';

    /**
     * @var FQSEN|null
     */
    private $fqsen;

    /**
     * @var ?FunctionInterface (Kept to check for type compatibility)
     * NOTE: We may use class FQSENs (for __invoke) or Method FQSENs if the closure was created via reflection
     */
    private $func;

    // Same as instance(), but guaranteed not to have memoized state.
    private static function closureInstance() : ClosureType
    {
        static $instance = null;
        if ($instance === null) {
            $instance = self::make('\\', self::NAME, [], false, self::FROM_NODE);
        }
        return $instance;
    }

    public static function instanceWithClosureFQSEN(FQSEN $fqsen, FunctionInterface $func = null)
    {
        static $original_instance = null;
        if ($original_instance === null) {
            $original_instance = self::closureInstance();
        }
        // Use an instance with no memoized or lazily initialized results.
        // Avoids picking up changes to ClosureType::instance(false) in the case that a result depends on asFQSEN()
        $instance = clone($original_instance);
        $instance->fqsen = $fqsen;
        $instance->func = $func;
        $instance->memoizeFlushAll();
        return $instance;
    }

    public function __clone()
    {
        assert($this->fqsen === null, 'should only clone null fqsen');
        $this->singleton_union_type = null;
        // same as new static($this->namespace, $this->name, $this->template_parameter_type_list, $this->is_nullable);
    }

    public function hasKnownFQSEN() : bool
    {
        return $this->fqsen !== null;
    }

    /**
     * Override asFQSEN to return the closure's FQSEN
     */
    public function asFQSEN() : FQSEN
    {
        if (!empty($this->fqsen)) {
            return $this->fqsen;
        }

        return parent::asFQSEN();
    }

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    protected function canCastToNonNullableType(Type $type) : bool
    {
        if ($type->isCallable()) {
            if ($this->getIsNullable() && !$type->getIsNullable()) {
                return false;
            }
            if ($type instanceof FunctionLikeDeclarationType) {
                // Check if the function declaration is known and available. It's not available for the generic \Closure.
                if ($this->func) {
                    return $this->func->asFunctionLikeDeclarationType()->canCastToNonNullableFunctionLikeDeclarationType($type);
                }
            }
            return true;
        }

        return parent::canCastToNonNullableType($type);
    }

    /**
     * @param bool $is_nullable
     * If true, returns a nullable instance of this closure type
     *
     * @return static
     */
    public static function instance(bool $is_nullable)
    {
        if ($is_nullable) {
            static $nullable_instance = null;

            if (empty($nullable_instance)) {
                $nullable_instance = self::make('\\', self::NAME, [], true, Type::FROM_NODE);
            }
            \assert($nullable_instance instanceof self);

            return $nullable_instance;
        }

        static $instance = null;

        if (empty($instance)) {
            $instance = self::make('\\', self::NAME, [], false, Type::FROM_NODE);
        }

        \assert($instance instanceof self);
        return $instance;
    }

    /**
     * @return bool
     * True if this type is a callable or a Closure.
     */
    public function isCallable() : bool
    {
        return true;
    }

    public function __toString()
    {
        if ($this->func) {
            return $this->func->asFunctionLikeDeclarationType()->__toString();
        }
        return '\Closure';
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

final class ResourceType extends NativeType
{
    /** @phan-override */
    const NAME = 'resource';

    public function isPrintableScalar() : bool
    {
        return false;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Config;
use Phan\Language\Type;

final class FalseType extends ScalarType
{
    /** @phan-override */
    const NAME = 'false';

    public function getIsPossiblyFalsey() : bool
    {
        return true;  // it's always falsey, whether or not it's nullable.
    }

    public function getIsAlwaysFalsey() : bool
    {
        return true;  // FalseType is always falsey, whether or not it's nullable.
    }

    public function getIsAlwaysFalse() : bool
    {
        return !$this->is_nullable;  // If it can be null, it's not **always** identical to false
    }

    public function getIsPossiblyTruthy() : bool
    {
        return false;
    }

    public function getIsAlwaysTruthy() : bool
    {
        return false;
    }

    public function getIsPossiblyFalse() : bool
    {
        return true;
    }

    public function asNonFalseType() : Type
    {
        assert($this->is_nullable, 'should only call on ?false');
        return NullType::instance(false);
    }

    public function getIsInBoolFamily() : bool
    {
        return true;
    }

    /**
     * Helper function for internal use by UnionType
     */
    public function getNormalizationFlags() : int
    {
        return $this->is_nullable ? (self::_bit_nullable | self::_bit_false) : self::_bit_false;
    }

    public function isPrintableScalar() : bool
    {
        // This would be '', which is probably not intended
        return Config::getValue('scalar_implicit_cast');
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Config;
use Phan\Language\Type;
use Phan\Language\UnionType;

final class NullType extends ScalarType
{
    /** @phan-override */
    const NAME = 'null';

    /**
     * @param string $namespace
     * The (optional) namespace of the type such as '\'
     * or '\Phan\Language'.
     *
     * @param string $name
     * The name of the type such as 'int' or 'MyClass'
     *
     * @param array<int,UnionType> $template_parameter_type_list
     * A (possibly empty) list of template parameter types
     *
     * @param bool $is_nullable (@phan-unused-param)
     * True if this type can be null, false if it cannot
     * be null. (NullType can always be null)
     */
    protected function __construct(
        string $namespace,
        string $name,
        $template_parameter_type_list,
        bool $is_nullable
    ) {
        parent::__construct(
            $namespace,
            $name,
            $template_parameter_type_list,
            true
        );
    }

    public function canCastToNonNullableType(Type $type) : bool
    {
        // null_casts_as_any_type means that null or nullable can cast to any type?
        return Config::get_null_casts_as_any_type()
            || (Config::get_null_casts_as_array() && $type->isArrayLike())
            || parent::canCastToNonNullableType($type);
    }

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    public function canCastToType(Type $type) : bool
    {
        // Check to see if we have an exact object match
        if ($this === $type) {
            return true;
        }

        // Null can cast to a nullable type.
        if ($type->getIsNullable()) {
            return true;
        }

        if (Config::get_null_casts_as_any_type()) {
            return true;
        }

        // NullType is a sub-type of ScalarType. So it's affected by scalar_implicit_cast.
        if ($type->isScalar()) {
            if (Config::getValue('scalar_implicit_cast')) {
                return true;
            }
            $scalar_implicit_partial = Config::getValue('scalar_implicit_partial');
            // check if $type->getName() is in the list of permitted types $this->getName() can cast to.
            if (\count($scalar_implicit_partial) > 0 &&
                \in_array($type->getName(), $scalar_implicit_partial['null'] ?? [], true)) {
                return true;
            }
        }
        if ($type instanceof MixedType) {
            return true;
        }

        return false;
    }

    /**
     * @param bool $is_nullable (@phan-unused-param)
     * Set to true if the type should be nullable, else pass
     * false
     *
     * @return Type
     * A new type that is a copy of this type but with the
     * given nullability value.
     */
    public function withIsNullable(bool $is_nullable) : Type
    {
        return $this;
    }

    public function __toString() : string
    {
        return $this->name;
    }

    public function getIsNullable() : bool
    {
        return true;
    }

    public function getIsPossiblyFalsey() : bool
    {
        return true;  // Null is always falsey.
    }

    public function getIsAlwaysFalsey() : bool
    {
        return true;  // Null is always falsey.
    }

    public function isPrintableScalar() : bool
    {
        // This would be '', which is probably not intended. allow null in union types for `echo` if there are **other** valid types.
        return Config::get_null_casts_as_any_type();
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\Type;
use Phan\Language\UnionType;

final class MixedType extends NativeType
{
    /** @phan-override */
    const NAME = 'mixed';

    // mixed or ?mixed can cast to/from anything.
    // For purposes of analysis, there's no difference between mixed and nullable mixed.
    public function canCastToType(Type $unused_type) : bool
    {
        return true;
    }

    // mixed or ?mixed can cast to/from anything.
    // For purposes of analysis, there's no difference between mixed and nullable mixed.
    protected function canCastToNonNullableType(Type $unused_type) : bool
    {
        return true;
    }

    public function isExclusivelyNarrowedFormOrEquivalentTo(
        UnionType $union_type,
        Context $unused_context,
        CodeBase $unused_code_base
    ) : bool {
        // Type casting rules allow mixed to cast to anything.
        // But we don't want `@param mixed $x` to take precedence over `int $x` in the signature.
        return $union_type->hasType($this);
    }

    /**
     * @suppress PhanPluginUnusedPublicFinalMethodArgument (TODO: maybe use it in the future?)
     */
    public function asGenericArrayType(int $key_type) : Type
    {
        return ArrayType::instance(false);
    }

    public function isPrintableScalar() : bool
    {
        return true;  // It's possible.
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Type;

final class CallableDeclarationType extends FunctionLikeDeclarationType
{
    /** @override */
    const NAME = 'callable';

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    public function canCastToNonNullableType(Type $type) : bool
    {
        if ($type->isCallable()) {
            if ($this->getIsNullable()) {
                return false;
            }
            if ($type instanceof FunctionLikeDeclarationType) {
                // TODO: Weaker mode to allow callable to cast to Closure
                return $type instanceof CallableDeclarationType && $this->canCastToNonNullableFunctionLikeDeclarationType($type);
            }
            return true;
        }

        return parent::canCastToNonNullableType($type);
    }

    /**
     * @override to stop PhanUndeclaredTypeParameter
     */
    public function isNativeType() : bool
    {
        return true;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\AnnotatedUnionType;
use Phan\Language\Type;
use Phan\Language\UnionType;
use Phan\Language\UnionTypeBuilder;
use Phan\CodeBase;
use Phan\Config;

/**
 * This is generated from phpdoc such as array{field:int}
 */
final class ArrayShapeType extends ArrayType
{
    /** @phan-override */
    const NAME = 'array';

    /**
     * @var array<string|int,UnionType|AnnotatedUnionType>
     * Maps 0 or more field names to the corresponding types
     */
    private $field_types = [];

    /**
     * @var ?array<int,ArrayType>
     */
    private $as_generic_array_type_instances = null;

    /**
     * @var ?int
     */
    private $key_type = null;

    /**
     * @var ?UnionType
     */
    private $generic_array_element_union_type = null;

    /**
     * @param array<string|int,UnionType|AnnotatedUnionType> $types
     * Maps 0 or more field names to the corresponding types
     *
     * @param bool $is_nullable
     * Set to true if the type should be nullable, else pass false
     */
    protected function __construct(array $types, bool $is_nullable)
    {
        // Could de-duplicate, but callers should be able to do that as well when converting to UnionType.
        // E.g. array<int|int> is int[].
        parent::__construct('\\', self::NAME, [], $is_nullable);
        $this->field_types = $types;
    }

    /**
     * @return array<string|int,UnionType>
     * An array mapping field keys of this type to their union types.
     */
    public function getFieldTypes() : array
    {
        return $this->field_types;
    }

    public function isNotEmptyArrayShape() : bool
    {
        return \count($this->field_types) !== 0;
    }

    /**
     * @param int|string|float|bool $field_key
     * @suppress PhanPartialTypeMismatchArgumentInternal
     */
    public function withoutField($field_key) : ArrayShapeType
    {
        $field_types = $this->field_types;
        if (!\array_key_exists($field_key, $field_types)) {
            return $this;
        }
        unset($field_types[$field_key]);
        return self::fromFieldTypes($field_types, $this->is_nullable);
    }

    /**
     * @param bool $is_nullable
     * Set to true if the type should be nullable, else pass
     * false
     *
     * @return Type
     * A new type that is a copy of this type but with the
     * given nullability value.
     */
    public function withIsNullable(bool $is_nullable) : Type
    {
        if ($is_nullable === $this->is_nullable) {
            return $this;
        }

        return self::fromFieldTypes(
            $this->field_types,
            $is_nullable
        );
    }

    /** @override */
    public function hasArrayShapeTypeInstances() : bool
    {
        return true;
    }

    /**
     * @return array<int,ArrayType>
     * @suppress PhanPartialTypeMismatchReturn
     */
    private function computeGenericArrayTypeInstances() : array
    {
        $field_types = $this->field_types;
        if (\count($field_types) === 0) {
            // there are 0 fields, so we know nothing about the field types (and there's no way to indicate an empty array yet)
            return [ArrayType::instance($this->is_nullable)];
        }

        $union_type_builder = new UnionTypeBuilder();
        foreach ($field_types as $key => $field_union_type) {
            foreach ($field_union_type->getTypeSet() as $type) {
                $union_type_builder->addType(GenericArrayType::fromElementType($type, $this->is_nullable, \is_string($key) ? GenericArrayType::KEY_STRING : GenericArrayType::KEY_INT));
            }
        }
        return $union_type_builder->getTypeSet();
    }

    public function getKeyType() : int
    {
        return $this->key_type ?? ($this->key_type = GenericArrayType::getKeyTypeForArrayLiteral($this->field_types));
    }

    public function genericArrayElementUnionType() : UnionType
    {
        return $this->generic_array_element_union_type ?? ($this->generic_array_element_union_type = UnionType::merge($this->field_types));
    }

    public function genericArrayElementType() : Type
    {
        // FIXME Deprecate genericArrayElementType
        return MixedType::instance(false);
    }

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    protected function canCastToNonNullableType(Type $type) : bool
    {
        if ($type instanceof ArrayType) {
            if ($type instanceof GenericArrayType) {
                if (($this->getKeyType() & ($type->getKeyType() ?: GenericArrayType::KEY_MIXED)) === 0 && !Config::getValue('scalar_array_key_cast')) {
                    // Attempting to cast an int key to a string key (or vice versa) is normally invalid.
                    // However, the scalar_array_key_cast config would make any cast of array keys a valid cast.
                    return false;
                }
                return $this->genericArrayElementUnionType()->canCastToUnionType($type->genericArrayElementUnionType());
            } elseif ($type instanceof ArrayShapeType) {
                foreach ($type->field_types as $key => $field_type) {
                    $this_field_type = $this->field_types[$key] ?? null;
                    // Can't cast {a:int} to {a:int, other:string} if other is missing
                    if ($this_field_type === null) {
                        if ($field_type->getIsPossiblyUndefined()) {
                            // ... unless the other field is allowed to be undefined.
                            continue;
                        }
                        return false;
                    }
                    // can't cast {a:int} to {a:string} or {a:string=}
                    if (!$this_field_type->canCastToUnionType($field_type)) {
                        return false;
                    }
                }
                return true;
            }
            // array{key:T} can cast to array.
            return true;
        }

        if (\get_class($type) === IterableType::class) {
            // can cast to Iterable but not Traversable
            return true;
        }

        $d = \strtolower((string)$type);
        if ($d[0] == '\\') {
            $d = \substr($d, 1);
        }
        if ($d === 'callable') {
            if (\array_keys($this->field_types) !== [0, 1]) {
                return false;
            }
            // TODO: Check types of offsets 0 and 1
            return true;
        }

        return parent::canCastToNonNullableType($type);
    }

    /**
     * @param array<string|int,UnionType|AnnotatedUnionType> $field_types
     * @param bool $is_nullable
     * @return ArrayShapeType
     * TODO: deduplicate
     */
    public static function fromFieldTypes(
        array $field_types,
        bool $is_nullable
    ) : ArrayShapeType {
        // TODO: Investigate if caching makes this any more efficient?
        static $cache = [];

        $key_parts = [];
        foreach ($field_types as $key => $field_union_type) {
            $key_parts[$key] = $field_union_type->generateUniqueId();
        }
        if ($is_nullable) {
            $key_parts[] = '?';
        }
        $key = \json_encode($key_parts);

        return $cache[$key] ?? ($cache[$key] = new self($field_types, $is_nullable));
    }

    /**
     * Returns an empty array shape (for `array{}`)
     * @param bool $is_nullable
     * @return ArrayShapeType
     * @suppress PhanUnreferencedPublicMethod (TODO: Remove if we support empty array shapes and still don't use this)
     */
    public static function empty(
        bool $is_nullable = false
    ) : ArrayShapeType {
        // TODO: Investigate if caching makes this any more efficient?
        static $nullable_shape = null;
        static $nonnullable_shape = null;

        if ($is_nullable) {
            return $nullable_shape ?? ($nullable_shape = self::fromFieldTypes([], true));
        }
        return $nonnullable_shape ?? ($nonnullable_shape = self::fromFieldTypes([], false));
    }

    public function isGenericArray() : bool
    {
        return true;
    }

    public function __toString() : string
    {
        $parts = [];
        foreach ($this->field_types as $key => $value) {
            $value_repr = $value->__toString();
            if (\substr($value_repr, -1) === '=') {
                // convert {key:type=} to {key?:type} in representation.
                $parts[] = $key . '?:' . \substr($value_repr, 0, -1);
            } else {
                $parts[] = "$key:$value_repr";
            }
        }
        return ($this->is_nullable ? '?' : '') . 'array{' . \implode(',', $parts) . '}';
    }

    /**
     * @param CodeBase $code_base
     * The code base to use in order to find super classes, etc.
     *
     * @param int $recursion_depth
     * This thing has a tendency to run-away on me. This tracks
     * how bad I messed up by seeing how far the expanded types
     * go
     *
     * @return UnionType
     * Expands class types to all inherited classes returning
     * a superset of this type.
     *
     * TODO: Once Phan has full support for ArrayShapeType in the type system,
     * make asExpandedTypes return a UnionType with a single ArrayShapeType?
     * @override
     */
    public function asExpandedTypes(
        CodeBase $code_base,
        int $recursion_depth = 0
    ) : UnionType {
        // We're going to assume that if the type hierarchy
        // is taller than some value we probably messed up
        // and should bail out.
        \assert(
            $recursion_depth < 20,
            "Recursion has gotten out of hand"
        );
        return $this->memoize(__METHOD__, function () use ($code_base, $recursion_depth) {
            $result_fields = [];
            foreach ($this->field_types as $key => $union_type) {
                // UnionType already increments recursion_depth before calling asExpandedTypes on a subclass of Type,
                // and has a depth limit of 10.
                // Don't increase recursion_depth here, it's too easy to reach.
                $expanded_field_type = $union_type->asExpandedTypes($code_base, $recursion_depth);
                if ($union_type->getIsPossiblyUndefined()) {
                    // array{key?:string} should become array{key?:string}.
                    $expanded_field_type = $union_type->withIsPossiblyUndefined(true);
                }
                $result_fields[$key] = $expanded_field_type;
            }
            return ArrayShapeType::fromFieldTypes($result_fields, $this->is_nullable)->asUnionType();
        });
    }

    /**
     * @return array<int,ArrayType>
     * @override
     */
    public function withFlattenedArrayShapeTypeInstances() : array
    {
        $instances = $this->as_generic_array_type_instances;
        if (\is_array($instances)) {
            return $instances;
        }
        return $this->as_generic_array_type_instances = $this->computeGenericArrayTypeInstances();
    }

    public function asGenericArrayType(int $key_type) : Type
    {
        return GenericArrayType::fromElementType($this, false, $key_type);
    }

    /**
     * Computes the non-nullable union of two or more array shape types.
     *
     * E.g. array{0: string} + array{0:int,1:int} === array{0:int|string,1:int}
     * @param array<int,ArrayShapeType> $array_shape_types
     * @suppress PhanPartialTypeMismatchArgument
     */
    public static function union(array $array_shape_types) : ArrayShapeType
    {
        if (\count($array_shape_types) === 0) {
            throw new \AssertionError('Unexpected union of 0 array shape types');
        }
        if (\count($array_shape_types) === 1) {
            return $array_shape_types[0];
        }
        $field_types = $array_shape_types[0]->field_types;
        unset($array_shape_types[0]);

        foreach ($array_shape_types as $type) {
            foreach ($type->field_types as $key => $union_type) {
                $old_union_type = $field_types[$key] ?? null;
                if ($old_union_type === null) {
                    $field_types[$key] = $union_type;
                    continue;
                }
                $field_types[$key] = $old_union_type->withUnionType($union_type);
            }
        }
        return self::fromFieldTypes($field_types, false);
    }

    /**
     * Computes the union of two array shape types.
     *
     * E.g. array{0: string} + array{0:stdClass,1:int} === array{0:string,1:int}
     */
    public static function combineWithPrecedence(ArrayShapeType $left, ArrayShapeType $right) : ArrayShapeType
    {
        return self::fromFieldTypes($left->field_types + $right->field_types, false);
    }

    /**
     * @override
     */
    public function shouldBeReplacedBySpecificTypes() : bool
    {
        return false;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\Type;
use Phan\Language\UnionType;

final class StaticType extends Type
{
    /** Not an override */
    const NAME = 'static';

    /**
     * @param bool $is_nullable
     * An optional parameter, which if true returns a
     * nullable instance of this native type
     *
     * @return static
     */
    public static function instance(bool $is_nullable) : Type
    {
        if ($is_nullable) {
            static $nullable_instance = null;

            if (empty($nullable_instance)) {
                $nullable_instance = static::make('\\', static::NAME, [], true, Type::FROM_TYPE);
            }

            \assert($nullable_instance instanceof static);
            return $nullable_instance;
        }

        static $instance;

        if (empty($instance)) {
            $instance = static::make('\\', static::NAME, [], false, Type::FROM_TYPE);
            \assert($instance instanceof static);
        } else {
            \assert($instance instanceof static);
        }
        return $instance;
    }

    public function isNativeType() : bool
    {
        return false;
    }

    public function isSelfType() : bool
    {
        return false;
    }

    public function isStaticType() : bool
    {
        return true;
    }

    public function __toString() : string
    {
        $string = $this->name;

        if ($this->getIsNullable()) {
            $string = '?' . $string;
        }

        return $string;
    }

    /**
     * @return Type
     * Either this or 'static' resolved in the given context.
     */
    public function withStaticResolvedInContext(
        Context $context
    ) : Type {
        // If the context isn't in a class scope, there's nothing
        // we can do
        if (!$context->isInClassScope()) {
            return $this;
        }
        $type = $context->getClassFQSEN()->asType();
        if ($this->getIsNullable()) {
            return $type->withIsNullable(true);
        }
        return $type;
    }

    public function isExclusivelyNarrowedFormOrEquivalentTo(
        UnionType $union_type,
        Context $context,
        CodeBase $code_base
    ) : bool {
        $result = $this->withStaticResolvedInContext($context);
        if ($result !== $this) {
            return $result->isExclusivelyNarrowedFormOrEquivalentTo($union_type, $context, $code_base);
        }
        return false;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

final class VoidType extends NativeType
{
    /** @phan-override */
    const NAME = 'void';
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Config;
use Phan\Language\Type;

// Not sure if it made sense to extend BoolType, so not doing that.
final class TrueType extends ScalarType
{
    /** @phan-override */
    const NAME = 'true';

    public function getIsPossiblyTruthy() : bool
    {
        return true;
    }

    public function getIsAlwaysTruthy() : bool
    {
        return true;
    }

    public function getIsPossiblyTrue() : bool
    {
        return true;
    }

    public function getIsAlwaysTrue() : bool
    {
        return !$this->is_nullable;  // If it can be null, it's not **always** identical to true
    }

    public function asNonTrueType() : Type
    {
        assert($this->is_nullable, 'should only call on ?true');
        return NullType::instance(true);
    }

    public function getIsInBoolFamily() : bool
    {
        return true;
    }

    /**
     * Helper function for internal use by UnionType
     */
    public function getNormalizationFlags() : int
    {
        return $this->is_nullable ? (self::_bit_nullable | self::_bit_true) : self::_bit_true;
    }

    public function isPrintableScalar() : bool
    {
        // This would be '1', which is probably not intended
        return Config::getValue('scalar_implicit_cast');
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Config;
use Phan\Language\Type;

final class BoolType extends ScalarType
{
    /** @phan-override */
    const NAME = 'bool';
    public function getIsPossiblyFalsey() : bool
    {
        return true;  // it's always falsey, since this is conceptually a collection of FalseType and TrueType
    }

    public function asNonFalseyType() : Type
    {
        return TrueType::instance(false);
    }

    public function asNonTruthyType() : Type
    {
        return FalseType::instance($this->is_nullable);
    }

    public function getIsPossiblyFalse() : bool
    {
        return true;  // it's possibly false, since this is conceptually a collection of FalseType and TrueType
    }

    public function asNonFalseType() : Type
    {
        return TrueType::instance($this->is_nullable);
    }

    public function getIsPossiblyTrue() : bool
    {
        return true;  // it's possibly true, since this is conceptually a collection of FalseType and TrueType
    }

    public function asNonTrueType() : Type
    {
        return FalseType::instance($this->is_nullable);
    }

    public function getIsInBoolFamily() : bool
    {
        return true;
    }

    public function getIsAlwaysTruthy() : bool
    {
        return false;  // overridden in various types. This base class (Type) is implicitly the type of an object, which is always truthy.
    }

    /**
     * Helper function for internal use by UnionType
     */
    public function getNormalizationFlags() : int
    {
        return $this->is_nullable ? (self::_bit_nullable | self::_bit_true | self::_bit_false) : (self::_bit_true | self::_bit_false);
    }

    public function isPrintableScalar() : bool
    {
        // This would be '' or '1', which is probably not intended
        return Config::getValue('scalar_implicit_cast');
    }
}

// Temporary hack to load FalseType and TrueType before BoolType::instance() is called
// (Due to bugs in php static variables)
\class_exists(FalseType::class);
\class_exists(TrueType::class);
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Type;

final class TemplateType extends Type
{
    /** @var string */
    private $template_type_identifier;

    /**
     * @param string $template_type_identifier
     * An identifier for the template type
     */
    public function __construct(
        string $template_type_identifier
    ) {
        $this->template_type_identifier = $template_type_identifier;
    }

    /**
     * @return string
     * The name associated with this type
     */
    public function getName() : string
    {
        return $this->template_type_identifier;
    }

    /**
     * @return string
     * A string representation of this type in FQSEN form.
     * @override
     */
    public function asFQSENString() : string
    {
        return $this->template_type_identifier;
    }

    /**
     * @return bool
     * True if this namespace is defined
     */
    public function hasNamespace() : bool
    {
        return false;
    }

    /**
     * @return string
     * The namespace associated with this type
     */
    public function getNamespace() : string
    {
        return '';
    }

    public function isObject() : bool
    {
        // Return true because we don't know, it may or may not be an object.
        // Not sure if this will be called.
        return true;
    }

    public function isObjectWithKnownFQSEN() : bool
    {
        // We have a template type ID, not an fqsen
        return false;
    }

    public function isPossiblyObject() : bool
    {
        return true;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Type;

/**
 * Callers should split this up into multiple Type instances.
 */
interface MultiType
{
    /**
     * @return array<int,Type>
     */
    public function asIndividualTypeInstances() : array;
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

final class FloatType extends ScalarType
{
    /** @phan-override */
    const NAME = 'float';
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\Element\Comment;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Parameter;
use Phan\Language\FileRef;
use Phan\Language\FQSEN;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\Scope\ClosedScope;
use Phan\Language\Type;
use Phan\Language\UnionType;
use Closure;
use ast\Node;

/**
 * @phan-file-suppress PhanPluginUnusedPublicMethodArgument
 */
abstract class FunctionLikeDeclarationType extends Type implements FunctionInterface
{
    // Subclasses will override this
    const NAME = '';

    /** @var FileRef */
    private $file_ref;

    /** @var array<int,ClosureDeclarationParameter> */
    private $params;

    /** @var UnionType */
    private $return_type;

    /** @var bool */
    private $returns_reference;

    // computed properties

    /** @var int see FunctionTrait */
    private $required_param_count;

    /** @var int see FunctionTrait */
    private $optional_param_count;

    private $is_variadic;
    // end computed properties

    /**
     * @param array<int,ClosureDeclarationParameter> $params
     * @param UnionType $return_type
     */
    public function __construct(FileRef $file_ref, array $params, UnionType $return_type, bool $returns_reference, bool $is_nullable)
    {
        parent::__construct('\\', static::NAME, [], $is_nullable);
        $this->file_ref = FileRef::copyFileRef($file_ref);
        $this->params = $params;
        $this->return_type = $return_type;
        $this->returns_reference = $returns_reference;

        $required_param_count = 0;
        $optional_param_count = 0;
        // TODO: Warn about required after optional
        foreach ($params as $param) {
            if ($param->isOptional()) {
                $optional_param_count++;
                if ($param->isVariadic()) {
                    $this->is_variadic = true;
                    $optional_param_count = FunctionInterface::INFINITE_PARAMETERS - $required_param_count;
                    break;
                }
            } else {
                $required_param_count++;
            }
        }
        $this->required_param_count = $required_param_count;
        $this->optional_param_count = $optional_param_count;
    }

    /**
     * Used when serializing this type in union types.
     * @return string (e.g. "Closure(int,string&...):string[]")
     */
    public function __toString() : string
    {
        return $this->memoize(__FUNCTION__, function () {
            $parts = [];
            foreach ($this->params as $value) {
                $parts[] = $value->__toString();
            }
            $return_type = $this->return_type;
            $return_type_string = $return_type->__toString();
            if ($return_type->typeCount() >= 2) {
                $return_type_string = "($return_type_string)";
            }
            return ($this->is_nullable ? '?' : '') . static::NAME . '(' . \implode(',', $parts) . '):' . $return_type_string;
        });
    }

    public function __clone()
    {
        throw new \AssertionError('Should not clone ClosureTypeDeclaration');
    }

    /**
     * @return bool
     * True if this type is a callable or a Closure or a FunctionLikeDeclarationType
     */
    public function isCallable() : bool
    {
        return true;
    }

    /**
     * @return ?ClosureDeclarationParameter
     * @suppress PhanPossiblyFalseTypeReturn is_variadic implies at least one parameter exists.
     */
    public function getClosureParameterForArgument(int $i)
    {
        $result = $this->params[$i] ?? null;
        if (!$result) {
            return $this->is_variadic ? end($this->params) : null;
        }
        return $result;
    }

    public function canCastToNonNullableFunctionLikeDeclarationType(FunctionLikeDeclarationType $type) : bool
    {
        if ($this->required_param_count > $type->required_param_count) {
            return false;
        }
        if ($this->getNumberOfParameters() < $type->getNumberOfParameters()) {
            return false;
        }
        if ($this->returns_reference !== $type->returns_reference) {
            return false;
        }
        // TODO: Allow nullable/null to cast to void?
        if (!$this->return_type->canCastToUnionType($type->return_type)) {
            return false;
        }
        foreach ($this->params as $i => $param) {
            $other_param = $type->getClosureParameterForArgument($i) ?? null;
            if (!$other_param) {
                break;
            }
            if (!$param->canCastToParameterIgnoringVariadic($other_param)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @override (Don't include \Closure in the expanded types. It interferes with type casting checking)
     */
    public function asExpandedTypes(
        CodeBase $unused_code_base,
        int $unused_recursion_depth = 0
    ) : UnionType {
        return $this->asUnionType();
    }

    /**
     * @param bool $is_nullable
     * Set to true if the type should be nullable, else pass
     * false
     *
     * @return Type
     * A new type that is a copy of this type but with the
     * given nullability value.
     *
     * @override - Avoid calling make() , which is not compatible with FunctionLikeDeclarationType::__construct
     *             (E.g. from UnionType->asNormalizedTypes)
     */
    public function withIsNullable(bool $is_nullable) : Type
    {
        if ($is_nullable === $this->is_nullable) {
            return $this;
        }
        return new static(
            $this->file_ref,
            $this->params,
            $this->return_type,
            $this->returns_reference,
            $is_nullable
        );
    }

    ////////////////////////////////////////////////////////////////////////////////
    // Begin FunctionInterface overrides. Most of these are intentionally no-ops
    ////////////////////////////////////////////////////////////////////////////////

    /**
     * @override
     * @return void
     */
    public function addReference(FileRef $_)
    {
    }

    /** @override */
    public function getReferenceCount(CodeBase $_) : int
    {
        return 1;
    }

    /** @override */
    public function getReferenceList() : array
    {
        return [];
    }

    /** @override */
    public function isPrivate() : bool
    {
        return false;
    }

    /** @override */
    public function isProtected() : bool
    {
        return false;
    }

    /** @override */
    public function isPublic() : bool
    {
        return true;
    }

    /** @override */
    public function setFQSEN(FQSEN $_)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    /** @override */
    public function alternateGenerator(CodeBase $_) : \Generator
    {
        yield $this;
    }

    /** @override */
    public function analyze(Context $context, CodeBase $_) : Context
    {
        return $context;
    }

    /** @override */
    public function analyzeFunctionCall(CodeBase $unused_code_base, Context $unused_context, array $_)
    {
        throw new \AssertionError('should not call ' . __METHOD__);
    }

    /** @override */
    public function analyzeWithNewParams(Context $unused_context, CodeBase $unused_codebase, array $unused_parameter_list) : Context
    {
        throw new \AssertionError('should not call ' . __METHOD__);
    }

    /** @override */
    public function appendParameter(Parameter $_)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    /** @override */
    public function clearParameterList()
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    /** @override */
    public function cloneParameterList()
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    /** @override */
    public function ensureScopeInitialized(CodeBase $_)
    {
    }

    /** @override */
    public function asFunctionLikeDeclarationType() : FunctionLikeDeclarationType
    {
        return $this;
    }

    /** @override */
    public function getComment()
    {
        return null;
    }

    /** @override */
    public function getDependentReturnType(CodeBase $code_base, Context $context, array $args) : UnionType
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    /** @override */
    public function hasDependentReturnType() : bool
    {
        return false;
    }

    // TODO: Maybe create mock FQSENs for these instead.
    /** @override */
    public function getElementNamespace() : string
    {
        return '\\';
    }

    /** @override */
    public function getFQSEN()
    {
        $hash = \substr(\md5($this->__toString()), 0, 12);
        return FullyQualifiedFunctionName::fromFullyQualifiedString('\\closure_phpdoc' . $hash);
    }

    /** @override */
    public function getRepresentationForIssue() : string
    {
        // Represent this as "Closure(int):void" in issue messages instead of \closure_phpdoc_abcd123456Df
        return $this->__toString();
    }

    /** @override */
    public function getHasReturn() : bool
    {
        return true;
    }

    /** @override */
    public function getInternalScope() : ClosedScope
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    /** @return Node|null */
    public function getNode()
    {
        return null;
    }

    /** @override */
    public function getNumberOfRequiredParameters() : int
    {
        return $this->required_param_count;
    }

    /** @override */
    public function getNumberOfOptionalParameters() : int
    {
        return $this->optional_param_count;
    }

    /** @override */
    public function getNumberOfRequiredRealParameters() : int
    {
        return $this->required_param_count;
    }

    /** @override */
    public function getNumberOfOptionalRealParameters() : int
    {
        return $this->optional_param_count;
    }

    /** @override */
    public function getNumberOfParameters() : int
    {
        return $this->optional_param_count + $this->required_param_count;
    }

    /** @override */
    public function getOutputReferenceParamNames() : array
    {
        return [];
    }

    /** @override */
    public function getPHPDocParameterTypeMap()
    {
        // Implement?
        return [];
    }

    /** @override */
    public function getPHPDocReturnType()
    {
        return $this->return_type;
    }

    /**
     * @return Parameter|null
     * @override
     */
    public function getParameterForCaller(int $i)
    {
        $list = $this->params;
        if (count($list) === 0) {
            return null;
        }
        $parameter = $list[$i] ?? null;
        if ($parameter) {
            // This is already not variadic
            return $parameter->asNonVariadicRegularParameter($i);
        }
        return null;
    }

    /**
     * @return array<int,Parameter>
     */
    public function getParameterList() : array
    {
        $result = [];
        foreach ($this->params as $i => $param) {
            $result[] = $param->asRegularParameter($i);
        }
        return $result;
    }

    public function getRealParameterList()
    {
        return $this->getParameterList();
    }

    public function getRealReturnType() : UnionType
    {
        return $this->return_type;
    }

    public function getThrowsUnionType() : UnionType
    {
        return UnionType::empty();
    }

    public function hasFunctionCallAnalyzer() : bool
    {
        return false;
    }

    public function isFromPHPDoc() : bool
    {
        return true;
    }

    public function isNSInternal(CodeBase $code_base) : bool
    {
        return false;
    }

    public function isNSInternalAccessFromContext(CodeBase $code_base, Context $context) : bool
    {
        return false;
    }

    public function isReturnTypeUndefined() : bool
    {
        return false;
    }

    public function needsRecursiveAnalysis() : bool
    {
        return false;
    }

    public function recordOutputReferenceParamName(string $parameter_name)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function returnsRef() : bool
    {
        return $this->returns_reference;
    }

    public function setComment(Comment $comment)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function setFunctionCallAnalyzer(Closure $analyzer)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function setDependentReturnTypeClosure(Closure $analyzer)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function setHasReturn(bool $_)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function setHasYield(bool $_)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function setInternalScope(ClosedScope $scope)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function setIsReturnTypeUndefined(bool $_)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function setNumberOfOptionalParameters(int $_)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function setNumberOfRequiredParameters(int $_)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function setPHPDocParameterTypeMap(array $parameter_map)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    /**
     * @param ?UnionType $union_type the raw phpdoc union type
     */
    public function setPHPDocReturnType($union_type)
    {
        throw new \AssertionError('unexpected call to ' . __METHOD__);
    }

    public function getContext() : Context
    {
        return (new Context())
            ->withFile($this->file_ref->getFile())
            ->withLineNumberStart($this->file_ref->getLineNumberStart());
    }

    public function getUnionType() : UnionType
    {
        return $this->return_type;
    }

    public function getSuppressIssueList() : array
    {
        // TODO: Inherit suppress issue list from phpdoc declaring this?
        return [];
    }

    public function hasSuppressIssue(string $issue_type) : bool
    {
        return in_array($issue_type, $this->getSuppressIssueList());
    }

    public function hydrate(CodeBase $_)
    {
    }

    public function incrementSuppressIssueCount(string $issue_name)
    {
    }

    public function isDeprecated() : bool
    {
        return false;
    }

    public function getFileRef() : FileRef
    {
        return $this->file_ref;
    }

    public function isPHPInternal() : bool
    {
        return false;
    }

    public function setIsDeprecated(bool $_)
    {
    }

    public function setSuppressIssueList(array $issues)
    {
        throw new \AssertionError('should not call ' . __METHOD__);
    }

    public function setUnionType(UnionType $type)
    {
        throw new \AssertionError('should not call ' . __METHOD__);
    }

    /**
     * @return array<mixed,string> in the same format as FunctionSignatureMap.php
     * @override (Unused, but part of the interface)
     */
    public function toFunctionSignatureArray() : array
    {
        // no need for returns ref yet
        $stub .= '(' . implode(', ', array_map(function (Parameter $parameter) : string {
            return $parameter->toStubString();
        }, $this->getRealParameterList())) . ')';

        $return_type = $this->return_type;
        $stub = [$return_type->__toString()];
        foreach ($this->params as $i => $parameter) {
            $name = "p$i";
            if ($parameter->isOptional()) {
                $name .= '=';
            }
            $type_string = $parameter->getNonVariadicUnionType()->__toString();
            if ($parameter->isPassByReference()) {
                $type_string .= '&';
            }
            if ($parameter->isVariadic()) {
                $type_string .= '...';
            }
            $stub[$name] = $type_string;
        }
        return $stub;
    }

    ////////////////////////////////////////////////////////////////////////////////
    // End FunctionInterface overrides
    ////////////////////////////////////////////////////////////////////////////////
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Type;

final class CallableType extends NativeType
{
    /** @phan-override */
    const NAME = 'callable';

    /**
     * @return bool
     * True if this type is a callable or a Closure.
     */
    public function isCallable() : bool
    {
        return true;
    }

    protected function canCastToNonNullableType(Type $type) : bool
    {
        // CallableDeclarationType is not a native type, we check separately here
        return parent::canCastToNonNullableType($type) || $type instanceof CallableDeclarationType;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Type;
use Phan\Language\UnionType;
use Phan\Language\UnionTypeBuilder;

class ArrayType extends IterableType
{
    /** @phan-override */
    const NAME = 'array';

    public function getIsAlwaysTruthy() : bool
    {
        return false;
    }

    public function asNonTruthyType() : Type
    {
        // There's no EmptyArrayType, so return $this
        return $this;
    }

    public function isPossiblyObject() : bool
    {
        return false;  // Overrides IterableType returning true
    }

    /**
     * @return UnionType with ArrayType subclass(es)
     * @suppress PhanUnreferencedPublicMethod may be used in the future or for plugins as array shape support improves.
     */
    public static function combineArrayTypesMerging(UnionType $union_type) : UnionType
    {
        $result = new UnionTypeBuilder();
        $array_shape_types = [];
        foreach ($union_type->getTypeSet() as $type) {
            if ($type->isGenericArray()) {
                if ($type instanceof ArrayShapeType) {
                    $array_shape_types[] = $type;
                } else {
                    $result->addType($type);
                }
            } elseif ($type instanceof ArrayType) {
                return $type->asUnionType();
            }
        }
        if ($result->isEmpty()) {
            return ArrayShapeType::union($array_shape_types)->asUnionType();
        }
        foreach ($array_shape_types as $type) {
            foreach ($type->withFlattenedArrayShapeTypeInstances() as $type_part) {
                $result->addType($type_part);
            }
        }
        return $result->getUnionType();
    }

    /**
     * E.g. array{0:int} + array{0:string,1:float} becomes array{0:int,1:float}
     *
     * @param UnionType $left the left hand side (e.g. of a `+` operator). Keys from these array shapes take precedence.
     * @param UnionType $right the right hand side (e.g. of a `+` operator).
     * @return UnionType with ArrayType subclass(es)
     */
    public static function combineArrayTypesOverriding(UnionType $left, UnionType $right) : UnionType
    {
        $result = new UnionTypeBuilder();
        $left_array_shape_types = [];
        foreach ($left->getTypeSet() as $type) {
            if ($type->isGenericArray()) {
                if ($type instanceof ArrayShapeType) {
                    $left_array_shape_types[] = $type;
                } else {
                    $result->addType($type);
                }
            } elseif ($type instanceof ArrayType) {
                return $type->asUnionType();
            }
        }
        $right_array_shape_types = [];
        foreach ($right->getTypeSet() as $type) {
            if ($type->isGenericArray()) {
                if ($type instanceof ArrayShapeType) {
                    $right_array_shape_types[] = $type;
                } else {
                    $result->addType($type);
                }
            } elseif ($type instanceof ArrayType) {
                return $type->asUnionType();
            }
        }
        if ($result->isEmpty()) {
            if (\count($left_array_shape_types) === 0) {
                return ArrayShapeType::union($right_array_shape_types)->asUnionType();
            }
            if (\count($right_array_shape_types) === 0) {
                return ArrayShapeType::union($left_array_shape_types)->asUnionType();
            }
            // fields from the left take precedence (e.g. [0, false] + ['string'] becomes [0, false])
            return ArrayShapeType::combineWithPrecedence(
                ArrayShapeType::union($left_array_shape_types),
                ArrayShapeType::union($right_array_shape_types)
            )->asUnionType();
        }
        foreach (\array_merge($left_array_shape_types, $right_array_shape_types) as $type) {
            foreach ($type->withFlattenedArrayShapeTypeInstances() as $type_part) {
                $result->addType($type_part);
            }
        }
        return $result->getUnionType();
    }

    /**
     * E.g. string|array{0:T1|T2,1:float} + [0 => int] becomes string|array{0:int, 1:float}
     *
     * TODO: Remove any top level native types that can't have offsets, e.g. IntType, null, etc.
     *
     * @param UnionType $left the left hand side (e.g. of a isset check).
     * @param int|string|float|bool $field_dim_value (Ideally int|string)
     * @param UnionType $field_type
     * @return UnionType with ArrayType subclass(es)
     */
    public static function combineArrayShapeTypesWithField(UnionType $left, $field_dim_value, UnionType $field_type) : UnionType
    {
        $result = new UnionTypeBuilder();
        $left_array_shape_types = [];
        foreach ($left->getTypeSet() as $type) {
            if ($type instanceof ArrayShapeType) {
                $left_array_shape_types[] = $type;
            } else {
                $result->addType($type);
            }
        }
        $result->addType(ArrayShapeType::combineWithPrecedence(
            ArrayShapeType::fromFieldTypes([$field_dim_value => $field_type], false),
            // TODO: Add possibly_undefined annotations in union
            ArrayShapeType::union($left_array_shape_types)
        ));
        return $result->getUnionType();
    }

    /**
     * Overridden in subclasses
     *
     * @param int $key_type @phan-unused-param (TODO: Use?)
     * Corresponds to the type of the array keys. Set this to a GenericArrayType::KEY_* constant.
     *
     * @return Type
     * Get a new type which is the generic array version of
     * this type. For instance, 'int[]' will produce 'int[][]'.
     *
     * As a special case to reduce false positives, 'array' (with no known types) will produce 'array'
     */
    public function asGenericArrayType(int $key_type) : Type
    {
        // TODO: Allow one more level of nesting? E.g. array->array[], but array[]->array[]
        return ArrayType::instance(false);
    }

    protected function canCastToNonNullableType(Type $type) : bool
    {
        // CallableDeclarationType is not a native type, we check separately here
        return parent::canCastToNonNullableType($type) || $type instanceof CallableDeclarationType;
    }
}
// Trigger the autoloader for GenericArrayType so that it won't be called
// before ArrayType.
// This won't pass if GenericArrayType is in the process of being instantiated.
\class_exists(GenericArrayType::class);
\class_exists(ArrayShapeType::class);
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Type;

class IterableType extends NativeType
{
    /** @phan-override */
    const NAME = 'iterable';

    public function isIterable() : bool
    {
        return true;
    }

    public function isPrintableScalar() : bool
    {
        return false;  // Overridden in subclass IterableType
    }

    public function isPossiblyObject() : bool
    {
        return true;  // can be Traversable, which is an object
    }

    /**
     * @return bool
     * True if this type is array-like (is of type array, is
     * a generic array, or implements ArrayAccess).
     */
    public function isArrayLike() : bool
    {
        // TODO: the base `iterable` isn't always array-like (no array access on Traversable).
        // In another PR, change `iterable` behavior?
        return true;
    }

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    protected function canCastToNonNullableType(Type $type) : bool
    {
        // TODO: Really?
        if ($type instanceof GenericArrayType) {
            return true;
        }

        return parent::canCastToNonNullableType($type);
    }
}
// Trigger autoloader for subclass before make() can get called.
\class_exists(ArrayType::class);
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Type;
use Phan\Language\UnionType;
use Phan\Language\UnionTypeBuilder;
use Phan\CodeBase;

/**
 * Callers should split this up into multiple GenericArrayType instances.
 *
 * This is generated from phpdoc array<int, T1|T2> where callers expect a subclass of Type.
 */
final class GenericMultiArrayType extends ArrayType implements MultiType
{
    /** @phan-override */
    const NAME = 'array';

    /**
     * @var array<int,Type>
     * The list of possible types of every element in this array (2 or more)
     */
    private $element_types = [];

    /**
     * @var int
     * Corresponds to the type of the array keys. Set this to a GenericArrayType::KEY_* constant.
     */
    private $key_type;

    /**
     * @param array<int,Type> $types
     * The 2 or more possible types of every element in this array
     *
     * @param bool $is_nullable
     * Set to true if the type should be nullable, else pass false
     *
     * @param int $key_type
     * Corresponds to the type of the array keys. Set this to a GenericArrayType::KEY_* constant.
     */
    protected function __construct(array $types, bool $is_nullable, int $key_type)
    {
        \assert(\count($types) >= 2);
        // Could de-duplicate, but callers should be able to do that as well when converting to UnionType.
        // E.g. array<int|int> is int[].
        parent::__construct('\\', self::NAME, [], false);
        $this->element_types = $types;
        $this->is_nullable = $is_nullable;
        $this->key_type = $key_type;
    }

    /**
     * @param bool $is_nullable
     * Set to true if the type should be nullable, else pass
     * false
     *
     * @return Type
     * A new type that is a copy of this type but with the
     * given nullability value.
     */
    public function withIsNullable(bool $is_nullable) : Type
    {
        if ($is_nullable === $this->is_nullable) {
            return $this;
        }

        return GenericMultiArrayType::fromElementTypes(
            $this->element_types,
            $is_nullable,
            $this->key_type
        );
    }

    /**
     * @return array<int,GenericArrayType>
     * @override
     */
    public function asIndividualTypeInstances() : array
    {
        return \array_map(function (Type $type) {
            return GenericArrayType::fromElementType($type, $this->is_nullable, $this->key_type);
        }, UnionType::normalizeMultiTypes($this->element_types));
    }

    /**
     * @param array<int,Type> $element_types
     * @param bool $is_nullable
     * @return GenericMultiArrayType
     */
    public static function fromElementTypes(
        array $element_types,
        bool $is_nullable,
        int $key_type
    ) : GenericMultiArrayType {
        return new self($element_types, $is_nullable, $key_type);
    }

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    protected function canCastToNonNullableType(Type $type) : bool
    {
        if ($type instanceof GenericArrayType) {
            return $this->genericArrayElementUnionType()->canCastToUnionType(
                $type->genericArrayElementUnionType()
            );
        }

        // TODO: More precise about checking if can cast to ArrayShapeType

        if ($type->isArrayLike()) {
            return true;
        }

        $d = \strtolower((string)$type);
        if ($d[0] == '\\') {
            $d = \substr($d, 1);
        }
        if ($d === 'callable') {
            return true;
        }

        return parent::canCastToNonNullableType($type);
    }

    public function isGenericArray() : bool
    {
        return true;
    }

    /** @var ?UnionType */
    private $element_types_union_type;

    /**
     * @return UnionType
     * A variation of this type that is not generic.
     * i.e. '(int|string)[]' becomes 'int|string'.
     */
    public function genericArrayElementUnionType() : UnionType
    {
        return $this->element_types_union_type
            ?? ($this->element_types_union_type = UnionType::of(
                UnionType::normalizeMultiTypes($this->element_types)
            ));
    }

    public function __toString() : string
    {
        $string = 'array<' . \implode('|', $this->element_types) . '>';
        if ($this->is_nullable) {
            $string = '?' . $string;
        }
        return $string;
    }

    /**
     * @param CodeBase $code_base
     * The code base to use in order to find super classes, etc.
     *
     * @param $recursion_depth
     * This thing has a tendency to run-away on me. This tracks
     * how bad I messed up by seeing how far the expanded types
     * go
     *
     * @return UnionType
     * Expands class types to all inherited classes returning
     * a superset of this type.
     * @override
     */
    public function asExpandedTypes(
        CodeBase $code_base,
        int $recursion_depth = 0
    ) : UnionType {
        // We're going to assume that if the type hierarchy
        // is taller than some value we probably messed up
        // and should bail out.
        \assert(
            $recursion_depth < 20,
            "Recursion has gotten out of hand"
        );
        // TODO: Use UnionType::merge from a future change?
        $result = new UnionTypeBuilder();
        foreach ($this->element_types as $type) {
            $result->addUnionType(
                GenericArrayType::fromElementType(
                    $type,
                    $this->is_nullable,
                    $this->key_type
                )->asExpandedTypes($code_base, $recursion_depth + 1)
            );
        }
        return $result->getUnionType();
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

final class ScalarRawType extends ScalarType implements MultiType
{
    /** @override */
    const NAME = 'scalar';

    /**
     * @return array<int,ScalarType>
     */
    public function asIndividualTypeInstances() : array
    {
        if ($this->is_nullable) {
            static $nullable_types = null;
            if ($nullable_types === null) {
                $nullable_types = [BoolType::instance(true), IntType::instance(true), FloatType::instance(true), StringType::instance(true)];
            }
            return $nullable_types;
        }
        static $nonnullable_types = null;
        if ($nonnullable_types === null) {
            $nonnullable_types = [BoolType::instance(false), IntType::instance(false), FloatType::instance(false), StringType::instance(false)];
        }
        return $nonnullable_types;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\CodeBase;
use Phan\Language\UnionType;
use Phan\Language\Type;
use Phan\Language\Type\ArrayType;

abstract class NativeType extends Type
{
    const NAME = '';

    /** @phan-override */
    const KEY_PREFIX = '!';

    /**
     * @param bool $is_nullable
     * If true, returns a nullable instance of this native type
     *
     * @return static
     */
    public static function instance(bool $is_nullable)
    {
        if ($is_nullable) {
            static $nullable_instance = null;

            if ($nullable_instance === null) {
                $nullable_instance = static::make('\\', static::NAME, [], true, Type::FROM_NODE);
            }

            return $nullable_instance;
        }

        static $instance = null;

        if ($instance === null) {
            $instance = static::make('\\', static::NAME, [], false, Type::FROM_NODE);
        }

        return $instance;
    }

    public function isNativeType() : bool
    {
        return true;
    }

    public function isSelfType() : bool
    {
        return false;
    }

    public function isArrayAccess() : bool
    {
        return false;
    }

    public function isTraversable() : bool
    {
        return false;
    }

    public function isObject() : bool
    {
        return false;
    }

    public function isObjectWithKnownFQSEN() : bool
    {
        return false;
    }

    public function isPossiblyObject() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    protected function canCastToNonNullableType(Type $type) : bool
    {
        // Anything can cast to mixed or ?mixed
        // Not much of a distinction in nullable mixed, except to emphasize in comments that it definitely can be null.
        // MixedType overrides the canCastTo*Type methods to always return true.
        if ($type instanceof MixedType) {
            return true;
        }

        if (!($type instanceof NativeType)
            || $this instanceof GenericArrayType
            || $type instanceof GenericArrayType
        ) {
            return parent::canCastToNonNullableType($type);
        }

        // Cast this to a native type
        \assert($type instanceof NativeType);

        // A nullable type cannot cast to a non-nullable type
        if ($this->getIsNullable() && !$type->getIsNullable()) {
            return false;
        }
        static $matrix;
        if ($matrix === null) {
            $matrix = self::initializeTypeCastingMatrix();
        }

        return $matrix[$this->getName()][$type->getName()]
            ?? parent::canCastToNonNullableType($type);
    }

    /**
     * @return array<string,array<string,bool>>
     */
    private static function initializeTypeCastingMatrix() : array
    {
        $generateRow = function (string ...$permittedCastTypeNames) {
            return [
                ArrayType::NAME    => in_array(ArrayType::NAME, $permittedCastTypeNames, true),
                IterableType::NAME => in_array(IterableType::NAME, $permittedCastTypeNames, true),
                BoolType::NAME     => in_array(BoolType::NAME, $permittedCastTypeNames, true),
                CallableType::NAME => in_array(CallableType::NAME, $permittedCastTypeNames, true),
                FalseType::NAME    => in_array(FalseType::NAME, $permittedCastTypeNames, true),
                FloatType::NAME    => in_array(FloatType::NAME, $permittedCastTypeNames, true),
                IntType::NAME      => in_array(IntType::NAME, $permittedCastTypeNames, true),
                MixedType::NAME    => true,
                NullType::NAME     => in_array(NullType::NAME, $permittedCastTypeNames, true),
                ObjectType::NAME   => in_array(ObjectType::NAME, $permittedCastTypeNames, true),
                ResourceType::NAME => in_array(ResourceType::NAME, $permittedCastTypeNames, true),
                ScalarRawType::NAME => in_array(ScalarRawType::NAME, $permittedCastTypeNames, true),
                StringType::NAME   => in_array(StringType::NAME, $permittedCastTypeNames, true),
                TrueType::NAME     => in_array(TrueType::NAME, $permittedCastTypeNames, true),
                VoidType::NAME     => in_array(VoidType::NAME, $permittedCastTypeNames, true),
            ];
        };

        // A matrix of allowable type conversions between
        // the various native types.
        // (Represented in a readable format, with only the true entries (omitting Mixed, which is always true))

        return [
            ArrayType::NAME    => $generateRow(ArrayType::NAME, IterableType::NAME, CallableType::NAME),
            BoolType::NAME     => $generateRow(BoolType::NAME, FalseType::NAME, TrueType::NAME, ScalarRawType::NAME),
            CallableType::NAME => $generateRow(CallableType::NAME),
            FalseType::NAME    => $generateRow(FalseType::NAME, BoolType::NAME, ScalarRawType::NAME),
            FloatType::NAME    => $generateRow(FloatType::NAME, ScalarRawType::NAME),
            IntType::NAME      => $generateRow(IntType::NAME, FloatType::NAME, ScalarRawType::NAME),
            IterableType::NAME => $generateRow(IterableType::NAME),
            MixedType::NAME    => $generateRow(MixedType::NAME),  // MixedType overrides the methods which would use this
            NullType::NAME     => $generateRow(NullType::NAME),
            ObjectType::NAME   => $generateRow(ObjectType::NAME),
            ResourceType::NAME => $generateRow(ResourceType::NAME),
            StringType::NAME   => $generateRow(StringType::NAME, CallableType::NAME, ScalarRawType::NAME),
            TrueType::NAME     => $generateRow(TrueType::NAME, BoolType::NAME, ScalarRawType::NAME),
            VoidType::NAME     => $generateRow(VoidType::NAME),
        ];
    }

    public function __toString() : string
    {
        // Native types can just use their
        // non-fully-qualified names
        $string = $this->name;

        if ($this->getIsNullable()) {
            $string = '?' . $string;
        }

        return $string;
    }

    public function asFQSENString() : string
    {
        return $this->name;
    }

    /**
     * @param CodeBase $code_base @phan-unused-param
     * The code base to use in order to find super classes, etc.
     *
     * @param int $recursion_depth @phan-unused-param
     * This thing has a tendency to run-away on me. This tracks
     * how bad I messed up by seeing how far the expanded types
     * go
     *
     * @return UnionType
     * Does nothing for Native Types, but GenericArrayType is an exception to that.
     * @override
     */
    public function asExpandedTypes(
        CodeBase $code_base,
        int $recursion_depth = 0
    ) : UnionType {
        return $this->asUnionType();
    }

    public function hasTemplateParameterTypes() : bool
    {
        return false;
    }
}
\class_exists(ArrayType::class);
\class_exists(ScalarType::class);
<?php declare(strict_types=1);

namespace Phan\Language\Type;

use Phan\Language\Type;

final class ObjectType extends NativeType
{
    /** @phan-override */
    const NAME = 'object';

    protected function canCastToNonNullableType(Type $type) : bool
    {
        // Inverse of check in Type->canCastToNullableType
        if (!$type->isNativeType() && !($type instanceof ArrayType)) {
            return true;
        }
        return parent::canCastToNonNullableType($type);
    }

    /**
     * @return bool
     * True if this type is an object (or the phpdoc `object`)
     * @override
     */
    public function isObject() : bool
    {
        return true;  // Overridden in various subclasses
    }

    /**
     * @override
     */
    public function isObjectWithKnownFQSEN() : bool
    {
        return false;  // Overridden in various subclasses
    }

    /**
     * @return bool
     * True if this type is an object (or the phpdoc `object`)
     * @override
     */
    public function isPossiblyObject() : bool
    {
        return true;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Type;

final class ClosureDeclarationType extends FunctionLikeDeclarationType
{
    /** @override */
    const NAME = 'Closure';

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    public function canCastToNonNullableType(Type $type) : bool
    {
        if ($type->isCallable()) {
            if ($this->getIsNullable()) {
                return false;
            }
            if ($type instanceof FunctionLikeDeclarationType) {
                return $this->canCastToNonNullableFunctionLikeDeclarationType($type);
            }
            return true;
        }

        return parent::canCastToNonNullableType($type);
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\Language\Type;

final class StringType extends ScalarType
{
    /** @phan-override */
    const NAME = 'string';

    protected function canCastToNonNullableType(Type $type) : bool
    {
        // CallableDeclarationType is not a native type, we check separately here
        return parent::canCastToNonNullableType($type) || $type instanceof CallableDeclarationType;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

final class IntType extends ScalarType
{
    /** @phan-override */
    const NAME = 'int';
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\AST\UnionTypeVisitor;
use Phan\Language\Type;
use Phan\Language\Context;
use Phan\Language\UnionType;
use Phan\Language\UnionTypeBuilder;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\CodeBase;
use Phan\Config;

use ast\Node;

final class GenericArrayType extends ArrayType
{
    /** @phan-override */
    const NAME = 'array';

    // In PHP, array keys can be integers or strings. These constants describe all possible combinations of those key types.

    /**
     * No array keys.
     * Array types with this key type Similar to KEY_MIXED, but adding a key type will change the array to the new key
     * instead of staying as KEY_MIXED.
     */
    const KEY_EMPTY  = 0;  // No way to create this type yet.
    /** array keys are integers */
    const KEY_INT    = 1;
    /** array keys are strings */
    const KEY_STRING = 2;
    /** array keys are integers or strings. */
    const KEY_MIXED  = 3;  // i.e. KEY_INT|KEY_STRING

    const KEY_NAMES = [
        self::KEY_EMPTY  => 'empty',
        self::KEY_INT    => 'int',
        self::KEY_STRING => 'string',
        self::KEY_MIXED  => 'mixed',  // treated the same way as int|string
    ];

    /**
     * @var Type|null
     * The type of every value in this array
     */
    private $element_type = null;

    /**
     * @var int
     * Enum representing the type of every key in this array
     */
    private $key_type;

    /**
     * @param Type $type
     * The type of every element in this array
     *
     * @param bool $is_nullable
     * Set to true if the type should be nullable, else pass
     * false
     *
     * @param int $key_type
     * Corresponds to the type of the array keys. Set this to a GenericArrayType::KEY_* constant.
     */
    protected function __construct(Type $type, bool $is_nullable, int $key_type)
    {
        if ($key_type & ~3) {
            throw new \InvalidArgumentException("Invalid key_type $key_type");
        }
        parent::__construct('\\', self::NAME, [], false);
        $this->element_type = $type;
        $this->is_nullable = $is_nullable;
        $this->key_type = $key_type;
    }

    public function getKeyType() : int
    {
        return $this->key_type;
    }

    /**
     * @param bool $is_nullable
     * Set to true if the type should be nullable, else pass
     * false
     *
     * @return Type
     * A new type that is a copy of this type but with the
     * given nullability value.
     */
    public function withIsNullable(bool $is_nullable) : Type
    {
        if ($is_nullable === $this->is_nullable) {
            return $this;
        }

        return GenericArrayType::fromElementType(
            $this->element_type,
            $is_nullable,
            $this->key_type
        );
    }

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    protected function canCastToNonNullableType(Type $type) : bool
    {
        if ($type instanceof ArrayType) {
            if ($type instanceof GenericArrayType) {
                if (!$this->genericArrayElementType()
                    ->canCastToType($type->genericArrayElementType())) {
                    return false;
                }
                if ((($this->key_type ?: self::KEY_MIXED) & ($type->key_type ?: self::KEY_MIXED)) === 0) {
                    // Attempting to cast an int key to a string key (or vice versa) is normally invalid.
                    // However, the scalar_array_key_cast config would make any cast of array keys a valid cast.
                    return Config::getValue('scalar_array_key_cast');
                }
                return true;
            } elseif ($type instanceof ArrayShapeType) {
                if ((($this->key_type ?: self::KEY_MIXED) & $type->getKeyType()) === 0 && !Config::getValue('scalar_array_key_cast')) {
                    // Attempting to cast an int key to a string key (or vice versa) is normally invalid.
                    // However, the scalar_array_key_cast config would make any cast of array keys a valid cast.
                    return false;
                }
                return $this->genericArrayElementUnionType()->canCastToUnionType($type->genericArrayElementUnionType());
            }
            return true;
        }

        if (\get_class($type) === IterableType::class) {
            // can cast to Iterable but not Traversable
            return true;
        }

        $d = \strtolower((string)$type);
        if ($d[0] == '\\') {
            $d = \substr($d, 1);
        }
        if ($d === 'callable') {
            return $this->key_type !== self::KEY_STRING;
        }

        return parent::canCastToNonNullableType($type);
    }

    /**
     * @param Type $type
     * The element type for an array.
     *
     * @param bool $is_nullable
     * Set to true if the type should be nullable, else pass
     * false
     *
     * @param int $key_type
     * Corresponds to the type of the array keys. Set this to a GenericArrayType::KEY_* constant.
     *
     * @return GenericArrayType
     * Get a type representing an array of the given type
     */
    public static function fromElementType(
        Type $type,
        bool $is_nullable,
        int $key_type
    ) : GenericArrayType {
        // Make sure we only ever create exactly one
        // object for any unique type
        static $canonical_object_maps = null;

        if ($canonical_object_maps === null) {
            $canonical_object_maps = [];
            for ($i = 0; $i < 8; $i++) {
                $canonical_object_maps[] = new \SplObjectStorage();
            }
        }
        $map_index = $key_type * 2 + ($is_nullable ? 1 : 0);

        $map = $canonical_object_maps[$map_index];

        if (!$map->contains($type)) {
            $map->attach(
                $type,
                new GenericArrayType($type, $is_nullable, $key_type)
            );
        }

        return $map->offsetGet($type);
    }

    public function isGenericArray() : bool
    {
        return true;
    }

    /**
     * @return Type
     * A variation of this type that is not generic.
     * i.e. 'int[]' becomes 'int'.
     */
    public function genericArrayElementType() : Type
    {
        return $this->element_type;
    }

    /**
     * @return UnionType
     * A variation of this type that is not generic.
     * i.e. 'int[]' becomes 'int'.
     */
    public function genericArrayElementUnionType() : UnionType
    {
        return $this->element_type->asUnionType();
    }

    public function __toString() : string
    {
        $string = (string)$this->element_type;
        if ($this->key_type === self::KEY_MIXED) {
            // Disambiguation is needed for ?T[] and (?T)[] but not array<int,?T>
            if ($string[0] === '?') {
                $string = '(' . $string . ')';
            }
            $string = "{$string}[]";
        } else {
            $string = 'array<' . self::KEY_NAMES[$this->key_type] . ',' . $string . '>';
        }

        if ($this->getIsNullable()) {
            if ($string[0] === '?') {
                $string = "?($string)";
            } else {
                $string = '?' . $string;
            }
        }

        return $string;
    }

    /**
     * @param CodeBase $code_base
     * The code base to use in order to find super classes, etc.
     *
     * @param $recursion_depth
     * This thing has a tendency to run-away on me. This tracks
     * how bad I messed up by seeing how far the expanded types
     * go
     *
     * @return UnionType
     * Expands class types to all inherited classes returning
     * a superset of this type.
     * @override
     */
    public function asExpandedTypes(
        CodeBase $code_base,
        int $recursion_depth = 0
    ) : UnionType {
        // We're going to assume that if the type hierarchy
        // is taller than some value we probably messed up
        // and should bail out.
        \assert(
            $recursion_depth < 20,
            "Recursion has gotten out of hand"
        );

        return $this->memoize(__METHOD__, function () use ($code_base, $recursion_depth) {
            $union_type = $this->asUnionType();

            $class_fqsen = $this->genericArrayElementType()->asFQSEN();

            if (!($class_fqsen instanceof FullyQualifiedClassName)) {
                return $union_type;
            }

            \assert($class_fqsen instanceof FullyQualifiedClassName);

            if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
                return $union_type;
            }

            $clazz = $code_base->getClassByFQSEN($class_fqsen);

            $class_union_type = $clazz->getUnionType();
            $additional_union_type = $clazz->getAdditionalTypes();
            if ($additional_union_type !== null) {
                $class_union_type = $union_type->withUnionType($additional_union_type);
            }

            $union_type = $union_type->withUnionType(
                $class_union_type->asGenericArrayTypes($this->key_type)
            );

            // Recurse up the tree to include all types
            $recursive_union_type_builder = new UnionTypeBuilder();
            $representation = (string)$this;
            foreach ($union_type->getTypeSet() as $clazz_type) {
                if ((string)$clazz_type != $representation) {
                    $recursive_union_type_builder->addUnionType(
                        $clazz_type->asExpandedTypes(
                            $code_base,
                            $recursion_depth + 1
                        )
                    );
                } else {
                    $recursive_union_type_builder->addType($clazz_type);
                }
            }

            // Add in aliases
            // (If enable_class_alias_support is false, this will do nothing)
            $fqsen_aliases = $code_base->getClassAliasesByFQSEN($class_fqsen);
            foreach ($fqsen_aliases as $alias_fqsen_record) {
                $alias_fqsen = $alias_fqsen_record->alias_fqsen;
                $recursive_union_type_builder->addType(
                    $alias_fqsen->asType()->asGenericArrayType($this->key_type)
                );
            }
            return $recursive_union_type_builder->getUnionType();
        });
    }

    public static function keyTypeFromUnionTypeKeys(UnionType $union_type) : int
    {
        $key_types = self::KEY_EMPTY;
        foreach ($union_type->getTypeSet() as $type) {
            if ($type instanceof GenericArrayType) {
                $key_types |= $type->key_type;
                // TODO: support array shape as well?
            } elseif ($type instanceof ArrayShapeType) {
                if ($type->isNotEmptyArrayShape()) {
                    $key_types |= $type->getKeyType();
                }
            }
        }
        // int|string corresponds to KEY_MIXED (KEY_INT|KEY_STRING)
        // And if we're unable to find any types, return KEY_MIXED.
        return $key_types ?: self::KEY_MIXED;
    }

    /** @suppress PhanUnreferencedPublicClassConstant */
    const CONVERT_KEY_MIXED_TO_EMPTY_UNION_TYPE = 0;
    const CONVERT_KEY_MIXED_TO_INT_OR_STRING_UNION_TYPE = 1;

    /**
     * @return UnionType
     */
    public static function unionTypeForKeyType(int $key_type, int $behavior = self::CONVERT_KEY_MIXED_TO_INT_OR_STRING_UNION_TYPE) : UnionType
    {
        static $int_type = null;
        static $string_type = null;
        if ($int_type === null) {
            $int_type = IntType::instance(false);
            $string_type = StringType::instance(false);
        }
        switch ($key_type) {
            case self::KEY_INT:
                return $int_type->asUnionType();
            case self::KEY_STRING:
                return $string_type->asUnionType();
            default:
                if ($behavior === self::CONVERT_KEY_MIXED_TO_INT_OR_STRING_UNION_TYPE) {
                    return new UnionType([$int_type, $string_type], true);
                }
                return UnionType::empty();
        }
    }

    public static function keyTypeFromUnionTypeValues(UnionType $union_type) : int
    {
        $key_types = self::KEY_EMPTY;
        foreach ($union_type->getTypeSet() as $type) {
            if ($type instanceof StringType) {
                $key_types |= self::KEY_STRING;
            } elseif ($type instanceof IntType) {
                $key_types |= self::KEY_INT;
            } elseif ($type instanceof MixedType) {
                // Anything including a mixed type is a mixed type.
                return self::KEY_MIXED;
            } // skip invalid types.
        }
        // int|string corresponds to KEY_MIXED (KEY_INT|KEY_STRING)
        // And if we're unable to find any types, return KEY_MIXED.
        return $key_types ?: self::KEY_MIXED;
    }

    /**
     * @param array<int|string,mixed> $array - The array keys are used for the final result.
     *
     * @return int
     * Corresponds to the type of the array keys of $array. This is a GenericArrayType::KEY_* constant (KEY_INT, KEY_STRING, or KEY_MIXED).
     */
    public static function getKeyTypeForArrayLiteral(array $array) : int
    {
        $key_type = GenericArrayType::KEY_EMPTY;
        foreach ($array as $key => $_) {
            $key_type |= (\is_string($key) ? GenericArrayType::KEY_STRING : GenericArrayType::KEY_INT);
        }
        return $key_type ?: GenericArrayType::KEY_MIXED;
    }

    /**
     * @return int
     * Corresponds to the type of the array keys of $array. This is a GenericArrayType::KEY_* constant (KEY_INT, KEY_STRING, or KEY_MIXED).
     */
    public static function getKeyTypeOfArrayNode(CodeBase $code_base, Context $context, Node $node, bool $should_catch_issue_exception = true) : int
    {
        $children = $node->children;
        if (($children[0] ?? null) instanceof Node
            && $children[0]->kind == \ast\AST_ARRAY_ELEM
        ) {
            $key_type_enum = GenericArrayType::KEY_EMPTY;
            // Check the all elements for key types.
            foreach ($children as $child) {
                // Don't bother recursing more than one level to iterate over possible types.
                $key_node = $child->children['key'];
                if ($key_node instanceof Node) {
                    $key_type_enum |= self::keyTypeFromUnionTypeValues(UnionTypeVisitor::unionTypeFromNode(
                        $code_base,
                        $context,
                        $key_node,
                        $should_catch_issue_exception
                    ));
                } elseif ($key_node !== null) {
                    if (\is_string($key_node)) {
                        $key_type_enum |= GenericArrayType::KEY_STRING;
                    } elseif (\is_scalar($key_node)) {
                        $key_type_enum |= GenericArrayType::KEY_INT;
                    }
                } else {
                    $key_type_enum |= GenericArrayType::KEY_INT;
                }
                // If we already think it's mixed, return immediately.
                if ($key_type_enum === GenericArrayType::KEY_MIXED) {
                    return GenericArrayType::KEY_MIXED;
                }
            }
            return $key_type_enum ?: GenericArrayType::KEY_MIXED;
        }
        return GenericArrayType::KEY_MIXED;
    }

    public function hasArrayShapeTypeInstances() : bool
    {
        return $this->element_type->hasArrayShapeTypeInstances();
    }

    /** @return array<int,Type> */
    public function withFlattenedArrayShapeTypeInstances() : array
    {
        // TODO: Any point in caching this?
        $type_instances = $this->element_type->withFlattenedArrayShapeTypeInstances();
        if (\count($type_instances) === 1 && $type_instances[0] === $this->element_type) {
            return [$this];
        }
        $results = [];
        foreach ($type_instances as $type) {
            $results[] = GenericArrayType::fromElementType($type, $this->is_nullable, $this->key_type);
        }
        return $results;
    }

    public function asGenericArrayType(int $key_type) : Type
    {
        return GenericArrayType::fromElementType($this, false, $key_type);
    }

    /**
     * @override
     */
    public function shouldBeReplacedBySpecificTypes() : bool
    {
        return false;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language\Type;

use Phan\CodeBase;
use Phan\Config;
use Phan\Language\Type;
use Phan\Language\UnionType;
use Phan\Language\Context;

abstract class ScalarType extends NativeType
{
    public function isScalar() : bool
    {
        return true;
    }

    public function isPrintableScalar() : bool
    {
        return true;  // Overridden in subclass IterableType and ResourceType
    }

    public function isSelfType() : bool
    {
        return false;
    }

    public function isStaticType() : bool
    {
        return false;
    }

    public function isIterable() : bool
    {
        return false;
    }

    public function isArrayLike() : bool
    {
        return false;
    }

    public function isGenericArray() : bool
    {
        return false;
    }

    /**
     * @param CodeBase $code_base (@phan-unused-param)
     *
     * @param Type $parent (@phan-unused-param)
     *
     * @return bool
     * True if this type represents a class which is a sub-type of
     * the class represented by the passed type.
     */
    public function isSubclassOf(CodeBase $code_base, Type $parent) : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this Type can be cast to the given Type
     * cleanly
     */
    protected function canCastToNonNullableType(Type $type) : bool
    {
        // Scalars may be configured to always cast to eachother
        if ($type->isScalar()) {
            if (Config::getValue('scalar_implicit_cast')) {
                return true;
            }
            $scalar_implicit_partial = Config::getValue('scalar_implicit_partial');
            if (\count($scalar_implicit_partial) > 0) {
                // check if $type->getName() is in the list of permitted types $this->getName() can cast to.
                if (\in_array($type->getName(), $scalar_implicit_partial[$this->getName()] ?? [], true)) {
                    return true;
                }
            }
        }

        return parent::canCastToNonNullableType($type);
    }

    /**
     * @override
     */
    public function isExclusivelyNarrowedFormOrEquivalentTo(
        UnionType $union_type,
        Context $unused_context,
        CodeBase $unused_code_base
    ) : bool {
        return $union_type->hasType($this) || $this->asUnionType()->canCastToUnionType($union_type);
    }

    /**
     * @override
     */
    public function asFQSENString() : string
    {
        return $this->name;
    }

    public function getIsAlwaysTruthy() : bool
    {
        // Most scalars (Except ResourceType) have a false value, e.g. 0/""/"0"/0.0/false.
        // (But ResourceType isn't a subclass of ScalarType in Phan's implementation)
        return false;
    }

    public function asNonTruthyType() : Type
    {
        // Subclasses of ScalarType all have false values within their types.
        return $this;
    }

    /**
     * @override
     */
    public function shouldBeReplacedBySpecificTypes() : bool
    {
        return false;
    }
}
\class_exists(IntType::class);
<?php declare(strict_types=1);
namespace Phan\Language;

use Phan\CodeBase;
use Phan\Exception\CodeBaseException;
use Phan\Exception\IssueException;
use Phan\Language\Type\ArrayType;

/**
 * NOTE: there may also be instances of UnionType that are empty, due to the constructor being public
 *
 * @phan-file-suppress PhanPluginUnusedPublicFinalMethodArgument the results don't depend on passed in parameters
 */
final class EmptyUnionType extends UnionType
{
    /**
     * An optional list of types represented by this union
     * @internal
     */
    public function __construct()
    {
        parent::__construct([], true);
    }

    /**
     * Use UnionType::empty() instead elsewhere in the codebase.
     */
    protected static function instance() : EmptyUnionType
    {
        static $self = null;
        return $self ?? ($self = new EmptyUnionType());
    }

    /**
     * @return Type[]
     * The list of simple types associated with this
     * union type. Keys are consecutive.
     * @override
     */
    public function getTypeSet() : array
    {
        return [];
    }

    /**
     * Add a type name to the list of types
     *
     * @return UnionType
     * @override
     */
    public function withType(Type $type)
    {
        return $type->asUnionType();
    }

    /**
     * Returns a new union type
     * which removes this type from the list of types,
     * keeping the keys in a consecutive order.
     *
     * Each type in $this->type_set occurs exactly once.
     *
     * @return UnionType
     * @override
     */
    public function withoutType(Type $type)
    {
        return $this;
    }

    /**
     * @return bool
     * True if this union type contains the given named
     * type.
     * @override
     */
    public function hasType(Type $type) : bool
    {
        return false;
    }

    /**
     * Returns a union type which add the given types to this type
     *
     * @return UnionType
     * @override
     */
    public function withUnionType(UnionType $union_type)
    {
        return $union_type;
    }

    /**
     * @return bool
     * True if this type has a type referencing the
     * class context in which it exists such as 'self'
     * or '$this'
     * @override
     */
    public function hasSelfType() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this union type has any types that are bool/false/true types
     * @override
     */
    public function hasTypeInBoolFamily() : bool
    {
        return false;
    }

    /**
     * @return UnionType[]
     * A map from template type identifiers to the UnionType
     * to replace it with
     * @override
     */
    public function getTemplateParameterTypeList() : array
    {
        return [];
    }

    /**
     * @param CodeBase $code_base
     * The code base to look up classes against
     *
     * TODO: Defer resolving the template parameters until parse ends. Low priority.
     *
     * @return UnionType[]
     * A map from template type identifiers to the UnionType
     * to replace it with
     */
    public function getTemplateParameterTypeMap(
        CodeBase $code_base
    ) : array {
        return [];
    }


    /**
     * @param UnionType[] $template_parameter_type_map
     * A map from template type identifiers to concrete types
     *
     * @return UnionType
     * This UnionType with any template types contained herein
     * mapped to concrete types defined in the given map.
     */
    public function withTemplateParameterTypeMap(
        array $template_parameter_type_map
    ) : UnionType {
        return $this;
    }

    /**
     * @return bool
     * True if this union type has any types that are generic
     * types
     * @override
     */
    public function hasTemplateType() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this union type has any types that have generic
     * types
     * @override
     */
    public function hasTemplateParameterTypes() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this type has a type referencing the
     * class context 'static'.
     * @override
     */
    public function hasStaticType() : bool
    {
        return false;
    }

    /**
     * @return UnionType
     * A new UnionType with any references to 'static' resolved
     * in the given context.
     */
    public function withStaticResolvedInContext(
        Context $context
    ) : UnionType {
        return $this;
    }

    /**
     * @return bool
     * True if and only if this UnionType contains
     * the given type and no others.
     * @override
     */
    public function isType(Type $type) : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this UnionType is exclusively native
     * types
     * @override
     */
    public function isNativeType() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True iff this union contains the exact set of types
     * represented in the given union type.
     * @override
     */
    public function isEqualTo(UnionType $union_type) : bool
    {
        return $union_type->isEmpty();
    }

    /**
     * @return bool
     * True iff this union contains a type that's also in
     * the other union type.
     */
    public function hasCommonType(UnionType $union_type) : bool
    {
        return false;
    }

    /**
     * @return bool - True if not empty and at least one type is NullType or nullable.
     */
    public function containsNullable() : bool
    {
        return false;
    }

    /**
     * @return bool - True if not empty, not possibly undefined, and at least one type is NullType or nullable.
     */
    public function containsNullableOrUndefined() : bool
    {
        return false;
    }

    /** @override */
    public function nonNullableClone() : UnionType
    {
        return $this;
    }

    /** @override */
    public function nullableClone() : UnionType
    {
        return $this;
    }

    /**
     * @return bool - True if type set is not empty and at least one type is NullType or nullable or FalseType or BoolType.
     * (I.e. the type is always falsey, or both sometimes falsey with a non-falsey type it can be narrowed down to)
     * This does not include values such as `IntType`, since there is currently no `NonZeroIntType`.
     * @override
     */
    public function containsFalsey() : bool
    {
        return false;
    }

    /** @override */
    public function nonFalseyClone() : UnionType
    {
        return $this;
    }

    /**
     * @return bool - True if type set is not empty and at least one type is NullType or nullable or FalseType or BoolType.
     * (I.e. the type is always falsey, or both sometimes falsey with a non-falsey type it can be narrowed down to)
     * This does not include values such as `IntType`, since there is currently no `NonZeroIntType`.
     * @override
     */
    public function containsTruthy() : bool
    {
        return false;
    }

    /** @override */
    public function nonTruthyClone() : UnionType
    {
        return $this;
    }

    /**
     * @return bool - True if type set is not empty and at least one type is BoolType or FalseType
     * @override
     */
    public function containsFalse() : bool
    {
        return false;
    }

    /**
     * @return bool - True if type set is not empty and at least one type is BoolType or TrueType
     * @override
     */
    public function containsTrue() : bool
    {
        return false;
    }

    public function nonFalseClone() : UnionType
    {
        return $this;
    }

    public function nonTrueClone() : UnionType
    {
        return $this;
    }

    /**
     * @param UnionType $union_type
     * A union type to compare against
     *
     * @param Context $context
     * The context in which this type exists.
     *
     * @param CodeBase $code_base
     * The code base in which both this and the given union
     * types exist.
     *
     * @return bool
     * True if each type within this union type can cast
     * to the given union type.
     */
    // Currently unused and buggy, commenting this out.
    /**
    public function isExclusivelyNarrowedFormOrEquivalentTo(
        UnionType $union_type,
        Context $context,
        CodeBase $code_base
    ) : bool {

        // Special rule: anything can cast to nothing
        // and nothing can cast to anything
        if ($union_type->isEmpty() || $this->isEmpty()) {
            return true;
        }

        // Check to see if the types are equivalent
        if ($this->isEqualTo($union_type)) {
            return true;
        }
        // TODO: Allow casting MyClass<TemplateType> to MyClass (Without the template?

        // Resolve 'static' for the given context to
        // determine whats actually being referred
        // to in concrete terms.
        $other_resolved_type =
            $union_type->withStaticResolvedInContext($context);
        $other_resolved_type_set = $other_resolved_type->type_set;

        // Convert this type to a set of resolved types to iterate over.
        $this_resolved_type_set =
            $this->withStaticResolvedInContext($context)->type_set;

        // TODO: Need to resolve expanded union types (parents, interfaces) of classes *before* this is called.

        // Test to see if every single type in this union
        // type can cast to the given union type.
        foreach ($this_resolved_type_set as $type) {
            // First check if this contains the type as an optimization.
            if ($other_resolved_type_set->contains($type)) {
                continue;
            }
            $expanded_types = $type->asExpandedTypes($code_base);
            if ($other_resolved_type->canCastToUnionType(
                $expanded_types
            )) {
                continue;
            }
        }
        return true;
    }
     */

    /**
     * @param Type[] $type_list
     * A list of types
     *
     * @return bool
     * True if this union type contains any of the given
     * named types
     */
    public function hasAnyType(array $type_list) : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this type has any subtype of `iterable` type (e.g. Traversable, Array).
     */
    public function hasIterable() : bool
    {
        return false;
    }

    /**
     * @return int
     * The number of types in this union type
     */
    public function typeCount() : int
    {
        return 0;
    }

    /**
     * @return bool
     * True if this Union has no types
     */
    public function isEmpty() : bool
    {
        return true;
    }

    /**
     * @param UnionType $target
     * The type we'd like to see if this type can cast
     * to
     *
     * @param CodeBase $code_base
     * The code base used to expand types
     *
     * @return bool
     * Test to see if this type can be cast to the
     * given type after expanding both union types
     * to include all ancestor types
     *
     * TODO: ensure that this is only called after the parse phase is over.
     */
    public function canCastToExpandedUnionType(
        UnionType $target,
        CodeBase $code_base
    ) : bool {
        return true;  // Empty can cast to anything.
    }

    /**
     * @param UnionType $target
     * A type to check to see if this can cast to it
     *
     * @return bool
     * True if this type is allowed to cast to the given type
     * i.e. int->float is allowed  while float->int is not.
     */
    public function canCastToUnionType(
        UnionType $target
    ) : bool {
        return true;  // Empty can cast to anything. See parent implementation.
    }

    /**
     * @return bool
     * True if all types in this union are scalars
     */
    public function isScalar() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if any types in this union are a printable scalar, or this is the empty union type
     * @internal
     */
    public function hasPrintableScalar() : bool
    {
        return true;
    }

    /**
     * @return bool
     * True if this union has array-like types (is of type array, is
     * a generic array, or implements ArrayAccess).
     */
    public function hasArrayLike() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this union has array-like types (is of type array, is
     * a generic array, or implements ArrayAccess).
     */
    public function hasGenericArray() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this union contains the ArrayAccess type.
     * (Call asExpandedTypes() first to check for subclasses of ArrayAccess)
     */
    public function hasArrayAccess() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this union contains the Traversable type.
     * (Call asExpandedTypes() first to check for subclasses of Traversable)
     */
    public function hasTraversable() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this union type represents types that are
     * array-like, and nothing else (e.g. can't be null).
     * If any of the array-like types are nullable, this returns false.
     */
    public function isExclusivelyArrayLike() : bool
    {
        return false;
    }

    /**
     * @return bool
     * True if this union type represents types that are arrays
     * or generic arrays, but nothing else.
     * @suppress PhanUnreferencedPublicMethod
     */
    public function isExclusivelyArray() : bool
    {
        return false;
    }

    /**
     * @return UnionType
     * Get the subset of types which are not native
     */
    public function nonNativeTypes() : UnionType
    {
        return $this;
    }

    /**
     * A memory efficient way to create a UnionType from a filter operation.
     * If this the filter preserves everything, returns $this instead
     */
    public function makeFromFilter(\Closure $cb) : UnionType
    {
        return $this;  // filtering empty results in empty
    }

    /**
     * @param Context $context
     * The context in which we're resolving this union
     * type.
     *
     * @return \Generator
     *
     * A list of class FQSENs representing the non-native types
     * associated with this UnionType
     *
     * @throws CodeBaseException
     * An exception is thrown if a non-native type does not have
     * an associated class
     *
     * @throws IssueException
     * An exception is thrown if static is used as a type outside of an object
     * context
     *
     * TODO: Add a method to ContextNode to directly get FQSEN instead?
     */
    public function asClassFQSENList(
        Context $context
    ) {
        if (false) {
            yield null;
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which to find classes
     *
     * @param Context $context
     * The context in which we're resolving this union
     * type.
     *
     * @return \Generator
     *
     * A list of classes representing the non-native types
     * associated with this UnionType
     *
     * @throws CodeBaseException
     * An exception is thrown if a non-native type does not have
     * an associated class
     *
     * @throws IssueException
     * An exception is thrown if static is used as a type outside of an object
     * context
     */
    public function asClassList(
        CodeBase $code_base,
        Context $context
    ) {
        if (false) {
            yield null;  // This is a generator yielding 0 results. This works around a bug in tolerant-php-parser 0.0.9
        }
    }

    /**
     * Takes "a|b[]|c|d[]|e" and returns "a|c|e"
     *
     * @return UnionType
     * A UnionType with generic array types filtered out
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function nonGenericArrayTypes() : UnionType
    {
        return $this;
    }

    /**
     * Takes "a|b[]|c|d[]|e" and returns "b[]|d[]"
     *
     * @return UnionType
     * A UnionType with generic array types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function genericArrayTypes() : UnionType
    {
        return $this;
    }

    /**
     * Takes "MyClass|int|array|?object" and returns "MyClass|?object"
     *
     * @return UnionType
     * A UnionType with known object types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function objectTypes() : UnionType
    {
        return $this;
    }

    /**
     * Takes "MyClass|int|array|?object" and returns "MyClass|?object"
     *
     * @return UnionType
     * A UnionType with known object types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function objectTypesWithKnownFQSENs() : UnionType
    {
        return $this;
    }

    /**
     * Returns true if objectTypes would be non-empty.
     *
     * @return bool
     */
    public function hasObjectTypes() : bool
    {
        return false;
    }

    /**
     * Returns the types for which is_scalar($x) would be true.
     * This means null/nullable is removed.
     * Takes "MyClass|int|?bool|array|?object" and returns "int|bool"
     * Takes "?MyClass" and returns an empty union type.
     *
     * @return UnionType
     * A UnionType with known scalar types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function scalarTypes() : UnionType
    {
        return $this;
    }

    /**
     * Returns the types for which is_callable($x) would be true.
     * TODO: Check for __invoke()?
     * Takes "Closure|false" and returns "Closure"
     * Takes "?MyClass" and returns an empty union type.
     *
     * @return UnionType
     * A UnionType with known callable types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function callableTypes() : UnionType
    {
        return $this;
    }

    /**
     * Returns true if this has one or more callable types
     * TODO: Check for __invoke()?
     * Takes "Closure|false" and returns true
     * Takes "?MyClass" and returns false
     *
     * @return bool
     * A UnionType with known callable types kept, other types filtered out.
     *
     * @see $this->callableTypes()
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function hasCallableType() : bool
    {
        return false;  // has no types
    }

    /**
     * Returns true if every type in this type is callable.
     * TODO: Check for __invoke()?
     * Takes "callable" and returns true
     * Takes "callable|false" and returns false
     *
     * @return bool
     * A UnionType with known callable types kept, other types filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function isExclusivelyCallable() : bool
    {
        return true; // !$this->hasTypeMatchingCallback(empty)
    }

    /**
     * Takes "a|b[]|c|d[]|e|array|ArrayAccess" and returns "a|c|e|ArrayAccess"
     *
     * @return UnionType
     * A UnionType with generic types(as well as the non-generic type "array")
     * filtered out.
     *
     * @see nonGenericArrayTypes
     */
    public function nonArrayTypes() : UnionType
    {
        return $this;
    }

    /**
     * @return bool
     * True if this is exclusively generic types
     */
    public function isGenericArray() : bool
    {
        return false;  // empty
    }

    /**
     * @return bool
     * True if any of the types in this UnionType made $matcher_callback return true
     */
    public function hasTypeMatchingCallback(\Closure $matcher_callback) : bool
    {
        return false;
    }

    /**
     * @return Type|false
     * Returns the first type in this UnionType made $matcher_callback return true
     */
    public function findTypeMatchingCallback(\Closure $matcher_callback)
    {
        return false;  // empty, no types
    }

    /**
     * Takes "a|b[]|c|d[]|e" and returns "b|d"
     *
     * @return UnionType
     * The subset of types in this
     */
    public function genericArrayElementTypes() : UnionType
    {
        return $this; // empty
    }

    /**
     * Takes "b|d[]" and returns "b[]|d[][]"
     *
     * @param int $key_type
     * Corresponds to the type of the array keys. Set this to a GenericArrayType::KEY_* constant.
     *
     * @return UnionType
     * The subset of types in this
     */
    public function elementTypesToGenericArray(int $key_type) : UnionType
    {
        return $this;
    }

    /**
     * @param \Closure $closure
     * A closure mapping `Type` to `Type`
     *
     * @return UnionType
     * A new UnionType with each type mapped through the
     * given closure
     */
    public function asMappedUnionType(\Closure $closure) : UnionType
    {
        return $this;  // empty
    }

    /**
     * @param int $key_type
     * Corresponds to the type of the array keys. Set this to a GenericArrayType::KEY_* constant.
     *
     * @return UnionType
     * Get a new type for each type in this union which is
     * the generic array version of this type. For instance,
     * 'int|float' will produce 'int[]|float[]'.
     *
     * If $this is an empty UnionType, this method will produce an empty UnionType
     */
    public function asGenericArrayTypes(int $key_type) : UnionType
    {
        return $this;  // empty
    }

    /**
     * @return UnionType
     * Get a new type for each type in this union which is
     * the generic array version of this type. For instance,
     * 'int|float' will produce 'int[]|float[]'.
     *
     * If $this is an empty UnionType, this method will produce 'array'
     */
    public function asNonEmptyGenericArrayTypes(int $key_type) : UnionType
    {
        return ArrayType::instance(false)->asUnionType();
    }

    /**
     * @param CodeBase $code_base
     * The code base to use in order to find super classes, etc.
     *
     * @param int $recursion_depth
     * This thing has a tendency to run-away on me. This tracks
     * how bad I messed up by seeing how far the expanded types
     * go
     *
     * @return UnionType
     * Expands all class types to all inherited classes returning
     * a superset of this type.
     */
    public function asExpandedTypes(
        CodeBase $code_base,
        int $recursion_depth = 0
    ) : UnionType {
        return $this;
    }

    /**
     * As per the Serializable interface
     *
     * @return string
     * A serialized representation of this type
     *
     * @see \Serializable
     */
    public function serialize() : string
    {
        return '';
    }

    /**
     * @return string
     * A human-readable string representation of this union
     * type
     */
    public function __toString() : string
    {
        return '';
    }

    /**
     * @return UnionType - A normalized version of this union type (May or may not be the same object, if no modifications were made)
     *
     * The following normalization rules apply
     *
     * 1. If one of the types is null or nullable, convert all types to nullable and remove "null" from the union type
     * 2. If both "true" and "false" (possibly nullable) coexist, or either coexists with "bool" (possibly nullable),
     *    then remove "true" and "false"
     */
    public function asNormalizedTypes() : UnionType
    {
        return $this;
    }

    public function hasTopLevelArrayShapeTypeInstances() : bool
    {
        return false;
    }

    /** @override */
    public function hasArrayShapeTypeInstances() : bool
    {
        return false;
    }

    /** @override */
    public function hasMixedType() : bool
    {
        return false;
    }

    /** @override */
    public function withFlattenedArrayShapeTypeInstances() : UnionType
    {
        return $this;
    }

    public function hasPossiblyObjectTypes() : bool
    {
        return false;
    }

    public function isExclusivelyBoolTypes() : bool
    {
        return false;
    }

    public function generateUniqueId() : string
    {
        return '';
    }

    public function hasTopLevelNonArrayShapeTypeInstances() : bool
    {
        return false;
    }

    public function shouldBeReplacedBySpecificTypes() : bool
    {
        return true;
    }

    /**
     * @param int|string|float|bool $field_key
     */
    public function withoutArrayShapeField($field_key) : UnionType
    {
        return $this;
    }

    public function withoutSubclassesOf(CodeBase $code_base, Type $object_type) : UnionType
    {
        return $this;
    }

    public function canStrictCastToUnionType(UnionType $target) : bool
    {
        return true;
    }

    public function hasArray() : bool
    {
        return false;
    }

    public function hasClassWithToStringMethod(CodeBase $code_base, Context $context) : bool
    {
        return false;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language;

/**
 * This is used to represent a union type that has various annotations.
 * Currently, the only annotation is is_possibly_undefined, which is used in optional fields of array shapes.
 * (It might eventually be used for variables that are defined in some branches but not others)
 *
 * NOTE: Instances should be immutable after UnionType returns them.
 * They are not unique for combination of types and options.
 *
 * The internal representation may change in the future.
 */
class AnnotatedUnionType extends UnionType
{
    /** @var bool */
    protected $is_possibly_undefined = false;

    /**
     * @override
     */
    public function withIsPossiblyUndefined(bool $is_possibly_undefined) : UnionType
    {
        if (!$is_possibly_undefined) {
            return UnionType::ofUniqueTypes($this->getTypeSet());
        }
        if (!$this->is_possibly_undefined) {
            return $this;
        }
        $result = clone($this);
        $result->is_possibly_undefined = $is_possibly_undefined;
        return $result;
    }

    public function getIsPossiblyUndefined() : bool
    {
        return $this->is_possibly_undefined;
    }

    public function __toString() : string
    {
        $result = parent::__toString();
        if ($this->is_possibly_undefined) {
            return $result . '=';
        }
        return $result;
    }

    /**
     * Add a type name to the list of types
     *
     * @return UnionType
     * @override
     */
    public function withType(Type $type)
    {
        return parent::withType($type)->withIsPossiblyUndefined(false);
    }

    /**
     * Add a type name to the list of types
     *
     * @return UnionType
     * @override
     */
    public function withoutType(Type $type)
    {
        return parent::withoutType($type)->withIsPossiblyUndefined(false);
    }

    /**
     * Returns a union type which add the given types to this type
     *
     * @return UnionType
     * @override
     */
    public function withUnionType(UnionType $union_type)
    {
        return parent::withUnionType($union_type)->withIsPossiblyUndefined(false);
    }

    /**
     * @return bool - True if not empty, not possibly undefined, and at least one type is NullType or nullable.
     * @override
     */
    public function containsNullableOrUndefined() : bool
    {
        return $this->is_possibly_undefined || $this->containsNullable();
    }

    /**
     * @override
     */
    public function generateUniqueId() : string
    {
        $id = parent::generateUniqueId();
        if ($this->is_possibly_undefined) {
            return $id . '=';
        }
        return $id;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language;

use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Exception\IssueException;
use ast\Node;

/**
 * A FutureUnionType is a UnionType that is lazily loaded.
 * Call `get()` in order force the type to be figured.
 */
class FutureUnionType
{

    /** @var CodeBase */
    private $code_base;

    /** @var Context */
    private $context;

    /** @var Node|string|int|bool|float */
    private $node;

    /**
     * @param CodeBase $code_base
     * @param Context $context
     * @param Node|string|int|bool|float $node
     */
    public function __construct(
        CodeBase $code_base,
        Context $context,
        $node
    ) {
        $this->code_base = $code_base;
        $this->context = $context;
        $this->node = $node;
    }

    /**
     * Force the future to figure out the type of the
     * given object or throw an IssueException if it
     * is unable to do so
     *
     * @return UnionType
     * The type of the future
     *
     * @throws IssueException
     * An exception is thrown if we are unable to determine
     * the type at the time this method is called
     */
    public function get() : UnionType
    {
        return UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $this->node,
            false
        );
    }

    /**
     * @internal (May rethink exposing the codebase in the future)
     */
    public function getCodebase() : CodeBase
    {
        return $this->code_base;
    }

    /**
     * @internal (May rethink exposing the codebase in the future)
     */
    public function getContext() : Context
    {
        return $this->context;
    }
}
<?php declare(strict_types=1);
namespace Phan\Language;

use Phan\Config;

/**
 * An object representing the context in which any
 * structural element (such as a class or method) lives.
 */
class FileRef implements \Serializable
{

    /**
     * @var string
     * The path to the file in which this element is defined
     */
    protected $file = 'internal';

    /**
     * @var int
     * The starting line number of the element within the $file
     */
    protected $line_number_start = 0;

    /**
     * @var int
     * The ending line number of the element within the $file
     */
    protected $line_number_end = 0;

    /**
     * @param string $file
     * The path to the file in which this element is defined
     *
     * @return static
     * This context with the given value is returned
     */
    public function withFile(string $file)
    {
        $context = clone($this);
        $context->file = $file;
        return $context;
    }

    /**
     * @return string
     * The path to the file in which the element is defined
     */
    public function getFile() : string
    {
        return $this->file;
    }

    /**
     * @return string
     * The path of the file relative to the project
     * root directory
     */
    public function getProjectRelativePath() : string
    {
        return self::getProjectRelativePathForPath($this->file);
    }

    /**
     * @param string $cwd_relative_path (relative or absolute path)
     * @return string
     * The path of the file relative to the project
     * root directory
     */
    public static function getProjectRelativePathForPath($cwd_relative_path)
    {
        // Get a path relative to the project root
        $path = \str_replace(
            Config::getProjectRootDirectory(),
            '',
            \realpath($cwd_relative_path) ?: $cwd_relative_path
        );

        // Strip any beginning directory separators
        if (0 === ($pos = \strpos($path, DIRECTORY_SEPARATOR))) {
            $path = \substr($path, $pos + 1);
        }

        return $path;
    }

    /**
     * @return bool
     * True if this object is internal to PHP
     */
    public function isPHPInternal() : bool
    {
        return ('internal' === $this->getFile());
    }

    /**
     * @var int $line_number
     * The starting line number of the element within the file
     *
     * @return static
     * This context with the given value is returned
     */
    public function withLineNumberStart(int $line_number)
    {
        $this->line_number_start = $line_number;
        return $this;
    }

    /*
     * @return int
     * The starting line number of the element within the file
     */
    public function getLineNumberStart() : int
    {
        return $this->line_number_start;
    }

    /**
     * @param int $line_number
     * The ending line number of the element within the $file
     *
     * @return static
     * This context with the given value is returned
     */
    public function withLineNumberEnd(int $line_number)
    {
        $this->line_number_end = $line_number;
        return $this;
    }

    /**
     * Get a string representation of the context
     *
     * @return string
     */
    public function __toString() : string
    {
        return $this->file . ':' . $this->line_number_start;
    }

    public function serialize()
    {
        return (string)$this;
    }

    public function unserialize($serialized)
    {
        $map = explode(':', $serialized);
        $this->file = $map[0];
        $this->line_number_start = (int)$map[1];
        $this->line_number_end = (int)($map[2] ?? 0);
    }

    /**
     * @param FileRef $other - An instance of FileRef or a subclass such as Context
     * @return FileRef - A plain file ref, with no other information
     */
    public static function copyFileRef(FileRef $other) : FileRef
    {
        $file_ref = new FileRef();
        $file_ref->file = $other->file;
        $file_ref->line_number_start = $other->line_number_start;
        $file_ref->line_number_end = $other->line_number_end;
        return $file_ref;
    }
}
<?php declare(strict_types=1);
namespace Phan;

use Phan\Language\Context;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Method;
use Phan\Language\Element\Func;
use Phan\PluginV2\LegacyPreAnalyzeNodeCapability;
use Phan\PluginV2\AnalyzeClassCapability;
use Phan\PluginV2\AnalyzeFunctionCapability;
use Phan\PluginV2\AnalyzeMethodCapability;
use Phan\PluginV2\LegacyAnalyzeNodeCapability;
use ast\Node;

/**
 * Plugins can be defined in the config and will have
 * their hooks called at appropriate times during analysis
 * of each file, class, method and function.
 *
 * Plugins must extends this class and return an instance
 * of themselves.
 *
 * @deprecated - Use PluginV2 instead
 * This class will eventually be removed in favor of PluginV2.
 */
abstract class Plugin extends PluginV2 implements
    AnalyzeClassCapability,
    AnalyzeFunctionCapability,
    AnalyzeMethodCapability,
    LegacyAnalyzeNodeCapability,
    LegacyPreAnalyzeNodeCapability
{

    /**
     * Do a first-pass analysis of a node before Phan
     * does its full analysis. This hook allows you to
     * update types in the CodeBase before analysis
     * happens.
     *
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * The context in which the node exits. This is
     * the context inside the given node rather than
     * the context outside of the given node
     *
     * @param Node $node
     * The php-ast Node being analyzed.
     * The parent node of the given node (if one exists).
     *
     * @return void
     *
     * @deprecated
     */
    abstract public function preAnalyzeNode(
        CodeBase $code_base,
        Context $context,
        Node $node
    );

    /**
     * Analyze the given node in the given context after
     * Phan has analyzed the node.
     *
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * The context in which the node exits. This is
     * the context inside the given node rather than
     * the context outside of the given node
     *
     * @param Node $node
     * The php-ast Node being analyzed.
     *
     * @param Node $node
     * The parent node of the given node (if one exists).
     *
     * @return void
     *
     * @deprecated
     */
    abstract public function analyzeNode(
        CodeBase $code_base,
        Context $context,
        Node $node,
        Node $parent_node = null
    );

    /**
     * @param CodeBase $code_base
     * The code base in which the class exists
     *
     * @param Clazz $class
     * A class being analyzed
     *
     * @return void
     */
    abstract public function analyzeClass(
        CodeBase $code_base,
        Clazz $class
    );

    /**
     * @param CodeBase $code_base
     * The code base in which the method exists
     *
     * @param Method $method
     * A method being analyzed
     *
     * @return void
     */
    abstract public function analyzeMethod(
        CodeBase $code_base,
        Method $method
    );

    /**
     * @param CodeBase $code_base
     * The code base in which the function exists
     *
     * @param Func $function
     * A function being analyzed
     *
     * @return void
     */
    abstract public function analyzeFunction(
        CodeBase $code_base,
        Func $function
    );
}
<?php declare(strict_types=1);
namespace Phan\Exception;

use Phan\Language\FQSEN;

class CodeBaseException extends \Exception
{

    /** @var FQSEN|null */
    private $missing_fqsen;

    /**
     * @param FQSEN|null $missing_fqsen
     * The FQSEN that cannot be found in the code base
     *
     * @param string $message
     * The error message
     */
    public function __construct(
        FQSEN $missing_fqsen = null,
        string $message = ""
    ) {
        parent::__construct($message);
        $this->missing_fqsen = $missing_fqsen;
    }

    /**
     * @return bool
     * True if we have an FQSEN defined
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function hasFQSEN() : bool
    {
        return !empty($this->missing_fqsen);
    }

    /**
     * @return FQSEN
     * The missing FQSEN
     */
    public function getFQSEN() : FQSEN
    {
        return $this->missing_fqsen;
    }
}
<?php declare(strict_types=1);
namespace Phan\Exception;

class TypeException extends \Exception
{
}
<?php declare(strict_types=1);
namespace Phan\Exception;

class EmptyFQSENException extends \Exception
{
    /** @var string */
    private $fqsen;

    /**
     * @param string $message
     * The error message
     */
    public function __construct(
        string $message,
        string $fqsen
    ) {
        parent::__construct($message);
        $this->fqsen = $fqsen;
    }

    public function getFQSEN() : string
    {
        return $this->fqsen;
    }
}
<?php declare(strict_types=1);
namespace Phan\Exception;

class UnanalyzableException extends NodeException
{
}
<?php declare(strict_types=1);
namespace Phan\Exception;

use Exception;
use Phan\IssueInstance;

/**
 * # Example Usage
 * ```
 * throw new IssueException(
 *     Issue::fromType(
 *         Issue::UndeclaredClassReference
 *     )(
 *         $context->getFile(),
 *         $node->getLine() ?? 0
 *     )
 * );
 * ```
 */
class IssueException extends Exception
{

    /** @var IssueInstance */
    private $issue_instance;

    /**
     * @param IssueInstance $issue_instance
     * An instance of an issue that was found but can't be
     * reported on immediately.
     */
    public function __construct(
        IssueInstance $issue_instance
    ) {
        parent::__construct();
        $this->issue_instance = $issue_instance;
    }

    /**
     * @return IssueInstance
     * The issue that was found
     */
    public function getIssueInstance() : IssueInstance
    {
        return $this->issue_instance;
    }

    /**
     * @override
     */
    public function __toString()
    {
        return \sprintf(
            "IssueException at %s:%d: %s\n%s",
            $this->getFile(),
            $this->getLine(),
            (string)$this->issue_instance,
            $this->getTraceAsString()
        );
    }
}
<?php declare(strict_types=1);
namespace Phan\Exception;

use ast\Node;

class NodeException extends \Exception
{

    /**
     * @var Node
     */
    private $node;

    /**
     * @param Node $node
     * The node causing the exception
     *
     * @param string $message
     * The error message
     */
    public function __construct(
        Node $node,
        string $message = ''
    ) {
        parent::__construct($message);
        $this->node = $node;
    }

    /**
     * @return Node
     * The node for which we have an exception
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function getNode() : Node
    {
        return $this->node;
    }
}
<?php declare(strict_types=1);

namespace Phan\Debug;

use Phan\Language\Type;
use Phan\Language\UnionType;

/**
 * Utility for debugging assignments to a given union type.
 * This can be used when creating a union type to figure out the causes of unexpected error messages.
 *
 * Not used as part of normal phan operations.
 */
class DebugUnionType extends UnionType
{

    /**
     * Add a type name to the list of types
     *
     * @return UnionType
     * @override
     */
    public function withType(Type $type) : UnionType
    {
        \printf("%s: Adding type %s to %s", \spl_object_hash($this), (string)$type, (string)$this);
        \debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        return parent::withType($type);
    }

    /**
     * Add the given types to this type
     *
     * @return UnionType
     */
    public function withUnionType(UnionType $union_type) : UnionType
    {
        \printf("%s: Adding union type %s to %s", \spl_object_hash($this), (string)$union_type, (string)$this);
        \debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        return parent::withUnionType($union_type);
    }
}
<?php declare(strict_types=1);
namespace Phan\Debug;

readline_completion_function(function ($input) {
    $matches = [];
    foreach (\get_declared_classes() as $className) {
        if (\strpos($className, $input) == 0) {
            $matches[] = $className;
        }
    }
    return $matches;
});

print "\n";
do {
    /** @var string|null */
    $input = readline("breakpoint> ");

    if (is_string($input)) {
        readline_add_history($input);
    }

    if (in_array($input, [
        'quit',
        'exit',
        'continue',
        'run',
        'c'
    ])) {
        break;
    }
    try {
        eval($input . ';');
    } catch (\ParseError $exception) {
        print "Parse error in `$input`\n";
    } catch (\Throwable $exception) {
        print $exception->getMessage() . "\n";
        print $exception->getTraceAsString() . "\n";
    }
    print "\n";
} while (true);
<?php declare(strict_types=1);
namespace Phan;

use Phan\Daemon\Request;
use Phan\Language\Type;
use Phan\LanguageServer\LanguageServer;
use Phan\LanguageServer\Logger as LanguageServerLogger;
use Phan\Library\FileCache;
use Phan\Output\BufferedPrinterInterface;
use Phan\Output\Collector\BufferingCollector;
use Phan\Output\IgnoredFilesFilterInterface;
use Phan\Output\IssueCollectorInterface;
use Phan\Output\IssuePrinterInterface;
use Phan\Plugin\ConfigPluginSet;

/**
 * This executes the the parse, method/function, then the analysis phases.
 *
 * This is the entry point of Phan's implementation.
 * Implementations such as `./phan` or the code climate integration call into this.
 *
 * @see self::analyzeFileList
 */
class Phan implements IgnoredFilesFilterInterface
{
    /** @var IssuePrinterInterface */
    public static $printer;

    /** @var IssueCollectorInterface */
    private static $issueCollector;

    /**
     * @return IssueCollectorInterface
     */
    public static function getIssueCollector() : IssueCollectorInterface
    {
        return self::$issueCollector;
    }

    /**
     * @param IssueCollectorInterface $issueCollector
     *
     * @return void
     */
    public static function setIssueCollector(
        IssueCollectorInterface $issueCollector
    ) {
        self::$issueCollector = $issueCollector;
    }

    /**
     * Take an array of serialized issues, deserialize them and then add
     * them to the issue collector.
     *
     * @param array[] $results
     */
    private static function collectSerializedResults(array $results)
    {
        $collector = self::getIssueCollector();
        foreach ($results as $issues) {
            if (empty($issues)) {
                continue;
            }

            foreach ($issues as $issue) {
                $collector->collectIssue($issue);
            }
        }
    }

    /**
     * Analyze the given set of files and emit any issues
     * found to STDOUT.
     *
     * @param CodeBase $code_base
     * A code base needs to be passed in because we require
     * it to be initialized before any classes or files are
     * loaded.
     *
     * @param \Closure $file_path_lister
     * Returns a list of files to scan (string[])
     *
     * @return bool
     * We emit messages to the configured printer and return
     * true if issues were found.
     *
     * @see \Phan\CodeBase
     */
    public static function analyzeFileList(
        CodeBase $code_base,
        \Closure $file_path_lister
    ) : bool {
        FileCache::setMaxCacheSize(FileCache::MINIMUM_CACHE_SIZE);
        self::checkForSlowPHPOptions();
        self::loadConfiguredPHPExtensionStubs($code_base);
        $is_daemon_request = Config::getValue('daemonize_socket') || Config::getValue('daemonize_tcp_port');
        $language_server_config = Config::getValue('language_server_config');
        $is_undoable_request = is_array($language_server_config) || $is_daemon_request;
        if ($is_daemon_request) {
            $code_base->eagerlyLoadAllSignatures();
        }
        if ($is_undoable_request) {
            $code_base->enableUndoTracking();
        }

        $file_path_list = $file_path_lister();

        $file_count = count($file_path_list);

        // We'll construct a set of files that we'll
        // want to run an analysis on
        $analyze_file_path_list = [];

        if (Config::getValue('consistent_hashing_file_order')) {
            // Parse the files in lexicographic order.
            // If there are duplicate class/function definitions,
            // this ensures they are added to the maps in the same order.
            sort($file_path_list, SORT_STRING);
        }

        if (Config::getValue('dump_parsed_file_list') === true) {
            // If --dump-parsed-file-list is provided,
            // print the files in the order they would be parsed.
            echo implode("\n", $file_path_list) . (count($file_path_list) > 0 ? "\n" : "");
            exit(EXIT_SUCCESS);
        }

        // This first pass parses code and populates the
        // global state we'll need for doing a second
        // analysis after.
        CLI::progress('parse', 0.0);
        $code_base->setCurrentParsedFile(null);
        foreach ($file_path_list as $i => $file_path) {
            \assert(\is_string($file_path));

            $code_base->setCurrentParsedFile($file_path);
            CLI::progress('parse', ($i + 1) / $file_count);

            // Kick out anything we read from the former version
            // of this file
            $code_base->flushDependenciesForFile($file_path);

            // If the file is gone, no need to continue
            $real = realpath($file_path);
            if ($real === false || !file_exists($real)) {
                continue;
            }
            try {
                // Parse the file
                Analysis::parseFile($code_base, $file_path);

                // Save this to the set of files to analyze
                $analyze_file_path_list[] = $file_path;
            } catch (\AssertionError $assertion_error) {
                error_log("While parsing $file_path...\n");
                error_log("$assertion_error\n");
                exit(EXIT_FAILURE);
            } catch (\Throwable $throwable) {
                // Catch miscellaneous errors such as $throwable and print their stack traces.
                error_log("While parsing $file_path, caught: " . $throwable . "\n");
                $code_base->recordUnparseableFile($file_path);
            }
        }
        $code_base->setCurrentParsedFile(null);

        // Don't continue on to analysis if the user has
        // chosen to just dump the AST
        if (Config::getValue('dump_ast')) {
            exit(EXIT_SUCCESS);
        }

        if (\is_string(Config::getValue('dump_signatures_file'))) {
            exit(self::dumpSignaturesToFile($code_base, Config::getValue('dump_signatures_file')));
        }

        $temporary_file_mapping = [];

        $request = null;
        Type::clearAllMemoizations();
        if ($is_undoable_request) {
            \assert($code_base->isUndoTrackingEnabled());
            if ($is_daemon_request) {
                \assert(!is_array($language_server_config), 'cannot use language server config for daemon mode');
                // Garbage collecting cycles doesn't help or hurt much here. Thought it would change something..
                // TODO: check for conflicts with other config options -
                //    incompatible with dump_ast, dump_signatures_file, output-file, etc.
                //    incompatible with dead_code_detection

                // This will fork and fall through every time a request to re-analyze the file set comes in.
                // TODO: The daemon should be periodically restarted?
                $request = Daemon::run($code_base, $file_path_lister);
                if (!$request) {
                    // TODO: Add a way to cleanly shut down.
                    error_log("Finished serving requests, exiting");
                    exit(2);
                }
            } else {
                assert(is_array($language_server_config));
                LanguageServerLogger::logInfo(sprintf("Starting accepting connections on the language server (pid=%d)", getmypid()));
                $request = LanguageServer::run($code_base, $file_path_lister, $language_server_config);
                if (!$request) {
                    // TODO: Add a way to cleanly shut down.
                    error_log("Finished serving requests, exiting");
                    exit(2);
                }
                LanguageServerLogger::logInfo(sprintf("language server (pid=%d) accepted connection", getmypid()));
            }
            self::setPrinter($request->getPrinter());

            // This is the list of all of the parsed files
            // (Also includes files which don't declare classes/functions/constants)
            $analyze_file_path_list = $request->filterFilesToAnalyze($code_base->getParsedFilePathList());
            if (count($analyze_file_path_list) === 0) {
                $request->respondWithNoFilesToAnalyze();  // respond and exit.
                exit(0);  // This is normal (E.g. .txt files, files outside of analysis list, etc)
            }

            // Do this before we stop tracking undo operations.
            $temporary_file_mapping = $request->getTemporaryFileMapping();

            // Stop tracking undo operations, now that the parse phase is done.
            $code_base->disableUndoTracking();
        }

        return self::finishAnalyzingRemainingStatements($code_base, $request, $analyze_file_path_list, $temporary_file_mapping);
    }

    /**
     * @param CodeBase $code_base
     * A code base needs to be passed in because we require
     * it to be initialized before any classes or files are
     * loaded.
     *
     * @param ?Request $request
     * @param array<int,string> $analyze_file_path_list
     * @param array<string,string> $temporary_file_mapping
     */
    public static function finishAnalyzingRemainingStatements(
        CodeBase $code_base,
        $request,
        array $analyze_file_path_list,
        array $temporary_file_mapping
    ) : bool {
        try {
            // With parsing complete, we need to tell the code base to
            // start hydrating any requested elements on their way out.
            // Hydration expands class types, imports parent methods,
            // properties, etc., and does stuff like that.
            //
            // This is an optimization that saves us a significant
            // amount of time on very large code bases. Instead of
            // hydrating all classes, we only hydrate the things we
            // actually need. When running as multiple processes this
            // lets us only need to do hydrate a subset of classes.
            $code_base->setShouldHydrateRequestedElements(true);

            // TODO: consider filtering if Config::getValue('include_analysis_file_list') is set
            // most of what needs considering is that Analysis::analyzeClasses() and Analysis:analyzeFunctions() have side effects
            // these side effects don't matter in daemon mode, but they do matter with this other form of incremental analysis
            // other parts of these analysis steps could be skipped, which would reduce the overall execution time
            $path_filter = isset($request) ? array_flip($analyze_file_path_list) : null;

            // Tie class aliases together with the source class
            if (Config::getValue('enable_class_alias_support')) {
                $code_base->resolveClassAliases();
            }

            // Take a pass over all functions verifying
            // various states now that we have the whole
            // state in memory
            Analysis::analyzeClasses($code_base, $path_filter);

            // Take a pass over all functions verifying
            // various states now that we have the whole
            // state in memory
            Analysis::analyzeFunctions($code_base, $path_filter);

            Analysis::loadMethodPlugins($code_base);

            // Filter out any files that are to be excluded from
            // analysis
            $analyze_file_path_list = array_filter(
                $analyze_file_path_list,
                function ($file_path) {
                    return !self::isExcludedAnalysisFile($file_path);
                }
            );
            if ($request instanceof Request && count($analyze_file_path_list) === 0) {
                $request->respondWithNoFilesToAnalyze();
                exit(0);
            }

            // Get the count of all files we're going to analyze
            $file_count = count($analyze_file_path_list);

            // Prevent an ugly failure if we have no files to
            // analyze.
            if (0 == $file_count) {
                return false;
            }

            // Get a map from process_id to the set of files that
            // the given process should analyze in a stable order
            $process_file_list_map =
                (new Ordering($code_base))->orderForProcessCount(
                    Config::getValue('processes'),
                    $analyze_file_path_list
                );

            /**
             * This worker takes a file and analyzes it
             * @return void
             */
            $analysis_worker = function ($i, $file_path) use ($file_count, $code_base, $temporary_file_mapping) {
                CLI::progress('analyze', ($i + 1) / $file_count);
                Analysis::analyzeFile($code_base, $file_path, $temporary_file_mapping[$file_path] ?? null);
            };

            // Determine how many processes we're running on. This may be
            // less than the provided number if the files are bunched up
            // excessively.
            $process_count = count($process_file_list_map);

            \assert(
                $process_count > 0 && $process_count <= Config::getValue('processes'),
                "The process count must be between 1 and the given number of processes. After mapping files to cores, $process_count process were set to be used."
            );

            $did_fork_pool_have_error = false;

            CLI::progress('analyze', 0.0);
            // Check to see if we're running as multiple processes
            // or not
            if ($process_count > 1) {
                // Run analysis one file at a time, splitting the set of
                // files up among a given number of child processes.
                $pool = new ForkPool(
                    $process_file_list_map,
                    /** @return void */
                    function () {
                        // Remove any issues that were collected prior to forking
                        // to prevent duplicate issues in the output.
                        self::getIssueCollector()->reset();
                    },
                    $analysis_worker,
                    function () use ($code_base) : array {
                        // This closure is run once, after running analysis_worker on each input.
                        // If there are any plugins defining finalizeProcess(), run those.
                        ConfigPluginSet::instance()->finalizeProcess($code_base);

                        // Return the collected issues to be serialized.
                        return self::getIssueCollector()->getCollectedIssues();
                    }
                );

                // Wait for all tasks to complete and collect the results.
                self::collectSerializedResults($pool->wait());
                $did_fork_pool_have_error = $pool->didHaveError();
            } else {
                // Get the task data from the 0th processor
                $analyze_file_path_list = array_values($process_file_list_map)[0];

                // If we're not running as multiple processes, just iterate
                // over the file list and analyze them
                foreach ($analyze_file_path_list as $i => $file_path) {
                    $analysis_worker($i, $file_path);
                }

                // Scan through all globally accessible elements
                // in the code base and emit errors for dead
                // code.
                Analysis::analyzeDeadCode($code_base);

                // If there are any plugins defining finalizeProcess(), run those.
                ConfigPluginSet::instance()->finalizeProcess($code_base);
            }

            // Get a count of the number of issues that were found
            $issue_count = count((self::$issueCollector)->getCollectedIssues());
            $is_issue_found =
                0 !== $issue_count;

            // Collect all issues, blocking
            self::display();

            if (Config::get()->print_memory_usage_summary) {
                self::printMemoryUsageSummary();
            }
        } catch (\Exception $e) {
            if ($request instanceof Request) {
                // Give people using the language server client/daemon a somewhat useful response.
                $request->sendJSONResponse([
                    "status" => Request::STATUS_ERROR_UNKNOWN,
                    "issue_count" => 1,
                    "issues" => 'Failed to analyze files: Uncaught exception: ' . (string)$e,
                ]);
                $request->exit(EXIT_SUCCESS);
            }
            throw $e;
        }


        if ($request instanceof Request) {
            $request->respondWithIssues($issue_count);
            $request->exit(EXIT_SUCCESS);
        }

        if (Config::getValue('print_memory_usage_summary')) {
            self::printMemoryUsageSummary();
        }

        if ($did_fork_pool_have_error) {
            // Make fork pool errors (e.g. due to memory limits) easy to detect when running CI jobs.
            return true;
        }

        return $is_issue_found;
    }

    /**
     * @param CodeBase $code_base
     * A code base needs to be passed in because we require
     * it to be initialized before any classes or files are
     * loaded.
     *
     * @param string[] $file_path_list
     * A set of files to expand with the set of dependencies
     * on those files.
     *
     * @return array<int,string>
     * Get an expanded list of files and dependencies for
     * the given file list
     *
     * TODO: This is no longer referenced, was removed while sqlite3 was temporarily removed.
     *       It would help in daemon mode if this was re-enabled
     * @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
     */
    public static function expandedFileList(
        CodeBase $code_base,
        array $file_path_list
    ) : array {

        $file_count = count($file_path_list);

        // We'll construct a set of files that we'll
        // want to run an analysis on
        $dependency_file_path_list = [];

        CLI::progress('dependencies', 0.0);  // trigger UI update of 0%
        foreach ($file_path_list as $i => $file_path) {
            CLI::progress('dependencies', ($i + 1) / $file_count);

            // Add the file itself to the list
            $dependency_file_path_list[] = $file_path;

            // Add any files that depend on this file
            $dependency_file_path_list = array_merge(
                $dependency_file_path_list,
                $code_base->dependencyListForFile($file_path)
            );
        }

        return array_unique($dependency_file_path_list);
    }

    /**
     * @return bool
     * True if this file is a member of a third party directory as
     * configured via the CLI flag '-3 [paths]'.
     */
    public static function isExcludedAnalysisFile(
        string $file_path
    ) : bool {
        $include_analysis_file_list = Config::getValue('include_analysis_file_list');
        if ($include_analysis_file_list) {
            return !in_array($file_path, $include_analysis_file_list, true);
        }

        $file_path = str_replace('\\', '/', $file_path);
        foreach (Config::getValue('exclude_analysis_directory_list') as $directory) {
            if (0 === strpos($file_path, $directory)
                || 0 === strpos($file_path, "./$directory")) {
                return true;
            }
        }

        return false;
    }

    /**
     * Emit all collected issues
     *
     * @return void
     */
    private static function display()
    {
        $collector = self::$issueCollector;

        $printer = self::$printer;

        foreach ($collector->getCollectedIssues() as $issue) {
            $printer->print($issue);
        }

        if ($collector instanceof BufferingCollector) {
            $collector->flush();
        }

        if ($printer instanceof BufferedPrinterInterface) {
            $printer->flush();
        }
    }

    /**
     * Save json encoded function&method signature to a map.
     * @return int - Exit code for process
     */
    private static function dumpSignaturesToFile(CodeBase $code_base, string $filename) : int
    {
        $encoded_signatures = json_encode($code_base->exportFunctionAndMethodSet(), JSON_PRETTY_PRINT);
        if (!file_put_contents($filename, $encoded_signatures)) {
            error_log(sprintf("Could not save contents to path '%s'\n", $filename));
            return EXIT_FAILURE;
        }
        return EXIT_SUCCESS;
    }

    /**
     * @return void
     */
    private static function printMemoryUsageSummary()
    {
        $memory = memory_get_usage()/1024/1024;
        $peak   = memory_get_peak_usage()/1024/1024;
        fwrite(STDERR, sprintf("Memory usage after analysis completed: %.02dMB/%.02dMB\n", $memory, $peak));
    }

    /**
     * @return void
     */
    public static function setPrinter(
        IssuePrinterInterface $printer
    ) {
        self::$printer = $printer;
    }

    /**
     * @param string $filename
     *
     * @return bool True if filename is ignored during analysis
     */
    public function isFilenameIgnored(string $filename) : bool
    {
        return self::isExcludedAnalysisFile($filename);
    }

    /**
     * Logs slow php options to stdout
     * @return void
     */
    private static function checkForSlowPHPOptions()
    {
        static $did_check = false;
        if ($did_check) {
            // Only perform this check once (e.g. in unit tests
            return;
        }
        $did_check = true;
        if (Config::getValue('skip_slow_php_options_warning')) {
            return;
        }
        $warned = false;
        // Unless debugging Phan itself, these two configurations are unnecessarily adding slowness.
        if (PHP_DEBUG) {
            fwrite(STDERR, "Warning: Phan is around twice as slow when php is compiled with --enable-debug (That option is only needed when debugging Phan itself).\n");
            $warned = true;
        }
        // We warn about xdebug in src/codebase.php, so skip that check here.
        if ($warned) {
            fwrite(STDERR, "(The above warning(s) about slow PHP settings can be disabled by setting 'skip_slow_php_options_warning' to true in .phan/config.php)\n");
        }
    }

    /**
     * Loads configured stubs for internal PHP extensions.
     * @return void
     */
    private static function loadConfiguredPHPExtensionStubs(CodeBase $code_base)
    {
        $stubs = Config::getValue('autoload_internal_extension_signatures');
        foreach ($stubs ?: [] as $extension_name => $path_to_extension) {
            // Prefer using reflection info from the running extension over what's in the stub files.
            // (The originals were already added to the CodeBase)
            if (\extension_loaded($extension_name)) {
                continue;
            }
            if (!\is_string($path_to_extension)) {
                throw new \InvalidArgumentException("Invalid autoload_internal_extension_signatures: path for $extension_name is not a string: value: " . var_export($path_to_extension, true));
            }
            $path_to_extension = Config::projectPath($path_to_extension);
            if (!is_file($path_to_extension)) {
                throw new \InvalidArgumentException("Invalid autoload_internal_extension_signatures: path for $extension_name is not a file: value: " . var_export($path_to_extension, true));
            }
            Analysis::parseFile($code_base, $path_to_extension, false, null, true);
        }
    }
}
<?php declare(strict_types=1);
namespace Phan;

/**
 * Utility for profiling Phan runs. Used if the profiler_enabled config setting is true.
 */
trait Profile
{
    /**
     * @var array<string,array<int,float>>
     */
    private static $label_delta_map = [];

    /**
     * Measure the clock-time taken to execute the given
     * closure and emit the time with the given label to
     * a log.
     *
     * @param $label
     * A label to emit with the time taken to run the
     * given closure
     *
     * @param \Closure $closure
     * Any closure to measure how long it takes to run
     *
     * @suppress PhanUnreferencedProtectedMethod (unused, may use something similar in the future)
     */
    protected static function time(string $label, \Closure $closure)
    {

        if (!Config::getValue('profiler_enabled')) {
            return $closure();
        }

        static $initialized = false;
        if (!$initialized) {
            self::initialize();
            $initialized = true;
        }

        // Measure the time to execute the given closure
        $start_time = microtime(true);
        $return_value = $closure();
        $end_time = microtime(true);

        // Emit a log message
        $delta = ($end_time - $start_time);

        self::$label_delta_map[$label][] = $delta;

        return $return_value;
    }

    /**
     * Initialize the profiler
     */
    private static function initialize()
    {

        // Create a shutdown function to emit the log when we're
        // all done
        register_shutdown_function(function () {
            $label_metric_map = [];

            // Compute whatever metric we care about
            foreach (self::$label_delta_map as $label => $delta_list) {
                $total_time = array_sum($delta_list);
                $count = count($delta_list);
                $average_time = $total_time/$count;
                $label_metric_map[$label] = [
                    $count,
                    $total_time,
                    $average_time
                ];
            }


            // Sort such that the highest metric value is on top
            uasort($label_metric_map, function ($a, $b) {
                return ($b[1] <=> $a[1]);
            });

            // Print it all out
            foreach ($label_metric_map as $label => $metrics) {
                print $label
                    . "\t"
                    . implode("\t", array_map(function (float $v) {
                        return sprintf("%0.6f", $v);
                    }, $metrics))
                    . "\n";
            }
        });
    }
}
<?php declare(strict_types=1);
namespace Phan;

use Phan\CodeBase\ClassMap;
use Phan\CodeBase\UndoTracker;
use Phan\Language\Context;
use Phan\Language\Element\ClassAliasRecord;
use Phan\Language\Element\ClassConstant;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Func;
use Phan\Language\Element\FunctionFactory;
use Phan\Language\Element\GlobalConstant;
use Phan\Language\Element\Method;
use Phan\Language\Element\Parameter;
use Phan\Language\Element\Property;
use Phan\Language\FQSEN\FullyQualifiedClassConstantName;
use Phan\Language\FQSEN\FullyQualifiedClassElement;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedGlobalConstantName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\FQSEN\FullyQualifiedPropertyName;
use Phan\Language\NamespaceMapEntry;
use Phan\Language\UnionType;
use Phan\Library\Map;
use Phan\Library\Set;

use ReflectionClass;

/**
 * A CodeBase represents the known state of a code base
 * we're analyzing.
 *
 * In order to understand internal classes, interfaces,
 * traits and functions, a CodeBase needs to be
 * initialized with the list of those elements begotten
 * before any classes are loaded.
 *
 * # Example
 * ```
 * // Grab these before we define our own classes
 * $internal_class_name_list = get_declared_classes();
 * $internal_interface_name_list = get_declared_interfaces();
 * $internal_trait_name_list = get_declared_traits();
 * $internal_function_name_list = get_defined_functions()['internal'];
 *
 * // Load any required code ...
 *
 * $code_base = new CodeBase(
 *     $internal_class_name_list,
 *     $internal_interface_name_list,
 *     $internal_trait_name_list,
 *     CodeBase::getPHPInternalConstantNameList(),
 *     $internal_function_name_list
 *  );
 *
 *  // Do stuff ...
 * ```
 *
 * This supports undoing some operations in the parse phase,
 * for a background daemon analyzing single files. (Phan\CodeBase\UndoTracker)
 *
 * @phan-file-suppress PhanPartialTypeMismatchReturn the way generic objects is type hinted is inadequate, etc.
 */
class CodeBase
{
    /**
     * @var Map
     * A map from FQSEN to an internal or user defined class
     */
    private $fqsen_class_map;

    /**
     * @var Map
     * A map from FQSEN to a user defined class
     */
    private $fqsen_class_map_user_defined;

    /**
     * @var Map
     * A map from FQSEN to an internal class
     */
    private $fqsen_class_map_internal;

    /**
     * @var Map
     * A map from FQSEN to a ReflectionClass
     */
    private $fqsen_class_map_reflection;

    /**
     * @var Map
     * A map from FQSEN to set of ClassAliasRecord objects
     */
    private $fqsen_alias_map;

    /**
     * @var Map
     * A map from FQSEN to a global constant
     */
    private $fqsen_global_constant_map;

    /**
     * @var Map
     * A map from FQSEN to function
     */
    private $fqsen_func_map;

    /**
     * @var Set
     * A set of internal function FQSENs to lazily initialize.
     * Entries are removed as new entries get added to fqsen_func_map.
     */
    private $internal_function_fqsen_set;

    /**
     * @var Set
     * The set of all methods
     */
    private $method_set;

    /**
     * @var Map
     * A map from FullyQualifiedClassName to a ClassMap,
     * an object that holds properties, methods and class
     * constants.
     */
    private $class_fqsen_class_map_map;

    /**
     * @var array<string,Set>
     * A map from a string method name to a Set of
     * Methods
     */
    private $name_method_map = [];

    /**
     * @var array<string,array<string,array<int,array<string,NamespaceMapEntry>>>>
     * Maps the file and namespace identifier to the use statements found in that namespace
     */
    private $parsed_namespace_maps = [];

    /**
     * @var array<string,array<string,int>>
     * Maps file paths to a set of file-level suppressions (E.g. 'PhanUnreferencedUseNormal', etc.)
     * The corresponding value is the number of times the issue was suppressed
     */
    private $file_level_suppression_set = [];

    /**
     * @var bool
     * If true, elements will be ensured to be hydrated
     * on demand as they are requested.
     */
    private $should_hydrate_requested_elements = false;

    /**
     * @var UndoTracker|null - undoes the addition of global constants, classes, functions, and methods.
     */
    private $undo_tracker;

    /**
     * @var bool
     */
    private $has_enabled_undo_tracker = false;

    /**
     * @var ?string (The currently parsed or analyzed file, if any. Used only for the crash reporting output)
     */
    private static $current_file = null;

    /**
     * Initialize a new CodeBase
     * TODO: Remove internal_function_name_list completely?
     * @param string[] $internal_class_name_list
     * @param string[] $internal_interface_name_list
     * @param string[] $internal_trait_name_list
     * @param string[] $internal_constant_name_list
     * @param string[] $internal_function_name_list
     */
    public function __construct(
        array $internal_class_name_list,
        array $internal_interface_name_list,
        array $internal_trait_name_list,
        array $internal_constant_name_list,
        array $internal_function_name_list
    ) {
        $this->fqsen_class_map = new Map;
        $this->fqsen_class_map_internal = new Map;
        $this->fqsen_class_map_reflection = new Map;
        $this->fqsen_class_map_user_defined = new Map;
        $this->fqsen_alias_map = new Map;
        $this->fqsen_global_constant_map = new Map;
        $this->fqsen_func_map = new Map;
        $this->class_fqsen_class_map_map = new Map;
        $this->method_set = new Set;
        $this->internal_function_fqsen_set = new Set;

        // Add any pre-defined internal classes, interfaces,
        // constants, traits and functions
        $this->addClassesByNames($internal_class_name_list);
        $this->addClassesByNames($internal_interface_name_list);
        $this->addClassesByNames($internal_trait_name_list);
        $this->addGlobalConstantsByNames($internal_constant_name_list);
        // We initialize the FQSENs early on so that they show up
        // in the proper casing.
        $this->addInternalFunctionsByNames($internal_function_name_list);
    }

    /**
     * @return void
     */
    public function enableUndoTracking()
    {
        if ($this->has_enabled_undo_tracker) {
            throw new \RuntimeException("Undo tracking already enabled");
        }
        $this->has_enabled_undo_tracker = true;
        $this->undo_tracker = new UndoTracker();
    }

    /**
     * @return void
     */
    public function disableUndoTracking()
    {
        if (!$this->has_enabled_undo_tracker) {
            throw new \RuntimeException("Undo tracking was never enabled");
        }
        $this->undo_tracker = null;
    }

    public function isUndoTrackingEnabled() : bool
    {
        return $this->undo_tracker !== null;
    }

    /**
     * @return void
     */
    public function setShouldHydrateRequestedElements(
        bool $should_hydrate_requested_elements
    ) {
        $this->should_hydrate_requested_elements =
            $should_hydrate_requested_elements;
    }

    /**
     * @return array<int,string> - The list of files which are successfully parsed.
     * This changes whenever the file list is reloaded from disk.
     * This also includes files which don't declare classes or functions or globals,
     * because those files use classes/functions/constants.
     *
     * (This is the list prior to any analysis exclusion or whitelisting steps)
     */
    public function getParsedFilePathList() : array
    {
        if ($this->undo_tracker) {
            return $this->undo_tracker->getParsedFilePathList();
        }
        throw new \RuntimeException("Calling getParsedFilePathList without an undo tracker");
    }

    /**
     * @return int The size of $this->getParsedFilePathList()
     */
    public function getParsedFilePathCount() : int
    {
        if ($this->undo_tracker) {
            return $this->undo_tracker->getParsedFilePathCount();
        }
        throw new \RuntimeException("Calling getParsedFilePathCount without an undo tracker");
    }

    /**
     * @param string|null $current_parsed_file
     * @return void
     */
    public function setCurrentParsedFile($current_parsed_file)
    {
        self::$current_file = $current_parsed_file;
        if ($this->undo_tracker) {
            $this->undo_tracker->setCurrentParsedFile($current_parsed_file);
        }
    }

    /**
     * Sets the currently analyzed file, to improve Phan's crash reporting.
     * @param string|null $current_analyzed_file
     * @return void
     */
    public function setCurrentAnalyzedFile($current_analyzed_file)
    {
        self::$current_file = $current_analyzed_file;
    }

    /**
     * @return ?string
     * @internal - For use only by the phan error handler
     */
    public static function getMostRecentlyParsedOrAnalyzedFile()
    {
        return self::$current_file;
    }

    /**
     * Called when a file is unparseable.
     * Removes the classes and functions, etc. from an older version of the file, if one exists.
     * @return void
     */
    public function recordUnparseableFile(string $current_parsed_file)
    {
        if ($this->undo_tracker) {
            $this->undo_tracker->recordUnparseableFile($this, $current_parsed_file);
        }
    }

    /**
     * @param string[] $class_name_list
     * A list of class names to load type information for
     *
     * @return void
     */
    private function addClassesByNames(array $class_name_list)
    {
        foreach ($class_name_list as $class_name) {
            $reflection_class = new \ReflectionClass($class_name);
            if (!$reflection_class->isUserDefined()) {
                // include internal classes, but not external classes such as composer
                $this->addReflectionClass($reflection_class);
            }
        }
    }

    /**
     * @param string[] $const_name_list
     * A list of global constant names to load type information for
     *
     * @return void
     */
    private function addGlobalConstantsByNames(array $const_name_list)
    {
        foreach ($const_name_list as $const_name) {
            if (!$const_name) {
                // #1015 workaround for empty constant names ('' and '0').
                fprintf(STDERR, "Saw constant with empty name of %s. There may be a bug in a PECL extension you are using (php -m will list those)\n", var_export($const_name, true));
                continue;
            }
            try {
                $const_obj = GlobalConstant::fromGlobalConstantName($const_name);
                $this->addGlobalConstant($const_obj);
            } catch (\InvalidArgumentException $e) {
                // Workaround for windows bug in #1011
                if (\strncmp($const_name, "\0__COMPILER_HALT_OFFSET__\0", 26) === 0) {
                    continue;
                }
                fprintf(STDERR, "Failed to load global constant value for %s, continuing: %s\n", var_export($const_name, true), $e->getMessage());
            }
        }
    }

    /**
     * @param array<int,string> $new_file_list
     * @param array<string,string> $file_mapping_contents maps relative path to absolute paths
     * @return array<int,string> - Subset of $new_file_list which changed on disk and has to be parsed again. Automatically unparses the old versions of files which were modified.
     */
    public function updateFileList(array $new_file_list, array $file_mapping_contents = [])
    {
        if ($this->undo_tracker) {
            return $this->undo_tracker->updateFileList($this, $new_file_list, $file_mapping_contents);
        }
        throw new \RuntimeException("Calling updateFileList without undo tracker");
    }

    /**
     * @param string $file_name
     * @return bool - true if caller should replace contents
     */
    public function beforeReplaceFileContents(string $file_name)
    {
        if ($this->undo_tracker) {
            return $this->undo_tracker->beforeReplaceFileContents($this, $file_name);
        }
        throw new \RuntimeException("Calling replaceFileContents without undo tracker");
    }

    public function eagerlyLoadAllSignatures()
    {
        $this->getInternalClassMap();  // Force initialization of remaining internal php classes to reduce latency of future analysis requests.
        $this->forceLoadingInternalFunctions();  // Force initialization of internal functions to reduce latency of future analysis requests.
    }

    /**
     * @return void
     */
    public function forceLoadingInternalFunctions()
    {
        $internal_function_fqsen_set = $this->internal_function_fqsen_set;
        $this->internal_function_fqsen_set = new Set;  // Don't need to track these any more.
        foreach ($internal_function_fqsen_set as $function_fqsen) {
            // hasFunctionWithFQSEN will automatically load $function_name, **unless** we don't have a signature for that function.
            if (!$this->hasFunctionWithFQSEN($function_fqsen)) {
                // Force loading these even if automatic loading failed.
                // (Shouldn't happen, the function list is fetched from reflection by callers.
                $function_alternate_generator = FunctionFactory::functionListFromReflectionFunction(
                    $function_fqsen,
                    new \ReflectionFunction($function_fqsen->getNamespacedName())
                );
                foreach ($function_alternate_generator as $function) {
                    $this->addFunction($function);
                }
            }
        }
    }

    /**
     * @param string[] $internal_function_name_list
     * @return void
     */
    private function addInternalFunctionsByNames(array $internal_function_name_list)
    {
        foreach ($internal_function_name_list as $function_name) {
            $this->internal_function_fqsen_set->attach(FullyQualifiedFunctionName::makeFromExtractedNamespaceAndName($function_name));
        }
    }

    /**
     * Clone dependent objects when cloning this object.
     */
    public function __clone()
    {
        $this->fqsen_class_map =
            $this->fqsen_class_map->deepCopyValues();

        $this->fqsen_class_map_user_defined =
            new Map;

        $this->fqsen_class_map_internal =
            new Map;

        foreach ($this->fqsen_class_map as $fqsen => $clazz) {
            if ($clazz->isPHPInternal()) {
                $this->fqsen_class_map_internal[$fqsen] = $clazz;
            } else {
                $this->fqsen_class_map_user_defined[$fqsen] = $clazz;
            }
        }

        $this->fqsen_class_map_reflection =
            clone($this->fqsen_class_map_reflection);

        $this->fqsen_alias_map =
            $this->fqsen_alias_map->deepCopyValues();

        $this->fqsen_global_constant_map =
            $this->fqsen_global_constant_map->deepCopyValues();

        $this->fqsen_func_map =
            $this->fqsen_func_map->deepCopyValues();

        // NOTE: If this were to become a deep copy, this would also have to update class_map.
        // (That also has references to Method, which should be shared in the resulting clone)
        $this->method_set = clone($this->method_set);

        $this->class_fqsen_class_map_map =
            $this->class_fqsen_class_map_map->deepCopyValues();

        $this->internal_function_fqsen_set =
            clone($this->internal_function_fqsen_set);

        $name_method_map = $this->name_method_map;
        $this->name_method_map = [];
        foreach ($name_method_map as $name => $method_map) {
            $this->name_method_map[$name] = $method_map->deepCopy();
        }
    }

    /**
     * @param array{clone:CodeBase,callbacks:?(Closure():void)[]} $restore_point
     * @return void
     */
    public function restoreFromRestorePoint(array $restore_point)
    {
        $clone = $restore_point['clone'];
        $this->undo_tracker             = $clone->undo_tracker;
        $this->has_enabled_undo_tracker = $clone->has_enabled_undo_tracker;

        // TODO: Restore the inner state of Clazz objects as well
        // (e.g. memoizations, types added in method/analysis phases, plugin changes, etc.
        // NOTE: Type::clearAllMemoizations is called elsewhere already.
        $this->fqsen_class_map              = $clone->fqsen_class_map;
        $this->fqsen_class_map_user_defined = $clone->fqsen_class_map_user_defined;
        $this->fqsen_class_map_internal     = $clone->fqsen_class_map_internal;
        $this->fqsen_class_map_reflection   = $clone->fqsen_class_map_reflection;
        $this->fqsen_alias_map              = $clone->fqsen_alias_map;
        $this->fqsen_global_constant_map    = $clone->fqsen_global_constant_map;
        $this->fqsen_func_map               = $clone->fqsen_func_map;
        $this->internal_function_fqsen_set  = $clone->internal_function_fqsen_set;
        $this->method_set                   = $clone->method_set;
        $this->class_fqsen_class_map_map    = $clone->class_fqsen_class_map_map;
        $this->name_method_map              = $clone->name_method_map;

        $this->parsed_namespace_maps        = $clone->parsed_namespace_maps;
        foreach ($restore_point['callbacks'] as $callback) {
            if ($callback) {
                $callback();
            }
        }
    }

    /**
     * For use by daemon mode when running without pcntl
     * Returns a serialized representation of everything in this CodeBase.
     * @internal
     * @return array{clone:CodeBase,callbacks:(?Closure():void)[]}
     */
    public function createRestorePoint() : array
    {
        // Create a deep copy of this CodeBase
        $clone = clone($this);
        // make a deep copy of the NamespaceMapEntry objects within parsed_namespace_maps
        $clone->parsed_namespace_maps = unserialize(serialize($clone->parsed_namespace_maps));
        /** @var array<int,?Closure()> */
        $callbacks = [];
        // Create callbacks to restore classes
        foreach ($this->fqsen_class_map as $class) {
            $callbacks[] = $class->createRestoreCallback();
        }
        // Create callbacks to restore methods and global functions
        foreach ($this->fqsen_func_map as $func) {
            $callbacks[] = $func->createRestoreCallback();
        }
        // Create callbacks to back up global constants
        // (They may refer to constants from other files.
        // The other files may change.)
        foreach ($this->fqsen_global_constant_map as $const) {
            $callbacks[] = $const->createRestoreCallback();
        }
        // Create callbacks to back up global constants
        // (They may refer to constants from other files.
        // The other files may change.)
        foreach ($this->class_fqsen_class_map_map as $class_map) {
            // Create callbacks to back up class constants and properties.
            // Methods were already backed up.
            foreach ($class_map->getClassConstantMap() as $const) {
                $callbacks[] = $const->createRestoreCallback();
            }
            foreach ($class_map->getPropertyMap() as $property) {
                $callbacks[] = $property->createRestoreCallback();
            }
        }

        return [
            'clone' => $clone,
            'callbacks' => $callbacks,
        ];
    }

    /**
     * @return CodeBase
     * A new code base is returned which is a shallow clone
     * of this one, which is to say that the sets and maps
     * of elements themselves are cloned, but the keys and
     * values within those sets and maps are not cloned.
     *
     * Updates to elements will bleed through code bases
     * with only shallow clones. See
     * https://github.com/phan/phan/issues/257
     */
    public function shallowClone() : CodeBase
    {
        $code_base = new CodeBase([], [], [], [], []);
        $code_base->fqsen_class_map =
            clone($this->fqsen_class_map);
        $code_base->fqsen_class_map_user_defined =
            clone($this->fqsen_class_map_user_defined);
        $code_base->fqsen_class_map_internal =
            clone($this->fqsen_class_map_internal);
        $code_base->fqsen_class_map_reflection =
            clone($this->fqsen_class_map_reflection);
        $code_base->fqsen_alias_map =
            clone($this->fqsen_alias_map);

        $code_base->fqsen_global_constant_map =
            clone($this->fqsen_global_constant_map);
        $code_base->fqsen_func_map =
            clone($this->fqsen_func_map);
        $code_base->internal_function_fqsen_set =
            clone($this->internal_function_fqsen_set);
        $code_base->class_fqsen_class_map_map =
            clone($this->class_fqsen_class_map_map);
        $code_base->method_set =
            clone($this->method_set);
        return $code_base;
    }

    /**
     * @param Clazz $class
     * A class to add.
     *
     * @return void
     */
    public function addClass(Clazz $class)
    {
        // Map the FQSEN to the class
        $fqsen = $class->getFQSEN();
        $this->fqsen_class_map[$fqsen] = $class;
        $this->fqsen_class_map_user_defined[$fqsen] = $class;
        if ($this->undo_tracker) {
            $this->undo_tracker->recordUndo(function (CodeBase $inner) use ($fqsen) {
                Daemon::debugf("Undoing addClass %s\n", $fqsen);
                $inner->fqsen_class_map->offsetUnset($fqsen);
                $inner->fqsen_class_map_user_defined->offsetUnset($fqsen);
                // unset($inner->fqsen_class_map_reflection[$fqsen]);  // should not be necessary
                $inner->class_fqsen_class_map_map->offsetUnset($fqsen);
            });
        }
    }

    /**
     * This should be called in the parse phase
     *
     * @param array<int,array<string,NamespaceMapEntry>> $namespace_map
     *
     * @return void
     * @internal
     */
    public function addParsedNamespaceMap(string $file, string $namespace, int $id, array $namespace_map)
    {
        $key = "$namespace@$id";
        // print("Adding $file $key count=" .count($namespace_map) . "\n");
        // debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        $this->parsed_namespace_maps[$file][$key] = $namespace_map;
        if ($this->undo_tracker) {
            $this->undo_tracker->recordUndo(function (CodeBase $inner) use ($file, $key) {
                Daemon::debugf("Undoing addParsedNamespaceMap file = %s namespace = %s\n", $file, $key);
                unset($inner->parsed_namespace_maps[$file][$key]);
                // Hack: addParsedNamespaceMap is called at least once per each file, so unset file-level suppressions at the same time in daemon mode
                unset($inner->file_level_suppression_set[$file]);
            });
        }
    }

    /**
     * This should be called in the analysis phase.
     * It retrieves the NamespaceMapEntry built in the parse pharse
     * (This is implemented this way to allow Phan to know if 'use Foo\Bar' was ever used and warn if it wasn't.)
     *
     * @param string $file the value of $context->getFile()
     * @param string $namespace the namespace value. Probably redundant.
     * @param int $id (An incrementing counter for namespaces. 0 or 1 in single namespace/absent namespace files)
     * @return array<int,array<string,NamespaceMapEntry>> $namespace_map
     * @internal
     */
    public function getNamespaceMapFromParsePhase(string $file, string $namespace, int $id)
    {
        $key = "$namespace@$id";

        // I'd hope that this is always defined when this is called.
        // However, it may not be if files rapidly change and add/remove namespaces?
        return $this->parsed_namespace_maps[$file][$key] ?? [];
    }

    /**
     * @param ReflectionClass $class
     * A class to add, lazily.
     *
     * @return void
     */
    public function addReflectionClass(ReflectionClass $class)
    {
        // Map the FQSEN to the class
        $class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString($class->getName());
        $this->fqsen_class_map_reflection[$class_fqsen] = $class;
    }

    /**
     * Call this to record the existence of a class_alias in the global scope.
     * After parse phase is complete (And daemonize has split off a new process),
     * call resolveClassAliases() to create FQSEN entries.
     *
     * @param FullyQualifiedClassName $original
     *  an existing class to alias to
     *
     * @param FullyQualifiedClassName $alias
     *  a name to alias $original to
     *
     *
     * @return void
     */
    public function addClassAlias(
        FullyQualifiedClassName $original,
        FullyQualifiedClassName $alias,
        Context $context,
        int $lineno
    ) {
        if (!isset($this->fqsen_alias_map[$original])) {
            $this->fqsen_alias_map[$original] = new Set();
        }
        $alias_record = new ClassAliasRecord($alias, $context, $lineno);
        $this->fqsen_alias_map[$original]->attach($alias_record);

        if ($this->undo_tracker) {
            // TODO: Track a count of aliases instead? This doesn't work in daemon mode if multiple files add the same alias to the same class.
            // TODO: Allow .phan/config.php to specify aliases or precedences for aliases?
            $this->undo_tracker->recordUndo(function (CodeBase $inner) use ($original, $alias_record) {
                $fqsen_alias_map = $inner->fqsen_alias_map[$original] ?? null;
                if ($fqsen_alias_map) {
                    $fqsen_alias_map->detach($alias_record);
                    if ($fqsen_alias_map->count() == 0) {
                        unset($inner->fqsen_alias_map[$original]);
                    }
                }
            });
        }
    }

    /**
     * @return void
     */
    public function resolveClassAliases()
    {
        \assert(!$this->undo_tracker, 'should only call this after daemon mode is finished');
        // loop through fqsen_alias_map and add entries to fqsen_class_map.
        foreach ($this->fqsen_alias_map as $original_fqsen => $alias_set) {
            $this->resolveClassAliasesForAliasSet($original_fqsen, $alias_set);
        }
    }

    /**
     * @return void
     */
    private function resolveClassAliasesForAliasSet(FullyQualifiedClassName $original_fqsen, Set $alias_set)
    {
        if (!$this->hasClassWithFQSEN($original_fqsen)) {
            // The original class does not exist.
            // Emit issues at the point of every single class_alias call with that original class.
            foreach ($alias_set as $alias_record) {
                \assert($alias_record instanceof ClassAliasRecord);
                Issue::maybeEmit(
                    $this,
                    $alias_record->context,
                    Issue::UndeclaredClassAliasOriginal,
                    $alias_record->lineno,
                    $original_fqsen,
                    $alias_record->alias_fqsen
                );
            }
            return;
        }
        // The original class exists. Attempt to create aliases of the original class.
        $class = $this->getClassByFQSEN($original_fqsen);
        foreach ($alias_set as $alias_record) {
            \assert($alias_record instanceof ClassAliasRecord);
            $alias_fqsen = $alias_record->alias_fqsen;
            // Don't do anything if there is a real class, or if an earlier class_alias created an alias.
            if ($this->hasClassWithFQSEN($alias_fqsen)) {
                // Emit a different issue type to make filtering out false positives easier.
                $clazz = $this->getClassByFQSEN($alias_fqsen);
                Issue::maybeEmit(
                    $this,
                    $alias_record->context,
                    Issue::RedefineClassAlias,
                    $alias_record->lineno,
                    $alias_fqsen,
                    $alias_record->context->getFile(),
                    $alias_record->lineno,
                    $clazz->getFQSEN(),
                    $clazz->getFileRef()->getFile(),
                    $clazz->getFileRef()->getLineNumberStart()
                );
            } else {
                $this->fqsen_class_map[$alias_fqsen] = $class;
            }
        }
    }

    /**
     * @return bool
     * True if a Clazz with the given FQSEN exists
     */
    public function hasClassWithFQSEN(
        FullyQualifiedClassName $fqsen
    ) : bool {
        if ($this->fqsen_class_map->offsetExists($fqsen)) {
            return true;
        }
        return $this->lazyLoadPHPInternalClassWithFQSEN($fqsen);
    }

    /**
     * @return bool
     * True if a Clazz with the given FQSEN was created
     */
    private function lazyLoadPHPInternalClassWithFQSEN(
        FullyQualifiedClassName $fqsen
    ) : bool {
        $reflection_class = $this->fqsen_class_map_reflection[$fqsen] ?? null;
        if ($reflection_class !== null) {
            $this->loadPHPInternalClassWithFQSEN($fqsen, $reflection_class);
            return true;
        }
        return false;
    }

    /** @return void */
    private function loadPHPInternalClassWithFQSEN(
        FullyQualifiedClassName $fqsen,
        ReflectionClass $reflection_class
    ) {
        $class = Clazz::fromReflectionClass($this, $reflection_class);
        $this->fqsen_class_map[$fqsen] = $class;
        $this->fqsen_class_map_internal[$fqsen] = $class;
        unset($this->fqsen_class_map_reflection[$fqsen]);
    }

    /**
     * @param FullyQualifiedClassName $fqsen
     * The FQSEN of a class to get
     *
     * @return Clazz
     * A class with the given FQSEN
     */
    public function getClassByFQSEN(
        FullyQualifiedClassName $fqsen
    ) : Clazz {
        $clazz = $this->fqsen_class_map[$fqsen];

        // This is an optimization that saves us a few minutes
        // on very large code bases.
        //
        // Instead of 'hydrating' all classes (expanding their
        // types and importing parent methods, properties, etc.)
        // all in one go, we just do it on the fly as they're
        // requested. When running as multiple processes this
        // lets us avoid a significant amount of hydration per
        // process.
        if ($this->should_hydrate_requested_elements) {
            $clazz->hydrate($this);
        }

        return $clazz;
    }

    /**
     * @param FullyQualifiedClassName $fqsen
     * The FQSEN of a class to get
     *
     * @return Clazz
     * A class with the given FQSEN
     */
    public function getClassByFQSENWithoutHydrating(
        FullyQualifiedClassName $fqsen
    ) : Clazz {
        return $this->fqsen_class_map[$fqsen];
    }

    /**
     * @param FullyQualifiedClassName $original
     * The FQSEN of class to get aliases of
     *
     * @return ClassAliasRecord[]
     * A list of all aliases of $original (and their definitions)
     */
    public function getClassAliasesByFQSEN(
        FullyQualifiedClassName $original
    ) : array {
        if (isset($this->fqsen_alias_map[$original])) {
            return $this->fqsen_alias_map[$original]->toArray();
        }

        return [];
    }


    /**
     * @return Map
     * A list of all classes
     *
     * @deprecated - use hasClassWithFQSEN and getClassByFQSEN or getUserDefinedClassMap instead
     */
    public function getClassMap() : Map
    {
        $this->getInternalClassMap(); // Force initialization of remaining internal php classes
        return $this->fqsen_class_map;
    }

    /**
     * @return Map
     * A map from FQSENs to classes which are internal.
     */
    public function getUserDefinedClassMap() : Map
    {
        return $this->fqsen_class_map_user_defined;
    }

    /**
     * @return Map
     * A list of all classes which are internal.
     */
    public function getInternalClassMap() : Map
    {
        if (\count($this->fqsen_class_map_reflection) > 0) {
            $fqsen_class_map_reflection = $this->fqsen_class_map_reflection;
            // Free up memory used by old class map. Prevent it from being freed before we can load it manually.
            $this->fqsen_class_map_reflection = new Map;
            foreach ($fqsen_class_map_reflection as $fqsen => $reflection_class) {
                $this->loadPHPInternalClassWithFQSEN($fqsen, $reflection_class);
            }
        }
        // TODO: Resolve internal classes and optimize the implementation.
        return $this->fqsen_class_map_internal;
    }

    /**
     * @param Method $method
     * A method to add to the code base
     *
     * @return void
     */
    public function addMethod(Method $method)
    {
        // Add the method to the map
        $this->getClassMapByFQSEN(
            $method->getFQSEN()
        )->addMethod($method);

        $this->method_set->attach($method);

        // If we're doing dead code detection(or something else) and this is a
        // method, map the name to the FQSEN so we can do hail-
        // mary references.
        if (Config::get_track_references()) {
            if (empty($this->name_method_map[$method->getFQSEN()->getNameWithAlternateId()])) {
                $this->name_method_map[$method->getFQSEN()->getNameWithAlternateId()] = new Set;
            }
            $this->name_method_map[$method->getFQSEN()->getNameWithAlternateId()]->attach($method);
        }
        if ($this->undo_tracker) {
            // The addClass's recordUndo should remove the class map. Only need to remove it from method_set
            $this->undo_tracker->recordUndo(function (CodeBase $inner) use ($method) {
                $inner->method_set->detach($method);
            });
        }
    }

    /**
     * @return bool
     * True if an element with the given FQSEN exists
     */
    public function hasMethodWithFQSEN(
        FullyQualifiedMethodName $fqsen
    ) : bool {
        return $this->getClassMapByFullyQualifiedClassName(
            $fqsen->getFullyQualifiedClassName()
        )->hasMethodWithName(
            $fqsen->getNameWithAlternateId()
        );
    }

    /**
     * @param FullyQualifiedMethodName $fqsen
     * The FQSEN of a method to get
     *
     * @return Method
     * A method with the given FQSEN
     */
    public function getMethodByFQSEN(
        FullyQualifiedMethodName $fqsen
    ) : Method {
        return $this->getClassMapByFullyQualifiedClassName(
            $fqsen->getFullyQualifiedClassName()
        )->getMethodByName(
            $fqsen->getNameWithAlternateId()
        );
    }

    /**
     * @return Method[]
     * The set of methods associated with the given class
     */
    public function getMethodMapByFullyQualifiedClassName(
        FullyQualifiedClassName $fqsen
    ) : array {
        return $this->getClassMapByFullyQualifiedClassName(
            $fqsen
        )->getMethodMap();
    }

    /**
     * @return Set
     * A set of all known methods with the given name
     */
    public function getMethodSetByName(string $name) : Set
    {
        \assert(
            Config::get_track_references(),
            __METHOD__ . ' can only be called when dead code '
            . ' detection (or force_tracking_references) is enabled.'
        );

        return $this->name_method_map[$name] ?? new Set;
    }

    /**
     * @return Set
     * The set of all methods and functions
     *
     * @deprecated - Use getFunctionMap and getMethodSet instead, this is slow and may be removed in a future release.
     */
    public function getFunctionAndMethodSet() : Set
    {
        $set = clone($this->method_set);
        foreach ($this->fqsen_func_map as $value) {
            $set->attach($value);
        }
        return $set;
    }

    /**
     * @return Set
     * The set of all methods that Phan is tracking.
     */
    public function getMethodSet() : Set
    {
        return $this->method_set;
    }

    /**
     * @return array<string,array<string,string>>
     * A human readable encoding of $this->func_and_method_set [string $function_or_method_name => [int|string $pos => string $spec]]
     * Excludes internal functions and methods.
     *
     * This can be used for debugging Phan's inference
     * @suppress PhanDeprecatedFunction
     */
    public function exportFunctionAndMethodSet() : array
    {
        $result = [];
        foreach ($this->getFunctionAndMethodSet() as $function_or_method) {
            if ($function_or_method->isPHPInternal()) {
                continue;
            }
            $fqsen = $function_or_method->getFQSEN();
            $function_or_method_name = (string)$fqsen;
            $signature = [(string)$function_or_method->getUnionType()];
            foreach ($function_or_method->getParameterList() as $param) {
                $name = $param->getName();
                $paramType = (string)$param->getUnionType();
                if ($param->isVariadic()) {
                    $name = '...' . $name;
                }
                if ($param->isPassByReference()) {
                    $name = '&' . $name;
                }
                if ($param->isOptional()) {
                    $name = $name . '=';
                }
                $signature[$name] = $paramType;
            }
            $result[$function_or_method_name] = $signature;
        }
        \ksort($result);
        return $result;
    }

    /**
     * @param Func $function
     * A function to add to the code base
     *
     * @return void
     */
    public function addFunction(Func $function)
    {
        // Add it to the map of functions
        $this->fqsen_func_map[$function->getFQSEN()] = $function;

        if ($this->undo_tracker) {
            $this->undo_tracker->recordUndo(function (CodeBase $inner) use ($function) {
                Daemon::debugf("Undoing addFunction on %s\n", $function->getFQSEN());
                unset($inner->fqsen_func_map[$function->getFQSEN()]);
            });
        }
    }

    /**
     * @return bool
     * True if an element with the given FQSEN exists
     */
    public function hasFunctionWithFQSEN(
        FullyQualifiedFunctionName $fqsen
    ) : bool {
        $has_function = $this->fqsen_func_map->contains($fqsen);

        if ($has_function) {
            return true;
        }

        // Make the following checks:
        //
        // 1. this is an internal function that hasn't been loaded yet.
        // 2. Unless 'ignore_undeclared_functions_with_known_signatures' is true, require that the current php binary or it's extensions define this function before that.
        return $this->hasInternalFunctionWithFQSEN($fqsen);
    }

    /**
     * @param FullyQualifiedFunctionName $fqsen
     * The FQSEN of a function to get
     *
     * @return Func
     * A function with the given FQSEN
     */
    public function getFunctionByFQSEN(
        FullyQualifiedFunctionName $fqsen
    ) : Func {
        return $this->fqsen_func_map[$fqsen];
    }

    /**
     * @return Map
     */
    public function getFunctionMap() : Map
    {
        return $this->fqsen_func_map;
    }

    /**
     * @param ClassConstant $class_constant
     * A class constant to add to the code base
     *
     * @return void
     */
    public function addClassConstant(ClassConstant $class_constant)
    {
        $this->getClassMapByFullyQualifiedClassName(
            $class_constant->getClassFQSEN()
        )->addClassConstant($class_constant);
    }

    /**
     * @return bool
     * True if an element with the given FQSEN exists
     */
    public function hasClassConstantWithFQSEN(
        FullyQualifiedClassConstantName $fqsen
    ) : bool {
        return $this->getClassMapByFullyQualifiedClassName(
            $fqsen->getFullyQualifiedClassName()
        )->hasClassConstantWithName($fqsen->getNameWithAlternateId());
    }

    /**
     * @param FullyQualifiedClassConstantName $fqsen
     * The FQSEN of a class constant to get
     *
     * @return ClassConstant
     * A class constant with the given FQSEN
     */
    public function getClassConstantByFQSEN(
        FullyQualifiedClassConstantName $fqsen
    ) : ClassConstant {
        return $this->getClassMapByFullyQualifiedClassName(
            $fqsen->getFullyQualifiedClassName()
        )->getClassConstantByName($fqsen->getNameWithAlternateId());
    }

    /**
     * @return ClassConstant[]
     * The set of class constants associated with the given class
     */
    public function getClassConstantMapByFullyQualifiedClassName(
        FullyQualifiedClassName $fqsen
    ) : array {
        return $this->getClassMapByFullyQualifiedClassName(
            $fqsen
        )->getClassConstantMap();
    }

    /**
     * @param GlobalConstant $global_constant
     * A global constant to add to the code base
     *
     * @return void
     */
    public function addGlobalConstant(GlobalConstant $global_constant)
    {
        $this->fqsen_global_constant_map[
            $global_constant->getFQSEN()
        ] = $global_constant;
        if ($this->undo_tracker) {
            $this->undo_tracker->recordUndo(function (CodeBase $inner) use ($global_constant) {
                Daemon::debugf("Undoing addGlobalConstant on %s\n", $global_constant->getFQSEN());
                unset($inner->fqsen_global_constant_map[$global_constant->getFQSEN()]);
            });
        }
    }

    /**
     * @return bool
     * True if an element with the given FQSEN exists
     */
    public function hasGlobalConstantWithFQSEN(
        FullyQualifiedGlobalConstantName $fqsen
    ) : bool {
        return $this->fqsen_global_constant_map->offsetExists($fqsen);
    }

    /**
     * @param FullyQualifiedGlobalConstantName $fqsen
     * The FQSEN of a global constant to get
     *
     * @return GlobalConstant
     * A global constant with the given FQSEN
     */
    public function getGlobalConstantByFQSEN(
        FullyQualifiedGlobalConstantName $fqsen
    ) : GlobalConstant {
        return $this->fqsen_global_constant_map[$fqsen];
    }

    /**
     * @return Map
     */
    public function getGlobalConstantMap() : Map
    {
        return $this->fqsen_global_constant_map;
    }

    /**
     * @param Property $property
     * A property to add to the code base
     *
     * @return void
     */
    public function addProperty(Property $property)
    {
        return $this->getClassMapByFullyQualifiedClassName(
            $property->getClassFQSEN()
        )->addProperty($property);
    }

    /**
     * @return bool
     * True if an element with the given FQSEN exists
     */
    public function hasPropertyWithFQSEN(
        FullyQualifiedPropertyName $fqsen
    ) : bool {
        return $this->getClassMapByFullyQualifiedClassName(
            $fqsen->getFullyQualifiedClassName()
        )->hasPropertyWithName($fqsen->getNameWithAlternateId());
    }

    /**
     * @param FullyQualifiedPropertyName $fqsen
     * The FQSEN of a property to get
     *
     * @return Property
     * A property with the given FQSEN
     */
    public function getPropertyByFQSEN(
        FullyQualifiedPropertyName $fqsen
    ) : Property {
        return $this->getClassMapByFullyQualifiedClassName(
            $fqsen->getFullyQualifiedClassName()
        )->getPropertyByName($fqsen->getNameWithAlternateId());
    }

    /**
     * @return Property[]
     * The set of properties associated with the given class
     */
    public function getPropertyMapByFullyQualifiedClassName(
        FullyQualifiedClassName $fqsen
    ) : array {
        return $this->getClassMapByFullyQualifiedClassName(
            $fqsen
        )->getPropertyMap();
    }

    /**
     * @param FullyQualifiedClassElement $fqsen
     * The FQSEN of a class element
     *
     * @return ClassMap
     * Get the class map for an FQSEN representing
     * a class element
     */
    private function getClassMapByFQSEN(
        FullyQualifiedClassElement $fqsen
    ) : ClassMap {
        return $this->getClassMapByFullyQualifiedClassName(
            $fqsen->getFullyQualifiedClassName()
        );
    }

    /**
     * @param FullyQualifiedClassName $fqsen
     * The FQSEN of a class
     *
     * @return ClassMap
     * Get the class map for an FQSEN representing
     * a class element
     */
    private function getClassMapByFullyQualifiedClassName(
        FullyQualifiedClassName $fqsen
    ) : ClassMap {
        $class_fqsen_class_map_map = $this->class_fqsen_class_map_map;
        if ($class_fqsen_class_map_map->offsetExists($fqsen)) {
            return $class_fqsen_class_map_map->offsetGet($fqsen);
        }
        $class_fqsen_class_map_map->offsetSet($fqsen, new ClassMap);
        return $class_fqsen_class_map_map->offsetGet($fqsen);
    }

    /**
     * @return Map
     */
    public function getClassMapMap() : Map
    {
        return $this->class_fqsen_class_map_map;
    }

    /**
     * @param FullyQualifiedFunctionName $fqsen
     * The FQSEN of a function we'd like to look up
     *
     * @return bool
     * If the FQSEN represents an internal function that
     * hasn't been loaded yet, true is returned.
     */
    private function hasInternalFunctionWithFQSEN(
        FullyQualifiedFunctionName $fqsen
    ) : bool {
        $canonical_fqsen = $fqsen->withAlternateId(0);
        $found = isset($this->internal_function_fqsen_set[$canonical_fqsen]);
        if (!$found) {
            // Act as though functions don't exist if they aren't loaded into the php binary
            // running phan (or that binary's extensions), even if the signature map contains them.
            // (All of the functions were loaded during initialization)
            //
            // Also, skip over user-defined global functions defined **by Phan** and its dependencies for analysis
            if (!Config::get()->ignore_undeclared_functions_with_known_signatures) {
                return false;
            }
            // If we already created the alternates, do nothing.
            // TODO: This assumes we call hasFunctionWithFQSEN before adding.
            if ($this->fqsen_func_map->offsetExists($canonical_fqsen)) {
                return false;
            }
        }

        $name = $canonical_fqsen->getName();
        if ($canonical_fqsen->getNamespace() !== '\\') {
            $name = \ltrim($canonical_fqsen->getNamespace(), '\\') . '\\' . $name;
        }

        // For elements in the root namespace, check to see if
        // there's a static method signature for something that
        // hasn't been loaded into memory yet and create a
        // method out of it as it's requested

        $function_signature_map =
            UnionType::internalFunctionSignatureMap(Config::get_closest_target_php_version_id());

        // Don't need to track this any more
        unset($this->internal_function_fqsen_set[$canonical_fqsen]);

        if (isset($function_signature_map[$name])) {
            $signature = $function_signature_map[$name];

            // Add each method returned for the signature
            foreach (FunctionFactory::functionListFromSignature(
                $canonical_fqsen,
                $signature
            ) as $function) {
                if ($found) {
                    $reflection_function = new \ReflectionFunction($name);
                    $function->setIsDeprecated($reflection_function->isDeprecated());
                    $function->setRealReturnType(UnionType::fromReflectionType($reflection_function->getReturnType()));
                    $function->setRealParameterList(Parameter::listFromReflectionParameterList($reflection_function->getParameters()));
                }
                $this->addFunction($function);
            }

            return true;
        } elseif ($found) {
            // Phan doesn't have extended information for the signature for this function, but the function exists.
            foreach (FunctionFactory::functionListFromReflectionFunction(
                $canonical_fqsen,
                new \ReflectionFunction($name)
            ) as $function) {
                $this->addFunction($function);
            }

            return true;
        }
        return false;
    }

    /**
     * @return int
     * The total number of elements of all types in the
     * code base.
     */
    public function totalElementCount() : int
    {
        $sum = (
            \count($this->getFunctionMap())
            + \count($this->getGlobalConstantMap())
            + \count($this->getUserDefinedClassMap())
            + \count($this->fqsen_class_map_internal)  // initialized internal classes
            + \count($this->fqsen_class_map_reflection)  // uninitialized internal classes
        );

        foreach ($this->getClassMapMap() as $class_map) {
            $sum += (
                \count($class_map->getClassConstantMap())
                + \count($class_map->getPropertyMap())
                + \count($class_map->getMethodMap())
            );
        }

        return $sum;
    }

    /**
     * @return void
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function flushDependenciesForFile(string $file_path)
    {
        // TODO: ...
    }

    /**
     * @return string[]
     * The list of files that depend on the code in the given
     * file path
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function dependencyListForFile(string $file_path) : array
    {
        // TODO: ...
        return [];
    }

    /**
     * @return array<int,string> every constant name except user-defined constants.
     */
    public static function getPHPInternalConstantNameList() : array
    {
        // Unit tests call this on every test case. Cache the **internal** constants in a static variable for efficiency; those won't change.
        static $constant_name_list = null;
        if ($constant_name_list === null) {
            // 'true', 'false', and 'null' aren't actually defined constants, they're keywords? Add them so that analysis won't break.
            $constant_name_list = \array_keys(\array_merge(['true' => true, 'false' => false, 'null' => null], ...\array_values(
                \array_diff_key(\get_defined_constants(true), ['user' => []])
            )));
        }
        return $constant_name_list;
    }

    /**
     * @param string $file path to a file
     * @param string $issue_type (e.g. 'PhanUnreferencedUseNormal')
     * @return void
     */
    public function addFileLevelSuppression(string $file, string $issue_type)
    {
        // TODO: Modify the implementation so that it can be checked by UnusedSuppressionPlugin.
        if (!isset($this->file_level_suppression_set[$file][$issue_type])) {
            $this->file_level_suppression_set[$file][$issue_type] = 0;
        }
    }

    /**
     * @param string $file path to a file
     * @param string $issue_type (e.g. 'PhanUnreferencedUseNormal')
     */
    public function hasFileLevelSuppression(string $file, string $issue_type) : bool
    {
        // TODO: Modify the implementation so that it can be checked by UnusedSuppressionPlugin.
        if (isset($this->file_level_suppression_set[$file][$issue_type])) {
            ++$this->file_level_suppression_set[$file][$issue_type];
            return true;
        }
        return false;
    }
}
<?php declare(strict_types=1);
namespace Phan;

use Phan\Config\Initializer;
use Phan\Output\Collector\BufferingCollector;
use Phan\Output\Filter\CategoryIssueFilter;
use Phan\Output\Filter\ChainedIssueFilter;
use Phan\Output\Filter\FileIssueFilter;
use Phan\Output\Filter\MinimumSeverityFilter;
use Phan\Output\PrinterFactory;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;

/**
 * Contains methods for parsing CLI arguments to Phan,
 * outputting to the CLI, as well as helper methods to retrieve files/folders
 * for the analyzed project.
 *
 * @phan-file-suppress PhanPartialTypeMismatchArgumentInternal
 */
class CLI
{
    /**
     * This should be updated to x.y.z-dev after every release, and x.y.z before a release.
     */
    const PHAN_VERSION = '0.12.5';

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * @return OutputInterface
     * @suppress PhanUnreferencedPublicMethod not used yet.
     */
    public function getOutput() : OutputInterface
    {
        return $this->output;
    }

    /**
     * @var array<int,string>
     * The set of file names to analyze
     */
    private $file_list_in_config = [];

    /**
     * @var array<int,string>
     * The set of file names to analyze
     */
    private $file_list = [];

    /**
     * @var bool
     * Set to true to ignore all files and directories
     * added by means other than -file-list-only on the CLI
     */
    private $file_list_only = false;

    /**
     * @var string|null
     * A possibly null path to the config file to load
     */
    private $config_file = null;

    /**
     * Create and read command line arguments, configuring
     * \Phan\Config as a side effect.
     */
    public function __construct()
    {
        global $argv;

        // Parse command line args
        // still available: g,n,t,u,w
        $opts = getopt(
            "f:m:o:c:k:aeqbr:pid:3:y:l:xj:zhvs:",
            [
                'allow-polyfill-parser',
                'backward-compatibility-checks',
                'color',
                'config-file:',
                'daemonize-socket:',
                'daemonize-tcp-port:',
                'dead-code-detection',
                'directory:',
                'disable-plugins',
                'dump-ast',
                'dump-parsed-file-list',
                'dump-signatures-file:',
                'exclude-directory-list:',
                'exclude-file:',
                'extended-help',
                'file-list:',
                'file-list-only:',
                'force-polyfill-parser',
                'help',
                'ignore-undeclared',
                'include-analysis-file-list:',
                'init',
                'init-level:',
                'init-analyze-dir:',
                'init-analyze-file:',
                'init-no-composer',
                'init-overwrite',
                'language-server-analyze-only-on-save',
                'language-server-on-stdin',
                'language-server-tcp-connect:',
                'language-server-tcp-server:',
                'language-server-verbose',
                'language-server-allow-missing-pcntl',
                'language-server-force-missing-pcntl',
                'markdown-issue-messages',
                'memory-limit:',
                'minimum-severity:',
                'output:',
                'output-mode:',
                'parent-constructor-required:',
                'polyfill-parse-all-element-doc-comments',
                'plugin:',
                'print-memory-usage-summary',
                'processes:',
                'progress-bar',
                'project-root-directory:',
                'quick',
                'require-config-exists',
                'signature-compatibility',
                'strict-param-checking',
                'strict-property-checking',
                'strict-return-checking',
                'strict-type-checking',
                'target-php-version',
                'use-fallback-parser',
                'version',
            ]
        );
        $opts = $opts ?? [];

        if (\array_key_exists('extended-help', $opts)) {
            $this->usage('', EXIT_SUCCESS, true);  // --help prints help and calls exit(0)
        }
        if (\array_key_exists('h', $opts) || \array_key_exists('help', $opts)) {
            $this->usage();  // --help prints help and calls exit(0)
        }
        if (\array_key_exists('v', $opts ?? []) || \array_key_exists('version', $opts ?? [])) {
            printf("Phan %s\n", self::PHAN_VERSION);
            exit(EXIT_SUCCESS);
        }

        // Determine the root directory of the project from which
        // we root all relative paths passed in as args
        $overriden_project_root_directory = $opts['d'] ?? $opts['project-root-directory'] ?? null;
        if (\is_string($overriden_project_root_directory)) {
            if (!\is_dir($overriden_project_root_directory)) {
                $this->usage(\json_encode($overriden_project_root_directory) . ' is not a directory', EXIT_FAILURE);
            }
            // Set the current working directory so that relative paths within the project will work.
            // TODO: Add an option to allow searching ancestor directories?
            \chdir($overriden_project_root_directory);
        }
        $cwd = \getcwd();
        if (!is_string($cwd)) {
            echo "Failed to find current working directory\n";
            exit(1);
        }
        Config::setProjectRootDirectory($cwd);

        if (\array_key_exists('init', $opts)) {
            $exit_code = Initializer::initPhanConfig($opts);
            if ($exit_code === 0) {
                exit($exit_code);
            }
            echo "\n";
            // --init is currently in --extended-help
            $this->usage('', $exit_code, true);
        }

        // Before reading the config, check for an override on
        // the location of the config file path.
        if (isset($opts['k'])) {
            $this->config_file = $opts['k'];
        } elseif (isset($opts['config-file'])) {
            $this->config_file = $opts['config-file'];
        }

        if (isset($opts['language-server-force-missing-pcntl'])) {
            Config::setValue('language_server_use_pcntl_fallback', true);
        } elseif (isset($opts['language-server-allow-missing-pcntl'])) {
            if (!extension_loaded('pcntl')) {
                Config::setValue('language_server_use_pcntl_fallback', true);
            }
        }

        // Now that we have a root directory, attempt to read a
        // configuration file `.phan/config.php` if it exists
        $this->maybeReadConfigFile(\array_key_exists('require-config-exists', $opts));

        $this->output = new ConsoleOutput();
        $factory = new PrinterFactory();
        $printer_type = 'text';
        $minimum_severity = Config::getValue('minimum_severity');
        $mask = -1;

        foreach ($opts as $key => $value) {
            switch ($key) {
                case 'r':
                case 'file-list-only':
                    // Mark it so that we don't load files through
                    // other mechanisms.
                    $this->file_list_only = true;

                    // Empty out the file list
                    $this->file_list_in_config = [];

                    // Intentionally fall through to load the
                    // file list
                case 'f':
                case 'file-list':
                    $file_list = \is_array($value) ? $value : [$value];
                    foreach ($file_list as $file_name) {
                        if (!is_string($file_name)) {
                            error_log("invalid argument for --file-list");
                            continue;
                        }
                        $file_path = Config::projectPath($file_name);
                        if (is_file($file_path) && is_readable($file_path)) {
                            /** @var array<int,string> */
                            $this->file_list_in_config = array_merge(
                                $this->file_list_in_config,
                                file(Config::projectPath($file_name), FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)
                            );
                        } else {
                            error_log("Unable to read file $file_path");
                        }
                    }
                    break;
                case 'l':
                case 'directory':
                    if (!$this->file_list_only) {
                        $directory_list = \is_array($value) ? $value : [$value];
                        foreach ($directory_list as $directory_name) {
                            if (!is_string($directory_name)) {
                                error_log("Invalid --directory setting");
                                return;
                            }
                            $this->file_list_in_config = array_merge(
                                $this->file_list,
                                $this->directoryNameToFileList(
                                    $directory_name
                                )
                            );
                        }
                    }
                    break;
                case 'k':
                case 'config-file':
                    break;
                case 'm':
                case 'output-mode':
                    if (!in_array($value, $factory->getTypes(), true)) {
                        $this->usage(
                            sprintf(
                                'Unknown output mode %s. Known values are [%s]',
                                json_encode($value),
                                implode(',', $factory->getTypes())
                            ),
                            EXIT_FAILURE
                        );
                    }

                    $printer_type = $value;
                    break;
                case 'c':
                case 'parent-constructor-required':
                    Config::setValue('parent_constructor_required', explode(',', $value));
                    break;
                case 'q':
                case 'quick':
                    Config::setValue('quick_mode', true);
                    break;
                case 'b':
                case 'backward-compatibility-checks':
                    Config::setValue('backward_compatibility_checks', true);
                    break;
                case 'p':
                case 'progress-bar':
                    Config::setValue('progress_bar', true);
                    break;
                case 'a':
                case 'dump-ast':
                    Config::setValue('dump_ast', true);
                    break;
                case 'dump-parsed-file-list':
                    Config::setValue('dump_parsed_file_list', true);
                    break;
                case 'dump-signatures-file':
                    Config::setValue('dump_signatures_file', $value);
                    break;
                case 'o':
                case 'output':
                    $this->output = new StreamOutput(fopen($value, 'w'));
                    break;
                case 'i':
                case 'ignore-undeclared':
                    $mask ^= Issue::CATEGORY_UNDEFINED;
                    break;
                case '3':
                case 'exclude-directory-list':
                    Config::setValue('exclude_analysis_directory_list', explode(',', $value));
                    break;
                case 'exclude-file':
                    Config::setValue('exclude_file_list', array_merge(
                        Config::getValue('exclude_file_list'),
                        \is_array($value) ? $value : [$value]
                    ));
                    break;
                case 'include-analysis-file-list':
                    Config::setValue('include_analysis_file_list', explode(',', $value));
                    break;
                case 'j':
                case 'processes':
                    Config::setValue('processes', (int)$value);
                    break;
                case 'z':
                case 'signature-compatibility':
                    Config::setValue('analyze_signature_compatibility', (bool)$value);
                    break;
                case 'y':
                case 'minimum-severity':
                    $minimum_severity = (int)$value;
                    break;
                case 'target-php-version':
                    Config::setValue('target_php_version', $value);
                    break;
                case 'polyfill-parse-all-element-doc-comments':
                    Config::setValue('polyfill_parse_all_element_doc_comments', true);
                    break;
                case 'd':
                case 'project-root-directory':
                    // We handle this flag before parsing options so
                    // that we can get the project root directory to
                    // base other config flags values on
                    break;
                case 'require-config-exists':
                    break;  // handled earlier.
                case 'language-server-allow-missing-pcntl':
                case 'language-server-force-missing-pcntl':
                    break;  // handled earlier
                case 'disable-plugins':
                    // Slightly faster, e.g. for daemon mode with lowest latency (along with --quick).
                    Config::setValue('plugins', []);
                    break;
                case 'plugin':
                    if (!is_array($value)) {
                        $value = [$value];
                    }
                    Config::setValue(
                        'plugins',
                        array_unique(array_merge(Config::getValue('plugins'), $value))
                    );
                    break;
                case 'use-fallback-parser':
                    Config::setValue('use_fallback_parser', true);
                    break;
                case 'strict-param-checking':
                    Config::setValue('strict_param_checking', true);
                    break;
                case 'strict-property-checking':
                    Config::setValue('strict_property_checking', true);
                    break;
                case 'strict-return-checking':
                    Config::setValue('strict_return_checking', true);
                    break;
                case 'strict-type-checking':
                    Config::setValue('strict_param_checking', true);
                    Config::setValue('strict_property_checking', true);
                    Config::setValue('strict_return_checking', true);
                    break;
                case 's':
                case 'daemonize-socket':
                    $this->checkCanDaemonize('unix');
                    $socket_dirname = realpath(dirname($value));
                    if (!file_exists($socket_dirname) || !is_dir($socket_dirname)) {
                        $msg = sprintf('Requested to create unix socket server in %s, but folder %s does not exist', json_encode($value), json_encode($socket_dirname));
                        $this->usage($msg, 1);
                    } else {
                        Config::setValue('daemonize_socket', $value);  // Daemonize. Assumes the file list won't change. Accepts requests over a Unix socket, or some other IPC mechanism.
                    }
                    break;
                    // TODO: HTTP server binding to 127.0.0.1, daemonize-port.
                case 'daemonize-tcp-port':
                    $this->checkCanDaemonize('tcp');
                    if (strcasecmp($value, 'default') === 0) {
                        $port = 4846;
                    } else {
                        $port = filter_var($value, FILTER_VALIDATE_INT);
                    }
                    if ($port >= 1024 && $port <= 65535) {
                        Config::setValue('daemonize_tcp_port', $port);
                    } else {
                        $this->usage("daemonize-tcp-port must be the string 'default' or a value between 1024 and 65535, got '$value'", 1);
                    }
                    break;
                case 'language-server-on-stdin':
                    Config::setValue('language_server_config', ['stdin' => true]);
                    break;
                case 'language-server-tcp-server':
                    // TODO: could validate?
                    Config::setValue('language_server_config', ['tcp-server' => $value]);
                    break;
                case 'language-server-tcp-connect':
                    Config::setValue('language_server_config', ['tcp' => $value]);
                    break;
                case 'language-server-analyze-only-on-save':
                    Config::setValue('language_server_analyze_only_on_save', true);
                    break;
                case 'language-server-verbose':
                    Config::setValue('language_server_debug_level', 'info');
                    break;
                case 'x':
                case 'dead-code-detection':
                    Config::setValue('dead_code_detection', true);
                    break;
                case 'allow-polyfill-parser':
                    // Just check if it's installed and of a new enough version.
                    // Assume that if there is an installation, it works, and warn later in ensureASTParserExists()
                    if (!extension_loaded('ast')) {
                        Config::setValue('use_polyfill_parser', true);
                        break;
                    }
                    if (version_compare((new \ReflectionExtension('ast'))->getVersion(), '0.1.5') < 0) {
                        Config::setValue('use_polyfill_parser', true);
                        break;
                    }
                    break;
                case 'force-polyfill-parser':
                    Config::setValue('use_polyfill_parser', true);
                    break;
                case 'memory-limit':
                    if (preg_match('@^([1-9][0-9]*)([KMG])?$@', $value, $match)) {
                        ini_set('memory_limit', $value);
                    } else {
                        fwrite(STDERR, "Invalid --memory-limit '$value', ignoring\n");
                    }
                    break;
                case 'print-memory-usage-summary':
                    Config::setValue('print_memory_usage_summary', true);
                    break;
                case 'markdown-issue-messages':
                    Config::setValue('markdown_issue_messages', true);
                    break;
                case 'color':
                    Config::setValue('color_issue_messages', true);
                    break;
                default:
                    $this->usage("Unknown option '-$key'", EXIT_FAILURE);
                    break;
            }
        }

        $this->ensureASTParserExists();

        $printer = $factory->getPrinter($printer_type, $this->output);
        $filter  = new ChainedIssueFilter([
            new FileIssueFilter(new Phan()),
            new MinimumSeverityFilter($minimum_severity),
            new CategoryIssueFilter($mask)
        ]);
        $collector = new BufferingCollector($filter);

        Phan::setPrinter($printer);
        Phan::setIssueCollector($collector);

        $pruneargv = [];
        foreach ($opts as $opt => $value) {
            foreach ($argv as $key => $chunk) {
                $regex = '/^'. (isset($opt[1]) ? '--' : '-') . $opt . '/';

                if (in_array($chunk, is_array($value) ? $value : [$value])
                    && $argv[$key-1][0] == '-'
                    || preg_match($regex, $chunk)
                ) {
                    $pruneargv[] = $key;
                }
            }
        }

        while (count($pruneargv) > 0) {
            $key = array_pop($pruneargv);
            unset($argv[$key]);
        }

        foreach ($argv as $arg) {
            if ($arg[0]=='-') {
                $this->usage("Unknown option '{$arg}'", EXIT_FAILURE);
            }
        }
        if (!$this->file_list_only) {
            // Merge in any remaining args on the CLI
            $this->file_list_in_config = array_merge(
                $this->file_list_in_config,
                array_slice($argv, 1)
            );
        }

        $this->recomputeFileList();

        // We can't run dead code detection on multiple cores because
        // we need to update reference lists in a globally accessible
        // way during analysis. With our parallelization mechanism, there
        // is no shared state between processes, making it impossible to
        // have a complete set of reference lists.
        \assert(
            Config::getValue('processes') === 1
            || !Config::getValue('dead_code_detection'),
            "We cannot run dead code detection on more than one core."
        );
    }

    /**
     * @return void
     */
    public function recomputeFileList()
    {
        $this->file_list = $this->file_list_in_config;

        if (!$this->file_list_only) {
            // Merge in any files given in the config
            /** @var array<int,string> */
            $this->file_list = array_merge(
                $this->file_list,
                Config::getValue('file_list')
            );

            // Merge in any directories given in the config
            foreach (Config::getValue('directory_list') as $directory_name) {
                $this->file_list = array_merge(
                    $this->file_list,
                    $this->directoryNameToFileList($directory_name)
                );
            }

            // Don't scan anything twice
            $this->file_list = array_unique($this->file_list);
        }

        // Exclude any files that should be excluded from
        // parsing and analysis (not read at all)
        if (count(Config::getValue('exclude_file_list')) > 0) {
            $exclude_file_set = [];
            foreach (Config::getValue('exclude_file_list') as $file) {
                $exclude_file_set[$file] = true;
            }

            $this->file_list = array_filter(
                $this->file_list,
                function (string $file) use ($exclude_file_set) : bool {
                    return empty($exclude_file_set[$file]);
                }
            );
        }
    }

    /** @return void - exits on usage error */
    private function checkCanDaemonize(string $protocol)
    {
        $opt = $protocol === 'unix' ? '--daemonize-socket' : '--daemonize-tcp-port';
        if (!in_array($protocol, stream_get_transports())) {
            $this->usage("The $protocol:///path/to/file schema is not supported on this system, cannot create a daemon with $opt", 1);
        }
        if (!Config::getValue('language_server_use_pcntl_fallback') && !function_exists('pcntl_fork')) {
            $this->usage("The pcntl extension is not available to fork a new process, so $opt will not be able to create workers to respond to requests.", 1);
        }
        if (Config::getValue('daemonize_socket') || Config::getValue('daemonize_tcp_port')) {
            $this->usage('Can specify --daemonize-socket or --daemonize-tcp-port only once', 1);
        }
    }

    /**
     * @return array<int,string>
     * Get the set of files to analyze
     * @suppress PhanPartialTypeMismatchReturn other types get inferred from assignments
     */
    public function getFileList() : array
    {
        return $this->file_list;
    }

    // FIXME: If I stop using defined() in UnionTypeVisitor,
    // this will warn about the undefined constant EXIT_SUCCESS when a
    // user-defined constant is used in parse phase in a function declaration
    private function usage(string $msg = '', int $exit_code = EXIT_SUCCESS, bool $print_extended_help = false)
    {
        global $argv;

        if (!empty($msg)) {
            echo "$msg\n";
        }

        echo <<<EOB
Usage: {$argv[0]} [options] [files...]
 -f, --file-list <filename>
  A file containing a list of PHP files to be analyzed

 -l, --directory <directory>
  A directory that should be parsed for class and
  method information. After excluding the directories
  defined in --exclude-directory-list, the remaining
  files will be statically analyzed for errors.

  Thus, both first-party and third-party code being used by
  your application should be included in this list.

  You may include multiple `--directory DIR` options.

 --exclude-file <file>
  A file that should not be parsed or analyzed (or read
  at all). This is useful for excluding hopelessly
  unanalyzable files.

 -3, --exclude-directory-list <dir_list>
  A comma-separated list of directories that defines files
  that will be excluded from static analysis, but whose
  class and method information should be included.

  Generally, you'll want to include the directories for
  third-party code (such as "vendor/") in this list.

 --include-analysis-file-list <file_list>
  A comma-separated list of files that will be included in
  static analysis. All others won't be analyzed.

  This is primarily intended for performing standalone
  incremental analysis.

 -d, --project-root-directory </path/to/project>
  Hunt for a directory named .phan in the provided directory
  and read configuration file .phan/config.php from that path.

 -r, --file-list-only
  A file containing a list of PHP files to be analyzed to the
  exclusion of any other directories or files passed in. This
  is unlikely to be useful.

 -k, --config-file
  A path to a config file to load (instead of the default of
  .phan/config.php).

 -m <mode>, --output-mode
  Output mode from 'text', 'json', 'csv', 'codeclimate', 'checkstyle', or 'pylint'

 -o, --output <filename>
  Output filename

 --init
   [--init-level=3]
   [--init-analyze-dir=path/to/src]
   [--init-analyze-file=path/to/file.php]
   [--init-no-composer]

  Generates a `.phan/config.php` in the current directory
  based on the project's composer.json.
  The logic used to generate the config file is currently very simple.
  Some third party classes (e.g. in vendor/)
  will need to be manually added to 'directory_list' or excluded,
  and you may end up with a large number of issues to be manually suppressed.
  See https://github.com/phan/phan/wiki/Tutorial-for-Analyzing-a-Large-Sloppy-Code-Base

  [--init-level] affects the generated settings in `.phan/config.php`
    (e.g. null_casts_as_array).
    `--init-level` can be set to 1 (strictest) to 5 (least strict)
  [--init-analyze-dir] can be used as a relative path alongside directories
    that Phan infers from composer.json's "autoload" settings
  [--init-analyze-file] can be used as a relative path alongside files
    that Phan infers from composer.json's "bin" settings
  [--init-no-composer] can be used to tell Phan that the project
    is not a composer project.
    Phan will not check for composer.json or vendor/,
    and will not include those paths in the generated config.
  [--init-overwrite] will allow 'phan --init' to overwrite .phan/config.php.

 --color
  Add colors to the outputted issues. Tested in Unix.
  This is recommended for only the default --output-mode ('text')

 -p, --progress-bar
  Show progress bar

 -q, --quick
  Quick mode - doesn't recurse into all function calls

 -b, --backward-compatibility-checks
  Check for potential PHP 5 -> PHP 7 BC issues

 --target-php-version {7.0,7.1,7.2,native}
  The PHP version that the codebase will be checked for compatibility against.
  For best results, the PHP binary used to run Phan should have the same PHP version.
  (Phan relies on Reflection for some param counts
   and checks for undefined classes/methods/functions)

 -i, --ignore-undeclared
  Ignore undeclared functions and classes

 -y, --minimum-severity <level in {0,5,10}>
  Minimum severity level (low=0, normal=5, critical=10) to report.
  Defaults to 0.

 -c, --parent-constructor-required
  Comma-separated list of classes that require
  parent::__construct() to be called

 -x, --dead-code-detection
  Emit issues for classes, methods, functions, constants and
  properties that are probably never referenced and can
  possibly be removed.

 -j, --processes <int>
  The number of parallel processes to run during the analysis
  phase. Defaults to 1.

 -z, --signature-compatibility
  Analyze signatures for methods that are overrides to ensure
  compatibility with what they're overriding.

 --disable-plugins
  Don't run any plugins. Slightly faster.

 --plugin <pluginName|path/to/Plugin.php>
  Add an additional plugin to run. This flag can be repeated.
  (Either pass the name of the plugin or a relative/absolute path to the plugin)

 --strict-param-checking
  Enables the config option `strict_param_checking`.

 --strict-property-checking
  Enables the config option `strict_property_checking`.

 --strict-return-checking
  Enables the config option `strict_return_checking`.

 --strict-type-checking
  Equivalent to
  `--strict-param-checking --strict-property-checking --strict-return-checking`.

 --use-fallback-parser
  If a file to be analyzed is syntactically invalid
  (i.e. "php --syntax-check path/to/file" would emit a syntax error),
  then retry, using a different, slower error tolerant parser to parse it.
  (And phan will then analyze what could be parsed).
  This flag is experimental and may result in unexpected exceptions or errors.
  This flag does not affect excluded files and directories.

 --allow-polyfill-parser
  If the `php-ast` extension isn't available or is an outdated version,
  then use a slower parser (based on tolerant-php-parser) instead.
  Note that https://github.com/Microsoft/tolerant-php-parser
  has some known bugs which may result in false positive parse errors.

 --force-polyfill-parser
  Use a slower parser (based on tolerant-php-parser) instead of the native parser,
  even if the native parser is available.
  Useful mainly for debugging.

 -s, --daemonize-socket </path/to/file.sock>
  Unix socket for Phan to listen for requests on, in daemon mode.

 --daemonize-tcp-port <default|1024-65535>
  TCP port for Phan to listen for JSON requests on, in daemon mode.
  (e.g. 'default', which is an alias for port 4846.)
  `phan_client` can be used to communicate with the Phan Daemon.

 -v, --version
  Print phan's version number

 -h, --help
  This help information

 --extended-help
  This help information, plus less commonly used flags
  (E.g. for daemon mode)

EOB;
        if ($print_extended_help) {
            echo <<<EOB

Extended help:
 -a, --dump-ast
  Emit an AST for each file rather than analyze.

 --dump-parsed-file-list
  Emit a newline-separated list of files Phan would parse to stdout.
  This is useful to verify that options such as exclude_file_regex are
  properly set up, or to run other checks on the files Phan would parse.

 --dump-signatures-file <filename>
  Emit JSON serialized signatures to the given file.
  This uses a method signature format similar to FunctionSignatureMap.php.

 --memory-limit <memory_limit>
  Sets the memory limit for analysis (per process).
  This is useful when developing or when you want guarantees on memory limits.
  K, M, and G are optional suffixes (Kilobytes, Megabytes, Gigabytes).

 --print-memory-usage-summary
  Prints a summary of memory usage and maximum memory usage.
  This is accurate when there is one analysis process.

 --markdown-issue-messages
  Emit issue messages with markdown formatting.

 --polyfill-parse-all-element-doc-comments
  Makes the polyfill aware of doc comments on class constants and declare statements
  even when imitating parsing a PHP 7.0 codebase.

 --language-server-on-stdin
  Start the language server (For the Language Server protocol).
  This is a different protocol from --daemonize, clients for various IDEs already exist.

 --language-server-tcp-server <addr>
  Start the language server listening for TCP connections on <addr> (e.g. 127.0.0.1:<port>)

 --language-server-tcp-connect <addr>
  Start the language server and connect to the client listening on <addr> (e.g. 127.0.0.1:<port>)

 --language-server-analyze-only-on-save
  Prevent the client from sending change notifications (Only notify the language server when the user saves a document)
  This significantly reduces CPU usage, but clients won't get notifications about issues immediately.

 --language-server-verbose
  Emit verbose logging messages related to the language server implementation to stderr.
  This is useful when developing or debugging language server clients.

 --language-server-allow-missing-pcntl
  Allow the fallback that doesn't use pcntl (New and experimental) to be used if the pcntl extension is not installed.
  This is useful for running the language server on Windows.

 --language-server-force-missing-pcntl
  Force Phan to use the fallback for when pcntl is absent (New and experimental). Useful for debugging that fallback.

 --require-config-exists
  Exit immediately with an error code if .phan/config.php does not exist.

EOB;
        }
        exit($exit_code);
    }

    /**
     * @param string $directory_name
     * The name of a directory to scan for files ending in `.php`.
     *
     * @return array<string,string>
     * A list of PHP files in the given directory
     */
    private function directoryNameToFileList(
        string $directory_name
    ) : array {
        $file_list = [];

        try {
            $file_extensions = Config::getValue('analyzed_file_extensions');

            if (!\is_array($file_extensions) || count($file_extensions) === 0) {
                throw new \InvalidArgumentException(
                    'Empty list in config analyzed_file_extensions. Nothing to analyze.'
                );
            }

            $exclude_file_regex = Config::getValue('exclude_file_regex');
            $iterator = new \CallbackFilterIterator(
                new \RecursiveIteratorIterator(
                    new \RecursiveDirectoryIterator(
                        $directory_name,
                        \RecursiveDirectoryIterator::FOLLOW_SYMLINKS
                    )
                ),
                function (\SplFileInfo $file_info) use ($file_extensions, $exclude_file_regex) {
                    if (!in_array($file_info->getExtension(), $file_extensions, true)) {
                        return false;
                    }

                    if (!$file_info->isFile() || !$file_info->isReadable()) {
                        $file_path = $file_info->getRealPath();
                        error_log("Unable to read file {$file_path}");
                        return false;
                    }

                    // Compare exclude_file_regex against the relative path within the project
                    // (E.g. src/foo.php)
                    if ($exclude_file_regex && self::isPathExcludedByRegex($exclude_file_regex, $file_info->getPathname())) {
                        return false;
                    }

                    return true;
                }
            );

            $file_list = array_keys(iterator_to_array($iterator));
        } catch (\Exception $exception) {
            error_log($exception->getMessage());
        }

        // Normalize leading './' in paths.
        $normalized_file_list = [];
        foreach ($file_list as $file_path) {
            $file_path = preg_replace('@^(\.[/\\\\]+)+@', '', $file_path);
            $normalized_file_list[$file_path] = $file_path;
        }
        usort($normalized_file_list, function (string $a, string $b) : int {
            // Sort lexicographically by paths **within the results for a directory**,
            // to work around some file systems not returning results lexicographically.
            // Keep directories together by replacing directory separators with the null byte
            // (E.g. "a.b" is lexicographically less than "a/b", but "aab" is greater than "a/b")
            return \strcmp(\preg_replace("@[/\\\\]+@", "\0", $a), \preg_replace("@[/\\\\]+@", "\0", $b));
        });

        return $normalized_file_list;
    }

    public static function shouldShowProgress() : bool
    {
        return Config::getValue('progress_bar') &&
            !Config::getValue('dump_ast') &&
            !Config::getValue('daemonize_tcp_port') &&
            !Config::getValue('daemonize_socket');
    }

    /**
     * Check if a path name is excluded by regex, in a platform independent way.
     * Normalizes $path_name on Windows so that '/' is always the directory separator.
     *
     * @param string $exclude_file_regex - PHP regex
     * @param string $path_name - path name within project, beginning with user-provided directory name.
     *                            On windows, may contain '\'.
     *
     * @return bool - True if the user's configured regex is meant to exclude $path_name
     */
    private static function isPathExcludedByRegex(
        string $exclude_file_regex,
        string $path_name
    ) : bool {
        // Make this behave the same way on linux/unix and on Windows.
        if (DIRECTORY_SEPARATOR === '\\') {
            $path_name = str_replace(DIRECTORY_SEPARATOR, '/', $path_name);
        }
        return preg_match($exclude_file_regex, $path_name) > 0;
    }

    /**
     * Update a progress bar on the screen
     *
     * @param string $msg
     * A short message to display with the progress
     * meter
     *
     * @param float $p
     * The percentage to display
     *
     * @return void
     */
    public static function progress(
        string $msg,
        float $p
    ) {
        if (!self::shouldShowProgress()) {
            return;
        }

        // Bound the percentage to [0, 1]
        $p = min(max($p, 0.0), 1.0);

        static $previous_update_time = 0.0;
        $time = microtime(true);


        // Don't update every time when we're moving
        // super fast
        if ($p > 0.0
            && $p < 1.0
            && (
                // If it hasn't been enough time and we're unlucky, then stop early
                ($time - $previous_update_time < Config::getValue('progress_bar_sample_interval'))
              && (rand(0, 1000) > (1000 * Config::getValue('progress_bar_sample_rate')))
            )) {
            return;
        }
        $previous_update_time = $time;

        // If we're on windows, just print a dot to show we're
        // working
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            fwrite(STDERR, '.');
            return;
        }
        $memory = memory_get_usage()/1024/1024;
        $peak = memory_get_peak_usage()/1024/1024;

        $current = (int)($p * 60);
        $rest = max(60 - $current, 0);

        // Build up a string, then make a single call to fwrite(). Should be slightly faster and smoother to render to the console.
        $msg = str_pad($msg, 10, ' ', STR_PAD_LEFT) .
               ' ' .
               str_repeat("\u{2588}", $current) .
               str_repeat("\u{2591}", $rest) .
               " " . sprintf("%1$ 3d", (int)(100*$p)) . "%" .
               sprintf(' %0.2dMB/%0.2dMB', $memory, $peak) . "\r";
        fwrite(STDERR, $msg);
    }

    /**
     * Look for a .phan/config file up to a few directories
     * up the hierarchy and apply anything in there to
     * the configuration.
     */
    private function maybeReadConfigFile(bool $require_config_exists)
    {

        // If the file doesn't exist here, try a directory up
        $config_file_name =
            !empty($this->config_file)
            ? realpath($this->config_file)
            : implode(DIRECTORY_SEPARATOR, [
                Config::getProjectRootDirectory(),
                '.phan',
                'config.php'
            ]);

        // Totally cool if the file isn't there
        if (!file_exists($config_file_name)) {
            if ($require_config_exists) {
                // But if the CLI option --require-config-exists is provided, exit immediately.
                // (Include extended help documenting that option)
                $this->usage("Could not find a config file at '$config_file_name', but --require-config-exists was set", EXIT_FAILURE, true);
            }
            return;
        }

        // Read the configuration file
        $config = require($config_file_name);

        // Write each value to the config
        foreach ($config as $key => $value) {
            Config::setValue($key, $value);
        }
    }

    /**
     * This will assert that ast\parse_code or a polyfill can be called.
     * @return void
     */
    private function ensureASTParserExists()
    {
        if (Config::getValue('use_polyfill_parser')) {
            return;
        }
        assert(
            extension_loaded('ast'),
            'The php-ast extension must be loaded in order for Phan to work. See https://github.com/phan/phan#getting-it-running for more details. Alternately, invoke Phan with the CLI option --allow-polyfill-parser (which is noticeably slower)'
        );

        try {
            // Split up the opening PHP tag to fix highlighting in vim.
            \ast\parse_code(
                '<' . '?php 42;',
                Config::AST_VERSION
            );
        } catch (\LogicException $throwable) {
            assert(
                false,
                'Unknown AST version ('
                . Config::AST_VERSION
                . ') in configuration. '
                . 'You may need to rebuild the latest '
                . 'version of the php-ast extension.'
            );
        }

        // Workaround for https://github.com/nikic/php-ast/issues/79
        try {
            \ast\parse_code(
                '<'.'?php syntaxerror',
                Config::AST_VERSION
            );
            assert(
                false,
                'Expected ast\\parse_code to throw ParseError on invalid inputs. Configured AST version: '
                . Config::AST_VERSION
                . '. '
                . 'You may need to rebuild the latest '
                . 'version of the php-ast extension.'
            );
        } catch (\ParseError $throwable) {
            // error message may validate with locale and version, don't validate that.
        }
    }
}
<?php declare(strict_types=1);

namespace Phan\Config;

/**
 * This class is used by 'phan --init'
 * as a representation of the data to use to create a phan config for a composer project.
 */
class InitializedSettings
{
    /** @var array<string,mixed> the values for setting names*/
    public $settings;

    /** @var array<string,array<int,string>> comments for settings */
    public $comment_lines;

    /** @var int the init-level CLI option used to generate the settings. Smaller numbers mean a stricter config. */
    public $init_level;

    /**
     * @param array<string,mixed> $data
     * @param array<string,array<int,string>> $comment_lines
     */
    public function __construct(
        array $data,
        array $comment_lines,
        int $init_level
    ) {
        $this->settings = $data;
        $this->comment_lines = $comment_lines;
        $this->init_level = $init_level;
    }
}
<?php declare(strict_types=1);

namespace Phan\Config;

use Phan\AST\Parser;
use Phan\CodeBase;
use Phan\Config;
use Phan\Issue;
use Phan\Language\Context;

use ast\Node;
use Composer\Semver\VersionParser;
use Composer\Semver\Constraint\ConstraintInterface;

/**
 * This class is used by 'phan --init' to generate a phan config for a composer project.
 * @phan-file-suppress PhanPartialTypeMismatchArgumentInternal
 */
class Initializer
{
    /**
     * @param array{init-overwrite:mixed=,init-no-composer:mixed=,init-level:string=} $opts
     * Returns a process exit code for `phan --init`
     */
    public static function initPhanConfig(array $opts) : int
    {
        Config::setValue('use_polyfill_parser', true);
        $cwd = getcwd();

        $config_path = "$cwd/.phan/config.php";
        if (!isset($opts['init-overwrite'])) {
            if (file_exists($config_path)) {
                printf("phan --init refuses to run: The Phan config already exists at '$config_path'\n(Can pass --init-overwrite to force Phan to overwrite that file)\n");
                return 1;
            }
        }
        if (isset($opts['init-no-composer'])) {
            $composer_settings = [];
            $vendor_path = null;
        } else {
            $composer_json_path = "$cwd/composer.json";
            if (!file_exists($composer_json_path)) {
                printf("phan --init assumes that there will be a composer.json file (at '$composer_json_path')\n(Can pass --init-no-composer if this is not a composer project)\n");
                return 1;
            }
            $composer_settings = json_decode(file_get_contents($composer_json_path), true);
            if (!is_array($composer_settings)) {
                printf("Failed to load '%s'\n", $composer_json_path);
                return 1;
            }

            $vendor_path = "$cwd/vendor";
            if (!is_dir($vendor_path)) {
                printf("phan --init assumes that 'composer.phar install' was run already (expected to find '$vendor_path')\n");
                return 1;
            }
        }
        $phan_settings = self::createPhanSettingsForComposerSettings($composer_settings, $vendor_path, $opts);
        if (!($phan_settings instanceof InitializedSettings)) {
            echo "phan --init failed to generate settings\n";
            return 1;
        }

        $phan_dir = dirname($config_path);
        if (!file_exists($phan_dir)) {
            if (!mkdir($phan_dir)) {
                echo "Failed to create directory '$phan_dir'\n";
                return 1;
            }
        }
        $settings_file_contents = self::generatePhanConfigFileContents($phan_settings);
        file_put_contents($config_path, $settings_file_contents);
        echo "Successfully initialized '$config_path' with the following contents\n\n";
        echo $settings_file_contents;
        return 0;
    }

    /**
     * @return array<string,string[]> maps a config name to a list of comment lines about that config
     * @suppress PhanPluginUnusedVariable used in loop
     */
    private static function computeCommentNameDocumentationMap() : array
    {
        // Hackish way of extracting comment lines from Config::DEFAULT_CONFIGURATION
        $config_file_lines = explode("\n", file_get_contents(dirname(__DIR__) . '/Config.php'));
        $prev_lines = [];
        $result = [];
        foreach ($config_file_lines as $line) {
            if (preg_match("/^        (['\"])([a-z0-9A-Z_]+)\\1\s*=>/", $line, $matches)) {
                $config_name = $matches[2];
                if (count($prev_lines) > 0) {
                    $result[$config_name] = $prev_lines;
                }
                $prev_lines = [];
                continue;
            }
            if (preg_match('@^\s*//@', $line)) {
                $prev_lines[] = trim($line);
            } else {
                $prev_lines = [];
            }
        }
        return $result;
    }

    public static function generateCommentForSetting(string $setting_name) : string
    {
        static $comment_source = null;
        if (is_null($comment_source)) {
            $comment_source = self::computeCommentNameDocumentationMap();
        }
        $lines = $comment_source[$setting_name] ?? null;
        if (empty($lines)) {
            return '';
        }
        return implode('', array_map(function (string $line) : string {
            return "    $line\n";
        }, $lines));
    }

    /**
     * @param array<int,string> $additional_comment_lines
     */
    public static function generateEntrySnippetForSetting(string $setting_name, $setting_value, array $additional_comment_lines) : string
    {
        $source = self::generateCommentForSetting($setting_name);
        foreach ($additional_comment_lines as $line) {
            $source .= "    // $line\n";
        }
        $source .= '    ';
        $source .= var_export($setting_name, true) . ' => ';
        if (is_array($setting_value)) {
            if (count($setting_value) > 0) {
                $source .= "[\n";
                foreach ($setting_value as $key => $element) {
                    assert(is_int($key));
                    $source .= '        ' . var_export($element, true) . ",\n";
                }
                $source .= "    ],\n";
            } else {
                $source .= "[],\n";
            }
        } else {
            $encoded_value = var_export($setting_value, true);
            if ($setting_name === 'minimum_severity') {
                switch ($setting_value) {
                    case Issue::SEVERITY_LOW:
                        $encoded_value = 'Issue::SEVERITY_LOW';
                        break;
                    case Issue::SEVERITY_NORMAL:
                        $encoded_value = 'Issue::SEVERITY_NORMAL';
                        break;
                    case Issue::SEVERITY_CRITICAL:
                        $encoded_value = 'Issue::SEVERITY_CRITICAL';
                        break;
                }
            }

            $source .= "$encoded_value,\n";
        }
        return $source;
    }

    public static function generatePhanConfigFileContents(InitializedSettings $settings_object) : string
    {
        $phan_settings = $settings_object->settings;
        $init_level = $settings_object->init_level;
        $comment_lines = $settings_object->comment_lines;

        $source = <<<EOT
<?php

use Phan\Issue;

/**
 * This configuration file was automatically generated by 'phan --init --init-level=$init_level'
 *
 * TODOs (added by 'phan --init'):
 *
 * - Go through this file and verify that there are no missing/unnecessary files/directories.
 *   (E.g. this only includes direct composer dependencies - You may have to manually add indirect composer dependencies to 'directory_list')
 * - Look at 'plugins' and add or remove plugins if appropriate (see https://github.com/phan/phan/tree/master/.phan/plugins#plugins)
 * - Add global suppressions for pre-existing issues to suppress_issue_types (https://github.com/phan/phan/wiki/Tutorial-for-Analyzing-a-Large-Sloppy-Code-Base)
 *
 * This configuration will be read and overlayed on top of the
 * default configuration. Command line arguments will be applied
 * after this file is read.
 *
 * @see src/Phan/Config.php
 * See Config for all configurable options.
 *
 * A Note About Paths
 * ==================
 *
 * Files referenced from this file should be defined as
 *
 * ```
 *   Config::projectPath('relative_path/to/file')
 * ```
 *
 * where the relative path is relative to the root of the
 * project which is defined as either the working directory
 * of the phan executable or a path passed in via the CLI
 * '-d' flag.
 */
return [

EOT;
        foreach ($phan_settings as $setting_name => $setting_value) {
            $source .= "\n";
            $source .= self::generateEntrySnippetForSetting($setting_name, $setting_value, $comment_lines[$setting_name] ?? []);
        }
        $source .= "];\n";
        return $source;
    }

    const LEVEL_MAP = [
        'strict'  => 1,
        'strong'  => 2,
        'average' => 3,
        'normal'  => 3,
        'weak'    => 4,
        'weakest' => 3,
    ];

    /**
     * @param array<string,mixed> $composer_settings (can be empty for --init-no-composer)
     * @param ?string $vendor_path (can be null for --init-no-composer)
     * @param array{init-overwrite:mixed=,init-no-composer:mixed=,init-level:string=} $opts parsed from getopt
     * @return ?InitializedSettings
     */
    private static function createPhanSettingsForComposerSettings(array $composer_settings, $vendor_path, array $opts)
    {
        $level = $opts['init-level'] ?? 3;
        $level = self::LEVEL_MAP[strtolower((string)$level)] ?? $level;
        if (filter_var($level, FILTER_VALIDATE_INT) === false) {
            echo "Invalid --init-level=$level\n";
            return null;
        }
        $level = max(1, min(5, (int)$level));
        $is_strong_or_weaker_level = $level >= 2;
        $is_average_level = $level >= 3;
        $is_weak_level    = $level >= 4;
        $is_weakest_level = $level >= 5;

        $cwd = getcwd();
        list($project_directory_list, $project_file_list) = self::extractAutoloadFilesAndDirectories('', $composer_settings);
        if ($vendor_path !== null && count($project_directory_list) === 0 && count($project_file_list) === 0) {
            echo "phan --init expects composer.json to contain 'autoload' psr-4 directories\n";
            return null;
        }
        $minimum_severity = $is_weak_level ? Issue::SEVERITY_NORMAL : Issue::SEVERITY_LOW;
        if ($is_weakest_level) {
            $plugins = [];
        } elseif ($is_average_level) {
            $plugins = [
                'AlwaysReturnPlugin',
                'PregRegexCheckerPlugin',
                'UnreachableCodePlugin',
            ];
        } else {
            $plugins = [
                'AlwaysReturnPlugin',
                'DollarDollarPlugin',
                'DuplicateArrayKeyPlugin',
                'PregRegexCheckerPlugin',
                'PrintfCheckerPlugin',
                'UnreachableCodePlugin',
            ];
        }

        $comments = [];
        list($target_php_version, $comments['target_php_version']) = self::determineTargetPHPVersion($composer_settings);

        $phan_settings = [
            'target_php_version'       => $target_php_version,
            'allow_missing_properties' => $is_weak_level,
            'null_casts_as_any_type'   => $is_weak_level,
            'null_casts_as_array'      => $is_average_level,
            'array_casts_as_null'      => $is_average_level,
            'scalar_implicit_cast'     => $is_weak_level,
            'scalar_array_key_cast'    => $is_average_level,
            // TODO: Migrate to a smaller subset scalar_implicit_partial as analysis gets stricter?
            'scalar_implicit_partial'  => [],
            'ignore_undeclared_variables_in_global_scope' => $is_average_level,
            'ignore_undeclared_functions_with_known_signatures' => $is_strong_or_weaker_level,
            'backward_compatibility_checks' => false,  // this is slow
            'check_docblock_signature_return_type_match' => !$is_average_level,
            'prefer_narrowed_phpdoc_param_type' => true,
            'prefer_narrowed_phpdoc_return_type' => true,
            'analyze_signature_compatibility' => !$is_weak_level,
            'phpdoc_type_mapping' => [],
            'dead_code_detection' => false,  // this is slow
            'quick_mode' => $is_weakest_level,
            'simplify_ast' => true,
            'generic_types_enabled' => true,
            'globals_type_map' => [],
            'minimum_severity' => $minimum_severity,
            'suppress_issue_types' => [],
            'exclude_file_regex' => $vendor_path !== null ? '@^vendor/.*/(tests?|Tests?)/@' : null,
            'exclude_file_list' => [],
            'exclude_analysis_directory_list' => $vendor_path !== null ? [
                'vendor/'
            ] : [],
            'processes' => 1,
            'analyzed_file_extensions' => ['php'],
            'autoload_internal_extension_signatures' => [],
            'plugins' => $plugins,
        ];

        $phan_directory_list = $project_directory_list;
        $phan_file_list = $project_file_list;

        // TODO: Figure out which require-dev directories can be skipped
        $require_directories = $composer_settings['require'] ?? [];
        $require_dev_directories = $composer_settings['require-dev'] ?? [];
        foreach (array_merge($require_directories, $require_dev_directories) as $requirement => $_) {
            if (substr_count($requirement, '/') !== 1) {
                // e.g. ext-ast, php >= 7.0, etc.
                continue;
            }
            $path_to_require = "$vendor_path/$requirement";
            if (!is_dir($path_to_require)) {
                $requirement = strtolower($requirement);
                $path_to_require = "$vendor_path/$requirement";
                if (!is_dir($path_to_require)) {
                    echo "Warning: $path_to_require does not exist, continuing\n";
                    continue;
                }
            }
            $path_to_composer_json = "$path_to_require/composer.json";
            if (!file_exists($path_to_composer_json)) {
                echo "Warning: $path_to_composer_json does not exist, continuing\n";
                continue;
            }
            $library_composer_settings = json_decode(file_get_contents($path_to_composer_json), true);
            if (!is_array($library_composer_settings)) {
                echo "Warning: $path_to_composer_json is invalid JSON, continuing\n";
                continue;
            }

            list($library_directory_list, $library_file_list) = self::extractAutoloadFilesAndDirectories("vendor/$requirement", $library_composer_settings);
            $phan_directory_list = array_merge($phan_directory_list, $library_directory_list);
            $phan_file_list = array_merge($phan_file_list, $library_file_list);
        }
        foreach (self::getArrayOption($opts, 'init-analyze-dir') as $extra_dir) {
            $path_to_require = "$cwd/$extra_dir";
            if (!is_dir($path_to_require)) {
                echo "phan --init-analyze-dir was given a missing/invalid relative directory '$extra_dir'\n";
                return null;
            }
            $phan_directory_list[] = $extra_dir;
        }

        foreach ($composer_settings['bin'] ?? [] as $relative_path_to_binary) {
            if (self::isPHPBinary($relative_path_to_binary)) {
                $phan_file_list[] = $relative_path_to_binary;
            }
        }
        foreach (self::getArrayOption($opts, 'init-analyze-file') as $extra_file) {
            $path_to_require = "$cwd/$extra_file";
            if (!is_file($path_to_require)) {
                echo "phan --init-analyze-file was given a missing/invalid relative file '$extra_file'\n";
                return null;
            }
            $phan_file_list[] = $extra_file;
        }

        if (count($phan_file_list) === 0 && count($phan_directory_list) === 0) {
            echo "phan --init failed to find any directories or files to analyze, giving up.\n";
            return null;
        }
        sort($phan_directory_list);
        sort($phan_file_list);

        $phan_settings['directory_list'] = array_unique($phan_directory_list);
        $phan_settings['file_list'] = array_unique($phan_file_list);
        return new InitializedSettings($phan_settings, $comments, $level);
    }

    /**
     * @return array{0:?string,1:array<int,string>}
     */
    public static function determineTargetPHPVersion(array $composer_settings) : array
    {
        $php_version_constraint = $composer_settings['require']['php'] ?? null;
        if (!$php_version_constraint || !is_string($php_version_constraint)) {
            return [null, ['TODO: Choose a target_php_version for this project, or leave as null and remove this comment']];
        }
        try {
            $version_constraint = self::parseConstraintsForRange($php_version_constraint);
        } catch (\UnexpectedValueException $e) {
            return [null, ['TODO: Choose a target_php_version for this project, or leave as null and remove this comment']];
        }
        if ($version_constraint->matches(self::parseConstraintsForRange('<7.1-dev'))) {
            $version_guess = '7.0';
        } elseif ($version_constraint->matches(self::parseConstraintsForRange('<7.2-dev'))) {
            $version_guess = '7.1';
        } elseif ($version_constraint->matches(self::parseConstraintsForRange('>= 7.2-dev'))) {
            $version_guess = '7.2';
        } else {
            return [null, ['TODO: Choose a target_php_version for this project, or leave as null and remove this comment']];
        }
        return [$version_guess, ['Automatically inferred from composer.json requirement for "php" of ' . json_encode($php_version_constraint)]];
    }

    private static function parseConstraintsForRange(string $constraints) : ConstraintInterface
    {
        return (new VersionParser())->parseConstraints($constraints);
    }

    /**
     * @return array<int,array<int,string>> [$directory_list, $file_list]
     */
    private static function extractAutoloadFilesAndDirectories(string $relative_dir, array $composer_settings)
    {
        $directory_list = [];
        $file_list = [];
        $autoload_setting = $composer_settings['autoload'] ?? [];
        $autoload_directories = array_merge(
            $autoload_setting['psr-4'] ?? [],
            $autoload_setting['psr-0'] ?? [],
            $autoload_setting['classmap'] ?? []
        );

        foreach ($autoload_directories as $lib_list) {
            if (is_string($lib_list)) {
                $lib_list = [$lib_list];
            }
            foreach ($lib_list as $lib) {
                if (!is_string($lib)) {
                    echo "Warning: unexpected autoload field in '$relative_dir/composer.json'\n";
                    continue;
                }
                $composer_lib_relative_path = "$relative_dir/$lib";
                $composer_lib_absolute_path = getcwd() . "/$composer_lib_relative_path";
                if (!file_exists($composer_lib_absolute_path)) {
                    echo "Warning: could not find '$composer_lib_relative_path'\n";
                    continue;
                }

                if (is_dir($composer_lib_absolute_path)) {
                    $directory_list[] = trim($composer_lib_relative_path, '/');
                } elseif (is_file($composer_lib_relative_path)) {
                    $file_list[] = trim($composer_lib_relative_path, '/');
                }
            }
        }
        return [array_unique($directory_list), array_unique($file_list)];
    }

    private static function getArrayOption(array $opts, string $key)
    {
        $values = $opts[$key] ?? [];
        if (is_string($values)) {
            return [$values];
        }
        return is_array($values) ? $values : [];
    }

    public static function isPHPBinary(string $relative_path) : bool
    {
        $cwd = getcwd();
        $absolute_path = "$cwd/$relative_path";
        if (!file_exists($absolute_path)) {
            printf("Failed to find '%s', continuing\n", $absolute_path);
            return false;
        }
        $contents = file_get_contents($absolute_path);
        try {
            // PHP binaries can have many forms, may begin with #/usr/bin/env php.
            // We assume that if it's parseable and contains at least one PHP executable line, it's valid.
            $ast = Parser::parseCode(new CodeBase([], [], [], [], []), new Context(), $relative_path, $contents, true);
            if (!($ast instanceof Node)) {
                return false;
            }
            $child_nodes = $ast->children;
            if (count($child_nodes) !== 1) {
                return true;
            }
            $node = $child_nodes[0];
            return $node->kind !== \ast\AST_ECHO || !is_string($node->children['expr']);
        } catch (\ParseError $e) {
            return false;
        } catch (\Phan\AST\TolerantASTConverter\ParseException $e) {
            return false;
        }
    }
}
<?php declare(strict_types=1);
namespace Phan;

use Phan\PluginV2\IssueEmitter;

/**
 * Plugins can be defined in the config and will have
 * their hooks called at appropriate times during analysis
 * of each file, class, method and function.
 *
 * Plugins must extend this class
 * (And at least one of the interfaces corresponding to plugin capabilities)
 * and return an instance of themselves.
 *
 * List of capabilities which a plugin may implement:
 *
 *  1. public function analyzeClass(CodeBase $code_base, Clazz $class)
 *     Analyze (and modify) a class definition, after parsing and before analyzing.
 *     (implement \Phan\PluginV2\AnalyzeClassCapability)
 *
 *  2. public function analyzeFunction(CodeBase $code_base, Func $function)
 *     Analyze (and modify) a function definition, after parsing and before analyzing.
 *     (implement \Phan\PluginV2\AnalyzeFunctionCapability)
 *
 *  3. public function analyzeMethod(CodeBase $code_base, Method $method)
 *     Analyze (and modify) a method definition, after parsing and before analyzing.
 *     (implement \Phan\PluginV2\AnalyzeMethodCapability)
 *
 *  4. public static function getPostAnalyzeNodeVisitorClassName() : string
 *     Returns the name of a class extending PluginAwarePostAnalysisVisitor, which will be used to analyze nodes in the analysis phase.
 *     If the PluginAwarePostAnalysisVisitor subclass has an instance property called parent_node_list,
 *     Phan will automatically set that property to the list of parent nodes (The nodes deepest in the AST are at the end of the list)
 *     (implement \Phan\PluginV2\PostAnalyzeNodeCapability)
 *
 *  5. public static function getPreAnalyzeNodeVisitorClassName() : string
 *     Returns the name of a class extending PluginAwarePreAnalysisVisitor, which will be used to pre-analyze nodes in the analysis phase.
 *     (implement \Phan\PluginV2\PreAnalyzeNodeCapability)
 *
 *  6. public function analyzeProperty(CodeBase $code_base, Property $property)
 *     Analyze (and modify) a property definition, after parsing and before analyzing.
 *     (implement \Phan\PluginV2\AnalyzePropertyCapability)
 *
 *  7. public function finalize(CodeBase $code_base)
 *     Called after the analysis phase is complete.
 *     (implement \Phan\PluginV2\FinalizeProcessCapability)
 *
 *  8. public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array<string, Closure(CodeBase,Context,Func|Method,array):void>
 *     Maps FQSEN of function or method to a closure used to analyze the function in question.
 *     'MyClass::myMethod' can be used as the FQSEN of a static or instance method.
 *     See .phan/plugins/PregRegexCheckerPlugin.php as an example.
 *
 *      Closure Type: function(CodeBase $code_base, Context $context, Func|Method $function, array $args) : void {...}
 *  9. public function getReturnTypeOverrides(CodeBase $code_base) : array<string,Closure(CodeBase,Context,Func|Method,array):UnionType>
 *     Maps FQSEN of function or method to a closure used to override the returned UnionType.
 *     See \Phan\Plugin\Internal\ArrayReturnTypeOverridePlugin as an example (That is automatically loaded by phan)
 *
 *     Closure type: function(CodeBase $code_base, Context $context, Func|Method $function, array $args) : UnionType {...}
 *
 * List of deprecated legacy capabilities
 *
 *  1. public static function analyzeNode(CodeBase $code_base, Context $context, Node $node, Node $parent_node = null)
 *     Analyzes $node
 *     (implement \Phan\PluginV2\LegacyAnalyzeNodeCapability)
 *     (Deprecated in favor of postAnalyzeNode and \Phan\PluginV2\AnalyzeNodeCapability, which are faster)
 *
 *  2. public static function preAnalyzeNode(CodeBase $code_base, Context $context, Node $node)
 *     Pre-analyzes $node
 *     (implement \Phan\PluginV2\LegacyPreAnalyzeNodeCapability)
 *     (Deprecated in favor of \Phan\PluginV2\PreAnalyzeNodeCapability, which is faster)
 *
 *  3. public static function postAnalyzeNode(CodeBase $code_base, Context $context, Node $node, array<int,Node> $parent_node_list = [])
 *     Analyzes $node
 *     (implement \Phan\PluginV2\LegacyPostAnalyzeNodeCapability)
 *     (Deprecated in favor of \Phan\PluginV2\PostAnalyzeNodeCapability, which is much faster)
 *
 *  4. public static function getAnalyzeNodeVisitorClassName() : string
 *     Returns the name of a class extending PluginAwareAnalysisVisitor, which will be used to analyze nodes in the analysis phase.
 *     Phan will automatically add the instance property parent_node to instances of that PluginAwareAnalysisVisitor,
 *     even if no such instance property was declared.
 *     (implement \Phan\PluginV2\AnalyzeNodeCapability)
 *     (Deprecated in favor of \Phan\PluginV2\PostAnalyzeNodeCapability, which is much faster)
 *
 * TODO: Implement a way to notify plugins that a parsed file is no longer valid,
 * if the replacement for pcntl is being used.
 * (Most of the plugins bundled with Phan don't need this)
 */
abstract class PluginV2
{
    /**
     * public function emitIssue(
     *     CodeBase $code_base,
     *     Context $context,
     *     string $issue_type,
     *     string $issue_message_fmt,
     *     array $issue_message_args = [],
     *     int $severity = Issue::SEVERITY_NORMAL,
     *     int $remediation_difficulty = Issue::REMEDIATION_B,
     *     int $issue_type_id = Issue::TYPE_ID_UNKNOWN
     * )
     */
    use IssueEmitter {
        emitPluginIssue as emitIssue;
    }
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer;

use AdvancedJsonRpc;
use Sabre\Event\Promise;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/ClientHandler.php
 * See ../../../LICENSE.LANGUAGE_SERVER
 */
class ClientHandler
{
    /**
     * @var ProtocolReader
     */
    public $protocolReader;

    /**
     * @var ProtocolWriter
     */
    public $protocolWriter;

    /**
     * @var IdGenerator
     */
    public $idGenerator;

    public function __construct(ProtocolReader $protocolReader, ProtocolWriter $protocolWriter)
    {
        $this->protocolReader = $protocolReader;
        $this->protocolWriter = $protocolWriter;
        $this->idGenerator = new IdGenerator;
    }

    /**
     * Sends a request to the client and returns a promise that is resolved with the result or rejected with the error
     *
     * @param string $method The method to call
     * @param array|object $params The method parameters
     * @return Promise <mixed> Resolved with the result of the request or rejected with an error
     *
     * @suppress PhanUnreferencedPublicMethod may be used as LSP support improves?
     */
    public function request(string $method, $params): Promise
    {
        $id = $this->idGenerator->generate();
        return $this->protocolWriter->write(
            new Protocol\Message(
                new AdvancedJsonRpc\Request($id, $method, (object)$params)
            )
        )->then(function () use ($id) {
            $promise = new Promise;
            /**
             * @suppress PhanUndeclaredProperty taken care of by isResponse checks on msg->body
             */
            $listener = function (Protocol\Message $msg) use ($id, $promise, &$listener) {
                if (AdvancedJsonRpc\Response::isResponse($msg->body) && $msg->body->id === $id) {
                    // Received a response
                    $this->protocolReader->removeListener('message', $listener);
                    if (AdvancedJsonRpc\SuccessResponse::isSuccessResponse($msg->body)) {
                        $promise->fulfill($msg->body->result);
                    } else {
                        $promise->reject($msg->body->error);
                    }
                }
            };
            $this->protocolReader->on('message', $listener);
            return $promise;
        });
    }

    /**
     * Sends a notification to the client
     *
     * @param string $method The method to call
     * @param array|object $params The method parameters
     * @return Promise <null> Will be resolved as soon as the notification has been sent
     */
    public function notify(string $method, $params): Promise
    {
        return $this->protocolWriter->write(
            new Protocol\Message(
                new AdvancedJsonRpc\Notification($method, (object)$params)
            )
        );
    }
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer;

use Phan\LanguageServer\Protocol\Message;
use Sabre\Event\Promise;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/ProtocolWriter.php
 */
interface ProtocolWriter
{
    /**
     * Sends a Message to the client
     *
     * @param Message $msg
     * @return Promise Resolved when the message has been fully written out to the output stream
     */
    public function write(Message $msg): Promise;
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer;

use Phan\LanguageServer\Protocol\Message;
use Phan\LanguageServer\Logger;
use Sabre\Event\Loop;
use Sabre\Event\Promise;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/ProtocolStreamWriter.php
 */
class ProtocolStreamWriter implements ProtocolWriter
{
    /**
     * @var resource $output
     */
    private $output;

    /**
     * @var array<int,array{message:string,promise:Promise}> $messages
     */
    private $messages = [];

    /**
     * @param resource $output
     */
    public function __construct($output)
    {
        $this->output = $output;
    }

    /**
     * {@inheritdoc}
     */
    public function write(Message $msg): Promise
    {
        // if the message queue is currently empty, register a write handler.
        if (empty($this->messages)) {
            Loop\addWriteStream($this->output, function () {
                $this->flush();
            });
        }

        Logger::logResponse($msg->headers, (string)$msg->body);

        $promise = new Promise();
        $this->messages[] = [
            'message' => (string)$msg,
            'promise' => $promise
        ];
        return $promise;
    }

    /**
     * Writes pending messages to the output stream.
     *
     * @return void
     */
    private function flush()
    {
        $keepWriting = true;
        while ($keepWriting) {
            $message = $this->messages[0]['message'];
            $promise = $this->messages[0]['promise'];

            $bytesWritten = @fwrite($this->output, $message);

            if ($bytesWritten > 0) {
                $message = substr($message, $bytesWritten);
            }

            // Determine if this message was completely sent
            if (strlen($message) === 0) {
                array_shift($this->messages);

                // This was the last message in the queue, remove the write handler.
                if (count($this->messages) === 0) {
                    Loop\removeWriteStream($this->output);
                    $keepWriting = false;
                }

                $promise->fulfill();
            } else {
                $this->messages[0]['message'] = $message;
                $keepWriting = false;
            }
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\LanguageServer;

use AdvancedJsonRpc;
use Closure;
use Phan\Phan;
use Phan\CodeBase;
use Phan\Config;
use Phan\Daemon\ExitException;
use Phan\Daemon\Request;
use Phan\Daemon\Transport\CapturerResponder;
use Phan\Daemon\Transport\StreamResponder;
use Phan\Issue;
use Phan\Language\FileRef;
use Phan\LanguageServer\Protocol\ClientCapabilities;
use Phan\LanguageServer\Protocol\Diagnostic;
use Phan\LanguageServer\Protocol\DiagnosticSeverity;
use Phan\LanguageServer\Protocol\InitializeResult;
use Phan\LanguageServer\Protocol\Message;
use Phan\LanguageServer\Protocol\Position;
use Phan\LanguageServer\Protocol\Range;
use Phan\LanguageServer\Protocol\SaveOptions;
use Phan\LanguageServer\Protocol\ServerCapabilities;
use Phan\LanguageServer\Protocol\TextDocumentSyncKind;
use Phan\LanguageServer\Protocol\TextDocumentSyncOptions;
use Phan\LanguageServer\Server\TextDocument;
use Phan\LanguageServer\ProtocolReader;
use Phan\LanguageServer\ProtocolWriter;
use Phan\LanguageServer\ProtocolStreamReader;
use Phan\LanguageServer\ProtocolStreamWriter;
use Sabre\Event\Loop;
use Sabre\Event\Promise;

use function Sabre\Event\coroutine;
use Throwable;

/**
 * Based on https://github.com/felixfbecker/php-language-server/blob/master/bin/php-language-server.php
 * and https://github.com/felixfbecker/php-language-server/blob/master/src/LanguageServer.php (for language server protocol implementation)
 *
 * This is similar to Phan daemon mode, but it's possible for it to receive concurrent events, or more than one type of event.
 * (in addition to file notifications, etc.)
 *
 * What will do in the most common case (checking for errors in a file):
 *
 * 0. Phan completes the parse phase, on the initial version of the codebase
 * 1. Phan starts a Sabre event loop and listens for requests and responses: see http://sabre.io/event/loop/
 *    It registers addReadStream and addWriteStream (See ProtocolStreamReader and ProtocolStreamWriter)
 * 2. Phan receives a notification that a file changed/was added/was removed
 * 3. If the files are within the phan project (contains .phan directory),
 *    then phan removes the old parse state of removed/changed files, then adds the new parse state of changed/added files.
 * 4. The main phan process (managing the event loop) fork, the fork shuts down the event loop without receiving any more events or touching the stream(will this work?)
 * 5. The forked process runs the analysis.
 * 6. The forked process reports the analysis result to the main process via IPC (similar to what \Phan\ForkPool does), along with the id of the request
 * 7. Phan notifies the client of the new issues ("diagnostic" in the open Language Server Protocol)
 *    See https://github.com/Microsoft/language-server-protocol#language-server-protocol
 *
 * TODO: support textDocument rename
 */
class LanguageServer extends AdvancedJsonRpc\Dispatcher
{
    /**
     * Handles workspace/* method calls
     *
     * @var Server\Workspace
     * @suppress PhanWriteOnlyPublicProperty used by AdvancedJsonRpc\Dispatcher via reflection
     */
    public $workspace;

    /**
     * @var ProtocolReader
     */
    protected $protocolReader;

    /**
     * @var ProtocolWriter
     */
    protected $protocolWriter;

    /**
     * @var LanguageClient
     */
    protected $client;

    /**
     * Handles textDocument/* method calls
     * (e.g. whenever a text document is opened, saved, or closed)
     *
     * @var TextDocument
     */
    public $textDocument;

    /**
     * @var Request|null
     */
    protected $most_recent_request;

    /**
     * @var CodeBase
     */
    protected $code_base;

    /**
     * @var Closure
     */
    protected $file_path_lister;

    /**
     * @var FileMapping
     */
    protected $file_mapping;

    /**
     * @var bool
     */
    private $is_accepting_new_requests = true;

    /**
     * @var array<string,string> maps Paths to URIs, for URIs which have pending analysis requests.
     * Requests are buffered because the language server may otherwise send requests faster than Phan can respond to them.
     * (`$reader->on('readMessageGroup')` notifies Phan that a group of 1 or more messages was read)
     */
    protected $analyze_request_set = [];

    public function __construct(ProtocolReader $reader, ProtocolWriter $writer, CodeBase $code_base, Closure $file_path_lister)
    {
        parent::__construct($this, '/');
        $this->protocolReader = $reader;
        $this->file_mapping = new FileMapping();
        $reader->on('close', function () {
            if (!$this->is_accepting_new_requests) {
                // This is the forked process, which forced the ProtocolReader to close. Don't exit().
                // Instead, carry on and analyze the input files.
                return;
            }
            $this->shutdown();
            $this->exit();
        });
        /** @suppress PhanUndeclaredClassMethod https://github.com/fruux/sabre-event/pull/52 */
        $reader->on('message', function (Message $msg) {
            /** @suppress PhanUndeclaredProperty Request->body->id is a request with an id */
            coroutine(function () use ($msg) {
                // Ignore responses, this is the handler for requests and notifications
                if (AdvancedJsonRpc\Response::isResponse($msg->body)) {
                    return;
                }
                Logger::logInfo('Received message in coroutine: ' . (string)$msg->body);
                $result = null;
                $error = null;
                try {
                    // Invoke the method handler to get a result
                    $result = yield $this->dispatch($msg->body);
                } catch (AdvancedJsonRpc\Error $e) {
                    Logger::logInfo('Saw error: ' . $e->getMessage());
                    // If a ResponseError is thrown, send it back in the Response
                    $error = $e;
                } catch (Throwable $e) {
                    Logger::logInfo('Saw throwable: ' . get_class($e) . ': ' . $e->getMessage() . "\n" . $e->getTraceAsString());
                    // If an unexpected error occurred, send back an INTERNAL_ERROR error response
                    $error = new AdvancedJsonRpc\Error(
                        (string)$e,
                        AdvancedJsonRpc\ErrorCode::INTERNAL_ERROR,
                        null,
                        $e
                    );
                }
                // Only send a Response for a Request
                // Notifications do not send Responses
                if (AdvancedJsonRpc\Request::isRequest($msg->body)) {
                    if ($error !== null) {
                        $responseBody = new AdvancedJsonRpc\ErrorResponse($msg->body->id, $error);
                    } else {
                        $responseBody = new AdvancedJsonRpc\SuccessResponse($msg->body->id, $result);
                    }
                    $this->protocolWriter->write(new Message($responseBody));
                }
            })->otherwise('\\Phan\\LanguageServer\\Utils::crash');
        });

        $reader->on('readMessageGroup', function () {
            $this->finalizeAnalyzingURIs();
        });

        $this->protocolWriter = $writer;
        // We create a client to send diagnostics, etc. to the IDE
        $this->client = new LanguageClient($reader, $writer);
        // We create a workspace to receive change notifications.
        $this->workspace = new Server\Workspace($this->client, $this, $this->file_mapping);

        // Phan specific code
        $this->code_base = $code_base;
        $this->file_path_lister = $file_path_lister;
    }

    /**
     * This creates an analyzing daemon, to be used by IDEs.
     * Format:
     *
     * - Read over TCP socket with JSONRPC 2
     * - Respond over TCP socket with JSONRPC 2
     *
     * @param CodeBase $code_base (Must have undo tracker enabled)
     *
     * @param \Closure $file_path_lister
     * Returns string[] - A list of files to scan. This may be different from the previous contents.
     *
     * @param array<string,mixed> $options (leave empty for stdout)
     *
     * @return Request|null - A writeable request, which has been fully read from.
     * Callers should close after they are finished writing.
     *
     * @suppress PhanUndeclaredConstant (pcntl unavailable on Windows)
     */
    public static function run(CodeBase $code_base, \Closure $file_path_lister, array $options)
    {
        \assert($code_base->isUndoTrackingEnabled());

        $make_language_server = function (ProtocolStreamReader $in, ProtocolStreamWriter $out) use ($code_base, $file_path_lister) : LanguageServer {
            return new LanguageServer(
                $in,
                $out,
                $code_base,
                $file_path_lister
            );
        };
        // example requests over TCP
        // Assumes that clients send and close the their requests quickly, then wait for a response.

        // {"method":"analyze","files":["/path/to/file1.php","/path/to/file2.php"]}

        // FIXME add re-parsing files to actions taken during loop
        /*
        $socket_server = self::createDaemonStreamSocketServer();
        // TODO: Limit the maximum number of active processes to a small number(4?)
        // TODO: accept SIGCHLD when child terminates, somehow?
        try {
            $gotSignal = false;
            pcntl_signal(SIGCHLD, function(...$args) use(&$gotSignal) {
                $gotSignal = true;
                Request::childSignalHandler(...$args);
            });
            while (true) {
                $gotSignal = false;  // reset this.
                // We get an error from stream_socket_accept. After the RuntimeException is thrown, pcntl_signal is called.
                $previousErrorHandler = set_error_handler(function ($severity, $message, $file, $line) use (&$previousErrorHandler) {
                    self::debugf("In new error handler '$message'");
                    if (!preg_match('/stream_socket_accept/i', $message)) {
                        return $previousErrorHandler($severity, $message, $file, $line);
                    }
                    throw new \RuntimeException("Got signal");
                });

                $conn = false;
                try {
                    $conn = stream_socket_accept($socket_server, -1);
                } catch(\RuntimeException $e) {
                    self::debugf("Got signal");
                    pcntl_signal_dispatch();
                    self::debugf("done processing signals");
                    if ($gotSignal) {
                        continue;  // Ignore notices from stream_socket_accept if it's due to being interrupted by a child process terminating.
                    }
                } finally {
                    restore_error_handler();
                }

                if (!\is_resource($conn)) {
                    // If we didn't get a connection, and it wasn't due to a signal from a child process, then stop the daemon.
                    break;
                }
                $request = Request::accept($code_base, $file_path_lister, $conn);
                if ($request instanceof Request) {
                    return $request;  // We forked off a worker process successfully, and this is the worker process
                }
            }
            error_log("Stopped accepting connections");
        } finally {
            restore_error_handler();
        }
        return null;
         */
        if (!empty($options['tcp'])) {
            // Connect to a TCP server
            $address = $options['tcp'];
            $socket = stream_socket_client('tcp://' . $address, $errno, $errstr);
            if ($socket === false) {
                fwrite(STDERR, "Could not connect to language client. Error $errno\n$errstr");
                exit(1);
            }
            stream_set_blocking($socket, false);
            $ls = $make_language_server(new ProtocolStreamReader($socket), new ProtocolStreamWriter($socket));
            Logger::logInfo("Connected to $address to receive requests");
            Loop\run();
            Logger::logInfo("Finished connecting to $address to receive requests");
            $most_recent_request = $ls->most_recent_request;
            $ls->most_recent_request = null;
            return $most_recent_request;
        } elseif (!empty($options['tcp-server'])) {
            // Run a TCP Server
            $address = $options['tcp-server'];
            $tcpServer = stream_socket_server('tcp://' . $address, $errno, $errstr);
            if ($tcpServer === false) {
                fwrite(STDERR, "Could not listen on $address. Error $errno\n$errstr");
                exit(1);
            }
            fwrite(STDOUT, "Server listening on $address\n");
            if (!extension_loaded('pcntl')) {
                fwrite(STDERR, "PCNTL is not available. Only a single connection will be accepted\n");
            }
            while ($socket = stream_socket_accept($tcpServer, -1)) {
                fwrite(STDOUT, "Connection accepted\n");
                stream_set_blocking($socket, false);
                /**if (false && extension_loaded('pcntl')) {  // FIXME re-enable, this was disabled to simplify testing
                    // TODO: This will work, but does it make sense?

                    // If PCNTL is available, fork a child process for the connection
                    // An exit notification will only terminate the child process
                    $pid = pcntl_fork();
                    if ($pid === -1) {
                        fwrite(STDERR, "Could not fork\n");
                        exit(1);
                    } else if ($pid === 0) {
                        // Child process
                        $reader = new ProtocolStreamReader($socket);
                        $writer = new ProtocolStreamWriter($socket);
                        $reader->on('close', function () {
                            fwrite(STDOUT, "Connection closed\n");
                        });
                        $ls = $make_language_server($reader, $writer);
                        Logger::logInfo("Worker started accepting requests on $address");
                        Loop\run();
                        Logger::logInfo("Worker finished accepting requests on $address");
                        $most_recent_request = $ls->most_recent_request;
                        $ls->most_recent_request = null;
                        return $most_recent_request;;
                    }
                } else {*/
                    // To avoid edge cases, we only accept one connection.
                    // If PCNTL is not available, we only accept one connection.
                    // An exit notification will terminate the server
                    $ls = $make_language_server(
                        new ProtocolStreamReader($socket),
                        new ProtocolStreamWriter($socket)
                    );
                    Logger::logInfo("Started listening on tcp");
                    Loop\run();
                    Logger::logInfo("Finished listening on tcp");
                    $most_recent_request = $ls->most_recent_request;
                    $ls->most_recent_request = null;
                    return $most_recent_request;
                /* } */
            }
        } else {
            assert($options['stdin'] === true);
            // Use STDIO
            stream_set_blocking(STDIN, false);
            $ls = $make_language_server(
                new ProtocolStreamReader(STDIN),
                new ProtocolStreamWriter(STDOUT)
            );
            Logger::logInfo("Started listening on stdin");
            Loop\run();
            Logger::logInfo("Finished listening on stdin");
            $most_recent_request = $ls->most_recent_request;
            $ls->most_recent_request = null;
            return $most_recent_request;
        }
    }

    /**
     * Asynchronously analyze the given URI.
     * @return void
     */
    public function analyzeURIAsync(string $uri)
    {
        $path_to_analyze = Utils::uriToPath($uri);
        Logger::logInfo("Called analyzeURIAsync, uri=$uri, path=$path_to_analyze");
        $this->analyze_request_set[$path_to_analyze] = $uri;
        // Don't call file_path_lister immediately -
        // That has to walk the directories in .phan/config.php to see if the requested path is included and not excluded.
    }

    /**
     * Gets URIs (and corresponding paths) which the language server client needs Phan to re-analyze.
     * This excludes any files that aren't in files and directories of .phan/config.php
     *
     * @return array<string,string> maps relative path to the file URI.
     */
    private function getFilteredURIsToAnalyze() : array
    {
        $uris_to_analyze = $this->analyze_request_set;
        if (\count($uris_to_analyze) === 0) {
            return [];
        }
        $this->analyze_request_set = [];

        // Always recompute the file list from the directory list : see src/phan.php
        $file_path_list = ($this->file_path_lister)(true);
        $filtered_uris_to_analyze = [];
        foreach ($uris_to_analyze as $path_to_analyze => $uri) {
            if (!is_string($uri)) {
                Logger::logInfo("Uri for path '$path_to_analyze' is not a string, should not happen");
                continue;
            }
            $relative_path_to_analyze = FileRef::getProjectRelativePathForPath($path_to_analyze);
            if (!\in_array($uri, $file_path_list) && !\in_array($relative_path_to_analyze, $file_path_list)) {
                Logger::logInfo("Path '$relative_path_to_analyze' (URI '$uri') not in parse list, skipping");
                continue;
            }
            $filtered_uris_to_analyze[$relative_path_to_analyze] = $uri;
        }
        return $filtered_uris_to_analyze;
    }

    /**
     * @return void
     */
    private function finalizeAnalyzingURIs()
    {
        $uris_to_analyze = $this->getFilteredURIsToAnalyze();
        if (\count($uris_to_analyze) === 0) {
            return;
        }

        // Add anything that's open in the IDE to the uris to analyze.
        // In the future, this behavior may be configurable.
        foreach ($this->file_mapping->getOverrides() as $path => $_) {
            if (!isset($uris_to_analyze[$path])) {
                $uris_to_analyze[$path] = $this->file_mapping->getURIForPath($path);
            }
        }

        if (Config::getValue('language_server_use_pcntl_fallback')) {
            $this->finishAnalyzingURIsWithoutPcntl($uris_to_analyze);
            return;
        }

        // TODO: check if $path_to_analyze can be analyzed first.
        $sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
        if (!$sockets) {
            error_log("unable to create stream socket pair");
            exit(EXIT_FAILURE);
        }

        $this->most_recent_request = null;

        $pid = pcntl_fork();
        if ($pid < 0) {
            error_log(posix_strerror(posix_get_last_error()));
            exit(EXIT_FAILURE);
        }

        // Parent
        // FIXME: make this async as well, and rate limit it.
        if ($pid > 0) {
            $read_stream = self::streamForParent($sockets);
            $concatenated = '';
            while (!feof($read_stream)) {
                $buffer = fread($read_stream, 1024);
                if (strlen($buffer) > 0) {
                    $concatenated .= $buffer;
                }
            }
            $json_contents = json_decode($concatenated, true);
            if (!\is_array($json_contents)) {
                Logger::logInfo("Fetched non-json: " . $concatenated);
                return;
            }
            $this->handleJSONResponseFromWorker($uris_to_analyze, $json_contents);
            return;
        }

        $child_stream = self::streamForChild($sockets);
        $paths_to_analyze = array_keys($uris_to_analyze);
        $this->most_recent_request = Request::makeLanguageServerAnalysisRequest(
            new StreamResponder($child_stream, false),
            $paths_to_analyze,
            $this->code_base,
            $this->file_path_lister,
            $this->file_mapping,
            true  // We are the fork. Call exit() instead of throwing ExitException
        );
        // FIXME update the parsed file lists before and after (e.g. add to analyzeURI). See Daemon\Request::accept()
        //    TODO: refactor accept() to make it easier to work with.
        // TODO: add unit tests

        $this->protocolReader->stopAcceptingNewRequests();
        $this->is_accepting_new_requests = false;
        Loop\stop();  // abort the loop (without closing streams?)
    }

    /**
     * @param array<string,string> $uris_to_analyze
     * @return void
     *
     * @suppress PhanAccessMethodInternal
     */
    private function finishAnalyzingURIsWithoutPcntl(array $uris_to_analyze)
    {
        $paths_to_analyze = array_keys($uris_to_analyze);
        // When there is no pcntl:
        // Create a fake request object.
        // Instead of stopping the loop, keep going with the loop and keep accepting the requests
        $responder = new CapturerResponder([]);
        $code_base = $this->code_base;

        $analysis_request = Request::makeLanguageServerAnalysisRequest(
            $responder,
            $paths_to_analyze,
            $code_base,
            $this->file_path_lister,
            $this->file_mapping,
            false  // We aren't forking. Throw ExitException instead of calling exit()
        );

        $analyze_file_path_list = $analysis_request->filterFilesToAnalyze($this->code_base->getParsedFilePathList());
        if (count($analyze_file_path_list) === 0) {
            // Nothing to do, don't start analysis
            return;
        }

        // Do this before we stop tracking undo operations.
        $temporary_file_mapping = $analysis_request->getTemporaryFileMapping();

        $restore_point = $code_base->createRestorePoint();

        // Stop tracking undo operations, now that the parse phase is done.
        // This is re-enabled in restoreFromRestorePoint
        $code_base->disableUndoTracking();

        Phan::setPrinter($analysis_request->getPrinter());

        try {
            Phan::finishAnalyzingRemainingStatements($this->code_base, $analysis_request, $analyze_file_path_list, $temporary_file_mapping);
        } catch (ExitException $e) {
            // This is normal, do nothing
        }

        $response_data = $responder->getResponseData();
        if (!$response_data) {
            // Something is probably broken if we don't get response data
            // But just in case we can recover, restore this.
            $code_base->restoreFromRestorePoint($restore_point);
            throw new \RuntimeException("Failed to get a response from a worker");
        }

        // Send a response with diagnostics to the language server client.
        // It should be slightly faster to send a response
        // if the language server sends data before restoring the state of the codebase.
        // (Transforming the JSON response does not depend on the $code_base object)
        $this->handleJSONResponseFromWorker($uris_to_analyze, $response_data);

        $code_base->restoreFromRestorePoint($restore_point);

        Logger::logInfo("Response from non-pcntl server: " . json_encode($response_data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES));
    }

    /**
     * @param array<string,string> $uris_to_analyze
     * @param array{issues:array} $response_data
     * @return void
     */
    private function handleJSONResponseFromWorker(array $uris_to_analyze, array $response_data)
    {
        if (!\array_key_exists('issues', $response_data)) {
            Logger::logInfo("Failed to fetch 'issues' from JSON:" . json_encode($response_data));
            return;
        }
        $diagnostics = [];
        // Normalize the uri so that it will be the same as URIs phan would send for diagnostics.
        // E.g. "file:///path/path%.php" will be normalized to "file:///path/path%25.php"
        foreach ($uris_to_analyze as $uri) {
            $normalized_requested_uri = Utils::pathToUri(Utils::uriToPath($uri));
            $diagnostics[$normalized_requested_uri] = [];  // send an empty diagnostic list on failure.
        }

        foreach ($response_data['issues'] ?? [] as $issue) {
            list($issue_uri, $diagnostic) = self::generateDiagnostic($issue);
            if ($diagnostic instanceof Diagnostic) {
                $diagnostics[$issue_uri][] = $diagnostic;
            }
        }
        foreach ($diagnostics as $diagnostics_uri => $diagnostics_list) {
            $this->client->textDocument->publishDiagnostics($diagnostics_uri, $diagnostics_list);
        }
    }

    /**
     * @param array $issue
     * @return null[]|string[]|Diagnostic[] - On success, returns [string $uri, Diagnostic $diagnostic]
     */
    private static function generateDiagnostic($issue) : array
    {
        if ($issue['type'] !== 'issue') {
            return [null, null];
        }
        //$check_name = $issue['check_name'];
        $description = $issue['description'];
        $severity = $issue['severity'];
        $path = Config::projectPath($issue['location']['path']);
        $issue_uri = Utils::pathToUri($path);
        $start_line = $issue['location']['lines']['begin'];
        $start_line = (int)max($start_line, 1);
        // If we ever supported end_line:
        // $end_line = $issue['location']['lines']['end'] ?? $start_line;
        // $end_line = max($end_line, 1);
        // Language server has 0 based lines and columns, phan has 1-based lines and columns.
        $range = new Range(new Position($start_line - 1, 0), new Position($start_line, 0));
        switch ($severity) {
            case Issue::SEVERITY_LOW:
                $diagnostic_severity = DiagnosticSeverity::INFORMATION;
                break;
            case Issue::SEVERITY_NORMAL:
                $diagnostic_severity = DiagnosticSeverity::WARNING;
                break;
            case Issue::SEVERITY_CRITICAL:
            default:
                $diagnostic_severity = DiagnosticSeverity::ERROR;
                break;
        }
        // TODO: copy issue code in 'json' format
        return [$issue_uri, new Diagnostic($description, $range, $issue['type_id'], $diagnostic_severity, 'Phan')];
    }

    /**
     * Prepare the socket pair to be used in a parent process and
     * return the stream the parent will use to read results.
     *
     * @param resource[] $sockets the socket pair for IPC
     * @return resource
     */
    private static function streamForParent(array $sockets)
    {
        list($for_read, $for_write) = $sockets;

        // The parent will not use the write channel, so it
        // must be closed to prevent deadlock.
        fclose($for_write);

        // stream_select will be used to read multiple streams, so these
        // must be set to non-blocking mode.
        if (!stream_set_blocking($for_read, false)) {
            error_log('unable to set read stream to non-blocking');
            exit(EXIT_FAILURE);
        }

        return $for_read;
    }

    /**
     * Prepare the socket pair to be used in a child process and return
     * the stream the child will use to write results.
     *
     * @param resource[] $sockets the socket pair for IPC.
     * @return resource
     */
    private static function streamForChild(array $sockets)
    {
        list($for_read, $for_write) = $sockets;

        // The while will not use the read channel, so it must
        // be closed to prevent deadlock.
        fclose($for_read);
        return $for_write;
    }


    /**
     * The initialize request is sent as the first request from the client to the server.
     *
     * @param ClientCapabilities $capabilities The capabilities provided by the client (editor)
     * @param string|null $rootPath The rootPath of the workspace. Is null if no folder is open.
     * @param int|null $processId The process Id of the parent process that started the server. Is null if the process has not been started by another process. If the parent process is not alive then the server should exit (see exit notification) its process.
     * @return Promise <InitializeResult>
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null): Promise
    {
        return coroutine(function () : \Generator {
            // Eventually, this might block on something. Leave it as a generator.
            if (false) {
                yield null;
            }

            // There would be an asynchronous indexing step, but the startup already did the indexing.
            if ($this->textDocument === null) {
                $this->textDocument = new TextDocument(
                    $this->client,
                    $this,
                    $this->file_mapping
                );
            }

            $serverCapabilities = new ServerCapabilities();

            // FULL: Ask the client to return always return full documents (because we need to rebuild the AST from scratch)
            // NONE: Don't sync until the user explitly saves a document.
            $serverCapabilities->textDocumentSync = $this->makeTextDocumentSyncOptions();

            // TODO: Support "Find all symbols"?
            //$serverCapabilities->documentSymbolProvider = true;
            // TODO: Support "Find all symbols in workspace"?
            //$serverCapabilities->workspaceSymbolProvider = true;
            // XXX do this next?
            // TODO: Support "Go to definition" (reasonably practical, should be able to infer types in many cases)
            // $serverCapabilities->definitionProvider = false;
            // TODO: (probably impractical, slow) Support "Find all references"? (We don't track this, except when checking for dead code elimination possibilities.
            // $serverCapabilities->referencesProvider = false;
            // Can't support "Hover" without phpdoc for internal functions, such as those from phpstorm
            // Also redundant if php.
            // $serverCapabilities->hoverProvider = false;
            // XXX support completion next?
            // Requires php-parser-to-php-ast (or tolerant php-parser)
            // Support "Completion"
            // $serverCapabilities->completionProvider = new CompletionOptions;
            // $serverCapabilities->completionProvider->resolveProvider = false;
            // $serverCapabilities->completionProvider->triggerCharacters = ['$', '>'];
            // Can't support global references at the moment, I think.
            //$serverCapabilities->xworkspaceReferencesProvider = true;
            //$serverCapabilities->xdefinitionProvider = true;
            //$serverCapabilities->xdependenciesProvider = true;

            return new InitializeResult($serverCapabilities);
        });
    }

    private function makeTextDocumentSyncOptions() : TextDocumentSyncOptions
    {
        $textDocumentSyncOptions = new TextDocumentSyncOptions();
        $textDocumentSyncOptions->openClose = true;
        $textDocumentSyncOptions->change = Config::getValue('language_server_analyze_only_on_save') ? TextDocumentSyncKind::NONE : TextDocumentSyncKind::FULL;

        $saveOptions = new SaveOptions();
        $saveOptions->includeText = true;
        $textDocumentSyncOptions->save = $saveOptions;
        return $textDocumentSyncOptions;
    }

    /**
     * @suppress PhanUnreferencedPublicMethod this is called by the client through AdvancedJsonRpc
     */
    public function initialized()
    {
        Logger::logInfo("Called initialized on language server, currently a no-op");
    }

    /**
     * The shutdown request is sent from the client to the server. It asks the server to shut down, but to not exit
     * (otherwise the response might not be delivered correctly to the client). There is a separate exit notification that
     * asks the server to exit.
     *
     * @return void
     */
    public function shutdown()
    {
        // TODO: Does phan need to do anything else except respond?
        Logger::logInfo("Called shutdown on language server");
    }

    /**
     * A notification to ask the server to exit its process.
     *
     * @return void
     */
    public function exit()
    {
        // This is handled by the main process. No forks are aftive.
        Logger::logInfo("Called exit on language server");
        exit(0);
    }
}
<?php declare(strict_types=1);

namespace Phan\LanguageServer;

use InvalidArgumentException;
use Sabre\Event\Loop;
use Throwable;

/**
 * Utils that are useful for implementing a language server.
 *
 * Taken from code by Felix Frederick Becker
 *
 * Source: https://github.com/felixfbecker/php-language-server
 *
 * - Mostly from that project's src/utils.php
 */
class Utils
{
    /**
     * @return void
     * @suppress PhanUnreferencedPublicMethod
     */
    public static function crash(Throwable $err)
    {
        Loop\nextTick(function () use ($err) {
            throw $err;
        });
    }

    /**
     * Transforms an absolute file path into a URI as used by the language server protocol.
     *
     * @param string $filepath
     * @return string
     */
    public static function pathToUri(string $filepath) : string
    {
        // TODO: Make the return value of str_replace depend on the param value
        $filepath = \trim(\str_replace('\\', '/', $filepath), '/');
        $parts = \explode('/', $filepath);
        // Don't %-encode the colon after a Windows drive letter
        $first = \array_shift($parts);
        if (substr($first, -1) !== ':') {
            $first = \rawurlencode($first);
        }
        $parts = \array_map('rawurlencode', $parts);
        array_unshift($parts, $first);
        $filepath = \implode('/', $parts);
        return 'file:///' . $filepath;
    }

    /**
     * Transforms URI into file path
     *
     * @param string $uri
     * @return string
     * @throws InvalidArgumentException
     */
    public static function uriToPath(string $uri) : string
    {
        $fragments = \parse_url($uri);
        if ($fragments === null || !isset($fragments['scheme']) || $fragments['scheme'] !== 'file') {
            throw new InvalidArgumentException("Not a valid file URI: $uri");
        }
        $filepath = \urldecode($fragments['path']);
        if (strpos($filepath, ':') !== false) {
            if ($filepath[0] === '/') {
                $filepath = \substr($filepath, 1);
            }
            $filepath = \str_replace('/', '\\', $filepath);
        }
        return $filepath;
    }
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer\Client;

use Phan\LanguageServer\ClientHandler;
use Phan\LanguageServer\Protocol\Diagnostic;
use Sabre\Event\Promise;

/**
 * Provides method handlers for all textDocument/* methods
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/TextDocument.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class TextDocument
{
    /**
     * @var ClientHandler
     */
    private $handler;

    public function __construct(ClientHandler $handler)
    {
        $this->handler = $handler;
    }

    /**
     * Diagnostics notification are sent from the server to the client to signal results of validation runs.
     *
     * @param string $uri
     * @param Diagnostic[] $diagnostics
     * @return Promise <void>
     */
    public function publishDiagnostics(string $uri, array $diagnostics): Promise
    {
        return $this->handler->notify('textDocument/publishDiagnostics', [
            'uri' => $uri,
            'diagnostics' => $diagnostics
        ]);
    }
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer;

use Sabre\Event\EmitterInterface;

/**
 * Must emit a "message" event with a Protocol\Message object as parameter
 * when a message comes in
 *
 * Must emit a "close" event when the stream closes
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/ProtocolReader.php
 * See ../../../LICENSE.LANGUAGE_SERVER
 */
interface ProtocolReader extends EmitterInterface
{
    /**
     * @return void
     */
    public function stopAcceptingNewRequests();
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer;

/**
 * Generates unique, incremental IDs for use as request IDs
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/IdGenerator.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class IdGenerator
{
    /**
     * @var int
     */
    public $counter = 1;

    /**
     * Returns a unique ID
     *
     * @return int
     */
    public function generate()
    {
        return $this->counter++;
    }
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/LanguageClient.php
 * See ../../../LICENSE.LANGUAGE_SERVER
 */
class LanguageClient
{
    /**
     * Handles textDocument/* methods
     *
     * @var Client\TextDocument
     */
    public $textDocument;

    /**
     *
     */
    public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
    {
        $handler = new ClientHandler($reader, $writer);

        $this->textDocument = new Client\TextDocument($handler);
    }
}
<?php declare(strict_types = 1);

namespace Phan\LanguageServer\Server;

use Phan\LanguageServer\FileMapping;
use Phan\LanguageServer\LanguageClient;
use Phan\LanguageServer\LanguageServer;
use Phan\LanguageServer\Logger;
use Phan\LanguageServer\Protocol\TextDocumentContentChangeEvent;
use Phan\LanguageServer\Protocol\TextDocumentIdentifier;
use Phan\LanguageServer\Protocol\TextDocumentItem;
use Phan\LanguageServer\Protocol\VersionedTextDocumentIdentifier;
use Phan\LanguageServer\Utils;

/**
 * Provides method handlers for all textDocument/* methods
 * Source: https://github.com/felixfbecker/php-language-server/blob/master/src/Server/TextDocument.php
 */
class TextDocument
{
    /**
     * The language client object to call methods on the client
     *
     * @var LanguageClient
     */
    protected $client;

    /**
     * The language client object to call methods on the server
     *
     * @var LanguageServer
     */
    protected $server;

    /**
     * Maps paths of files on disk to overrides.
     * @var FileMapping
     */
    protected $file_mapping;

    /**
     * @param LanguageClient $client
     * @param FileMapping $file_mapping
     */
    public function __construct(
        LanguageClient $client,
        LanguageServer $server,
        FileMapping $file_mapping
    ) {
        $this->client = $client;
        $this->server = $server;
        $this->file_mapping = $file_mapping;
    }


    /**
     * The document symbol request is sent from the client to the server to list all symbols found in a given text
     * document.
     * FIXME: reintroduce when support is added back
     *
     * @param TextDocumentIdentifier $textDocument
     * @return Promise <SymbolInformation[]>
     */
    /*
    public function documentSymbol(TextDocumentIdentifier $textDocument): Promise
    {
        return $this->documentLoader->getOrLoad($textDocument->uri)->then(function (PhpDocument $document) {
            $symbols = [];
            foreach ($document->getDefinitions() as $fqn => $definition) {
                $symbols[] = $definition->symbolInformation;
            }
            return $symbols;
        });
    }
     */

    /**
     * The document open notification is sent from the client to the server to signal newly opened text documents. The
     * document's truth is now managed by the client and the server must not try to read the document's truth using the
     * document's uri.
     *
     * @param TextDocumentItem $textDocument The document that was opened.
     * @return void
     * @suppress PhanUnreferencedPublicMethod called by client via AdvancedJsonRpc
     */
    public function didOpen(TextDocumentItem $textDocument)
    {
        $this->file_mapping->addOverrideURI($textDocument->uri, $textDocument->text);
        Logger::logInfo("Called didOpen, uri={$textDocument->uri}");
        $this->server->analyzeURIAsync($textDocument->uri);

        //$document = $this->documentLoader->open($textDocument->uri, $textDocument->text);
        // TODO: make this trigger re-analysis
        // TODO: Check based on parse and analyze directories and Phan supported file extensions if this file affects Phan's analysis.
        // TODO:   Add functions to quickly check if a relative/absolute path is within the parse or analysis list of a project
        // TODO:   Maybe allow reloading .phan/config, at least the files and directories to parse/analyze
    }

    /**
     * The document save notification is sent from the client to the server when the document was saved in the client.
     * TODO: Should this use willSave instead
     * TODO: Why is this not triggering on Ctrl+S
     *
     * @param VersionedTextDocumentIdentifier $textDocument
     * @param string|null $text (NOTE: can't use ?T here)
     * @return void
     * @suppress PhanTypeMismatchArgument
     * @suppress PhanUnreferencedPublicMethod called by client via AdvancedJsonRpc
     */
    public function didSave(TextDocumentIdentifier $textDocument, string $text = null)
    {
        $this->file_mapping->addOverrideURI($textDocument->uri, $text);
        Logger::logInfo("Called didSave, uri={$textDocument->uri} len(text)=" . strlen($text ?? ''));
        $this->server->analyzeURIAsync($textDocument->uri);
    }

    /**
     * The document change notification is sent from the client to the server to signal changes to a text document.
     *
     * @param VersionedTextDocumentIdentifier $textDocument
     * @param TextDocumentContentChangeEvent[] $contentChanges
     * @return void
     * @suppress PhanUnreferencedPublicMethod called by client via AdvancedJsonRpc
     */
    public function didChange(VersionedTextDocumentIdentifier $textDocument, array $contentChanges)
    {
        foreach ($contentChanges as $change) {
            $this->file_mapping->addOverrideURI($textDocument->uri, $change->text);
        }
        Logger::logInfo("Called didChange, uri={$textDocument->uri} version={$textDocument->version}");
        $this->server->analyzeURIAsync($textDocument->uri);

        // TODO:   Maybe allow reloading .phan/config, at least the files and directories to parse/analyze
    }

    /**
     * The document close notification is sent from the client to the server when the document got closed in the client.
     * The document's truth now exists where the document's uri points to (e.g. if the document's uri is a file uri the
     * truth now exists on disk).
     *
     * @param TextDocumentIdentifier $textDocument The document that was closed
     * @return void
     * @suppress PhanUnreferencedPublicMethod called by client via AdvancedJsonRpc
     */
    public function didClose(TextDocumentIdentifier $textDocument)
    {
        $this->file_mapping->removeOverrideURI($textDocument->uri);
        $this->client->textDocument->publishDiagnostics(Utils::pathToUri(Utils::uriToPath($textDocument->uri)), []);
        Logger::logInfo("Called didClose, uri={$textDocument->uri}");
    }
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer\Server;

use Phan\LanguageServer\FileMapping;
use Phan\LanguageServer\LanguageClient;
use Phan\LanguageServer\LanguageServer;
use Phan\LanguageServer\Protocol\FileChangeType;
use Phan\LanguageServer\Protocol\FileEvent;
use Phan\LanguageServer\Utils;

/**
 * Provides method handlers for all workspace/* methods
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Server/Workspace.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class Workspace
{
    /**
     * @var LanguageClient
     */
    public $client;

    /**
     * @var LanguageServer
     */
    public $server;

    /**
     * @var FileMapping
     */
    public $file_mapping;

    /**
     * @param LanguageClient    $client            LanguageClient instance used to signal updated results
     * FIXME: Rewrite to avoid static methods?
     */
    public function __construct(
        LanguageClient $client,
        LanguageServer $server,
        FileMapping $file_mapping
    ) {
        $this->client = $client;
        $this->server = $server;
        $this->file_mapping = $file_mapping;
    }

    /**
     * The watched files notification is sent from the client to the server when the client detects changes to files watched by the language client.
     *
     * @param FileEvent[] $changes
     * @return void
     * @suppress PhanUnreferencedPublicMethod called by client via AdvancedJsonRpc
     */
    public function didChangeWatchedFiles(array $changes)
    {
        // invalidate Phan's cache for these files if changed, added, or modified outside of the IDE
        foreach ($changes as $change) {
            $this->file_mapping->removeOverrideURI($change->uri);
        }
        // Trigger diagnostics. TODO: Is that necessary?
        foreach ($changes as $change) {
            if ($change->type === FileChangeType::DELETED) {
                $this->client->textDocument->publishDiagnostics(Utils::pathToUri(Utils::uriToPath($change->uri)), []);
            }
        }
        // TODO: more than one file
        foreach ($changes as $change) {
            // TODO: What about CREATED? Will that be emitted for renaming files?
            if ($change->type === FileChangeType::CHANGED) {
                $uri = $change->uri;
                $this->server->analyzeURIAsync($uri);
            }
        }
    }

    /**
     * no-op for now. Stop the JSON RPC2 framework from warning about this method being undefined.
     * TODO: Define this so that Phan can respond to changes in client configuration.
     * @suppress PhanUnreferencedPublicMethod called by client via AdvancedJsonRpc
     *
     * @param $settings @phan-unused-param
     */
    public function didChangeConfiguration($settings)
    {
    }
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer;

use Phan\LanguageServer\Logger;
use Phan\LanguageServer\Protocol\Message;
use AdvancedJsonRpc\Message as MessageBody;
use Sabre\Event\Loop;
use Sabre\Event\Emitter;

use Exception;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/ProtocolStreamReader.php
 */
class ProtocolStreamReader extends Emitter implements ProtocolReader
{
    const PARSE_HEADERS = 1;
    const PARSE_BODY = 2;

    /** @var resource */
    private $input;
    /**
     * This is checked by ProtocolStreamReader so that it will stop reading from streams in the forked process.
     * There could be buffered bytes in stdin/over TCP, those would be processed by TCP if it were not for this check.
     * @var bool
     */
    private $is_accepting_new_requests = true;
    /** @var int */
    private $parsing_mode = self::PARSE_HEADERS;
    /** @var string */
    private $buffer = '';
    /** @var string[] */
    private $headers = [];
    /** @var int */
    private $content_length;
    /** @var bool */
    private $did_emit_close = false;

    /**
     * @param resource $input
     */
    public function __construct($input)
    {
        $this->input = $input;

        $this->on('close', function () {
            Loop\removeReadStream($this->input);
        });

        Loop\addReadStream($this->input, function () {
            if (feof($this->input)) {
                // If stream_select reported a status change for this stream,
                // but the stream is EOF, it means it was closed.
                $this->emitClose();
                return;
            }
            if (!$this->is_accepting_new_requests) {
                // If we fork, don't read any bytes in the input buffer from the worker process.
                $this->emitClose();
                return;
            }
            $emitted_messages = $this->readMessages();
            if ($emitted_messages > 0) {
                $this->emit('readMessageGroup');
            }
        });
    }

    /**
     * @return int
     */
    private function readMessages() : int
    {
        $c = '';
        $emitted_messages = 0;
        while (($c = fgetc($this->input)) !== false && $c !== '') {
            $this->buffer .= $c;
            switch ($this->parsing_mode) {
                case self::PARSE_HEADERS:
                    if ($this->buffer === "\r\n") {
                        $this->parsing_mode = self::PARSE_BODY;
                        $this->content_length = (int)$this->headers['Content-Length'];
                        $this->buffer = '';
                    } elseif (substr($this->buffer, -2) === "\r\n") {
                        $parts = explode(':', $this->buffer);
                        $this->headers[$parts[0]] = trim($parts[1]);
                        $this->buffer = '';
                    }
                    break;
                case self::PARSE_BODY:
                    if (strlen($this->buffer) === $this->content_length) {
                        if (!$this->is_accepting_new_requests) {
                            // If we fork, don't read any bytes in the input buffer from the worker process.
                            $this->emitClose();
                            return $emitted_messages;
                        }
                        Logger::logRequest($this->headers, $this->buffer);
                        // MessageBody::parse can throw an Error, maybe log an error?
                        try {
                            $msg = new Message(MessageBody::parse($this->buffer), $this->headers);
                        } catch (Exception $e) {
                            $msg = null;
                        }
                        if ($msg) {
                            $emitted_messages++;
                            $this->emit('message', [$msg]);
                            if (!$this->is_accepting_new_requests) {
                                // If we fork, don't read any bytes in the input buffer from the worker process.
                                $this->emitClose();
                                return $emitted_messages;
                            }
                        }
                        $this->parsing_mode = self::PARSE_HEADERS;
                        $this->headers = [];
                        $this->buffer = '';
                    }
                    break;
            }
        }
        return $emitted_messages;
    }

    /**
     * @return void
     */
    public function stopAcceptingNewRequests()
    {
        $this->is_accepting_new_requests = false;
    }

    /**
     * @return void
     */
    private function emitClose()
    {
        if ($this->did_emit_close) {
            return;
        }
        $this->did_emit_close = true;
        $this->emit('close');
    }
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer;

use Phan\Config;

/**
 * A logger used by Phan for developing or debugging the language server.
 * Logs to stderr.
 */
class Logger
{
    /** @var resource|false */
    public static $file = false;

    public static function shouldLog() : bool
    {
        return Config::getValue('language_server_debug_level') === 'info';
    }

    /** @return void */
    public static function logRequest(array $headers, string $buffer)
    {
        if (!self::shouldLog()) {
            return;
        }
        self::logInfo(sprintf("Request:\n%s\nData:\n%s\n\n", json_encode($headers), $buffer));
    }

    /** @return void */
    public static function logResponse(array $headers, string $buffer)
    {
        if (!self::shouldLog()) {
            return;
        }
        self::logInfo(sprintf("Response:\n%s\nData:\n%s\n\n", json_encode($headers), $buffer));
    }

    /** @return void */
    public static function logInfo(string $msg)
    {
        if (!self::shouldLog()) {
            return;
        }
        $file = self::getLogFile();
        fwrite($file, $msg . "\n");
    }

    /**
     * @return resource
     */
    private static function getLogFile()
    {
        $file = self::$file;
        if (!$file) {
            self::$file = $file = STDERR;
        }
        return $file;
    }

    /**
     * @param resource $newFile
     * @return void
     * @suppress PhanUnreferencedPublicMethod this is made available for debugging issues
     */
    public static function setLogFile($newFile)
    {
        if (!is_resource($newFile)) {
            throw new \InvalidArgumentException("Expected newFile to be a resource, got " . gettype($newFile));
        }
        if (is_resource(self::$file)) {
            if (self::$file === $newFile) {
                return;
            }
            fclose(self::$file);
            self::$file = false;
        }
        self::$file = $newFile;
    }
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * An event describing a file change.
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/FileEvent.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class FileEvent
{
    /**
     * The file's URI.
     *
     * @var string
     */
    public $uri;

    /**
     * The change type.
     *
     * @var int
     */
    public $type;

    /**
     * @param string $uri
     * @param int $type
     */
    public function __construct(string $uri, int $type)
    {
        $this->uri = $uri;
        $this->type = $type;
    }
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/TextDocumentIdentifier.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class TextDocumentIdentifier
{
    /**
     * The text document's URI.
     *
     * @var string|null
     */
    public $uri;

    /**
     * @param string|null $uri The text document's URI.
     */
    public function __construct(string $uri = null)
    {
        $this->uri = $uri;
    }
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/InitializeResult.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class InitializeResult
{
    /**
     * The capabilities the language server provides.
     *
     * @var ServerCapabilities
     * @suppress PhanWriteOnlyPublicProperty (Used by AdvancedJsonRpc)
     */
    public $capabilities;

    /**
     * @param ?ServerCapabilities $capabilities
     */
    public function __construct(ServerCapabilities $capabilities = null)
    {
        $this->capabilities = $capabilities ?? new ServerCapabilities();
    }
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * TODO: Contribute to php-language-server?
 * Based on TextDocumentSyncOptions description in
 * https://microsoft.github.io/language-server-protocol/specification
 */
class TextDocumentSyncOptions
{
    /**
     * @var bool|null
     * Open and close notifications are sent to the server.
     * @suppress PhanWriteOnlyPublicProperty this is serialized and sent to the client
     */
    public $openClose;

    /**
     * @var int|null
     * Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full
     * and TextDocumentSyncKindIncremental.
     * @suppress PhanWriteOnlyPublicProperty this is serialized and sent to the client
     */
    public $change;

    /**
     * @var bool|null
     * Will save notifications get sent to the server.
     * @suppress PhanUnreferencedPublicProperty this is serialized and sent to the client
     */
    public $willSave;

    /**
     * @var bool|null
     * Will save wait until requests get sent to the server.
     * @suppress PhanUnreferencedPublicProperty this is serialized and sent to the client
     */
    public $willSaveWaitUntil;

    /**
     * @var SaveOptions|null
     * Save notifications are sent to the server.
     */
    public $save;
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * Based on https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/ServerCapabilities.php
 * FIXME: Hook up capabilities for
 */
class ServerCapabilities
{
    /**
     * Defines how text documents are synced.
     *
     * @var TextDocumentSyncOptions|int|null
     * @suppress PhanWriteOnlyPublicProperty (used by AdvancedJsonRpc)
     */
    public $textDocumentSync;

    /**
     * FIXME: Make the server provide completion support, and re-integrate work on TysonAndre/php-parser-to-php-ast
     *
     * @var CompletionOptions|null
     */
    // public $completionProvider;

    /**
     * The server provides find references support.
     *
     * @var bool|null
     */
    // public $referencesProvider;

    /**
     * The server provides document highlight support.
     *
     * @var bool|null
     */
    // public $documentHighlightProvider;

    /**
     * The server provides document symbol support.
     *
     * @var bool|null
     */
    // public $documentSymbolProvider;

    /**
     * The server provides workspace symbol support.
     *
     * @var bool|null
     */
    // public $workspaceSymbolProvider;

    /**
     * The server provides code actions.
     *
     * @var bool|null
     */
    //public $codeActionProvider;

    /**
     * The server provides code lens.
     *
     * @var CodeLensOptions|null
     */
    //public $codeLensProvider;

    /**
     * The server provides document formatting.
     *
     * @var bool|null
     */
    //public $documentFormattingProvider;

    /**
     * The server provides document range formatting.
     *
     * @var bool|null
     */
    //public $documentRangeFormattingProvider;

    /**
     * The server provides document formatting on typing.
     *
     * @var DocumentOnTypeFormattingOptions|null
     */
    //public $documentOnTypeFormattingProvider;

    /**
     * The server provides rename support.
     *
     * @var bool|null
     */
    //public $renameProvider;

    /**
     * The server provides workspace references exporting support.
     *
     * @var bool|null
     */
    //public $xworkspaceReferencesProvider;

    /**
     * The server provides extended text document definition support.
     *
     * @var bool|null
     */
    //public $xdefinitionProvider;

    /**
     * TODO: implement this?
     * The server provides workspace dependencies support.
     *
     * @var bool|null
     */
    //public $dependenciesProvider;
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * Represents a diagnostic, such as a compiler error or warning. Diagnostic objects are only valid in the scope of a
 * resource.
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/Diagnostic.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class Diagnostic
{
    /**
     * The range at which the message applies.
     *
     * @var Range
     * @suppress PhanWriteOnlyPublicProperty this is serialized and sent to the client.
     */
    public $range;

    /**
     * The diagnostic's severity. Can be omitted. If omitted it is up to the
     * client to interpret diagnostics as error, warning, info or hint.
     *
     * @var int|null
     * @suppress PhanWriteOnlyPublicProperty this is serialized and sent to the client.
     */
    public $severity;

    /**
     * The diagnostic's code. Can be omitted.
     *
     * @var int|string|null
     * @suppress PhanWriteOnlyPublicProperty this is serialized and sent to the client.
     */
    public $code;

    /**
     * A human-readable string describing the source of this
     * diagnostic, e.g. 'typescript' or 'super lint'.
     *
     * @var string|null
     * @suppress PhanWriteOnlyPublicProperty this is serialized and sent to the client.
     */
    public $source;

    /**
     * The diagnostic's message.
     *
     * @var string
     * @suppress PhanWriteOnlyPublicProperty this is serialized and sent to the client.
     */
    public $message;

    /**
     * @param  string $message  The diagnostic's message
     * @param  Range  $range    The range at which the message applies
     * @param  int    $code     The diagnostic's code
     * @param  int    $severity DiagnosticSeverity
     * @param  string $source   A human-readable string describing the source of this diagnostic
     * @suppress PhanTypeMismatchProperty
     * @suppress PhanTypeMismatchDeclaredParamNullable
     */
    public function __construct(string $message = null, Range $range = null, int $code = null, int $severity = null, string $source = null)
    {
        $this->message = $message;
        $this->range = $range;
        $this->code = $code;
        $this->severity = $severity;
        $this->source = $source;
    }
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * TODO: Contribute to php-language-server?
 * Based on SaveOptions description in
 * https://microsoft.github.io/language-server-protocol/specification
 */
class SaveOptions
{
    /**
     * @var bool|null
     * The client is supposed to include the content on save.
     * @suppress PhanWriteOnlyPublicProperty (used by AdvancedJsonRpc)
     */
    public $includeText;
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * A range in a text document expressed as (zero-based) start and end positions.
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/Range.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class Range
{
    /**
     * The range's start position.
     *
     * @var Position
     */
    public $start;

    /**
     * The range's end position.
     *
     * @var Position
     */
    public $end;

    public function __construct(Position $start = null, Position $end = null)
    {
        $this->start = $start;
        $this->end = $end;
    }

    /**
     * Checks if a position is within the range
     *
     * @param Position $position
     * @return bool
     * @suppress PhanUnreferencedPublicMethod
     */
    public function includes(Position $position): bool
    {
        return $this->start->compare($position) <= 0 && $this->end->compare($position) >= 0;
    }
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/VersionedTextDocumentIdentifier.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class VersionedTextDocumentIdentifier extends TextDocumentIdentifier
{
    /**
     * The version number of this document.
     *
     * @var int
     */
    public $version;
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * An event describing a change to a text document. If range and rangeLength are omitted
 * the new text is considered to be the full content of the document.
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/TextDocumentContentChangeEvent.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class TextDocumentContentChangeEvent
{
    /**
     * The range of the document that changed.
     *
     * @var Range|null
     * @suppress PhanUnreferencedPublicProperty (We don't support partial updates, yet)
     */
    public $range;

    /**
     * The length of the range that got replaced.
     *
     * @var int|null
     * @suppress PhanUnreferencedPublicProperty (We don't support partial updates, yet)
     */
    public $rangeLength;

    /**
     * The new text of the document.
     *
     * @var string
     */
    public $text;
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * The file event type. Enum
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/FileChangeType.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
abstract class FileChangeType
{
    /**
     * The file got created.
     * @suppress PhanUnreferencedPublicClassConstant
     */
    const CREATED = 1;

    /**
     * The file got changed.
     */
    const CHANGED = 2;

    /**
     * The file got deleted.
     */
    const DELETED = 3;
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/ClientCapabilities.php
 */
class ClientCapabilities
{
    /**
     * The client supports workspace/xfiles requests
     *
     * @var bool|null
     * @suppress PhanUnreferencedPublicProperty may use eventually
     */
    public $xfilesProvider;

    /**
     * The client supports textDocument/xcontent requests
     *
     * @var bool|null
     * @suppress PhanUnreferencedPublicProperty may use eventually
     */
    public $xcontentProvider;

    /**
     * The client supports xcache/* requests
     *
     * @var bool|null
     * @suppress PhanUnreferencedPublicProperty may use eventually
     */
    public $xcacheProvider;
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * Position in a text document expressed as zero-based line and character offset.
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/Position.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class Position
{
    /**
     * Line position in a document (zero-based).
     *
     * @var int
     */
    public $line;

    /**
     * Character offset on a line in a document (zero-based).
     *
     * @var int
     */
    public $character;

    /**
     * @suppress PhanTypeMismatchProperty
     */
    public function __construct(int $line = null, int $character = null)
    {
        $this->line = $line;
        $this->character = $character;
    }

    /**
     * Compares this position to another position
     * Returns
     *  - 0 if the positions match
     *  - a negative number if $this is before $position
     *  - a positive number otherwise
     *
     * @param Position $position
     * @return int
     */
    public function compare(Position $position): int
    {
        if ($this->line === $position->line && $this->character === $position->character) {
            return 0;
        }

        if ($this->line !== $position->line) {
            return $this->line - $position->line;
        }

        return $this->character - $position->character;
    }

    /**
     * Returns the offset of the position in a string
     *
     * @param string $content
     * @return int
     * @suppress PhanUnreferencedPublicMethod
     */
    public function toOffset(string $content): int
    {
        $lines = explode("\n", $content);
        $slice = array_slice($lines, 0, $this->line);
        // TODO: array_sum should infer sum of ints is typically an int
        return ((int)array_sum(array_map('strlen', $slice))) + count($slice) + $this->character;
    }
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * An item to transfer a text document from the client to the server.
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/TextDocumentItem.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
class TextDocumentItem
{
    /**
     * The text document's URI.
     *
     * @var string
     */
    public $uri;

    /**
     * The text document's language identifier.
     *
     * @var string
     * @suppress PhanUnreferencedPublicProperty (Part of the protocol but Phan doesn't use it)
     */
    public $languageId;

    /**
     * The version number of this document (it will strictly increase after each
     * change, including undo/redo).
     *
     * @var int
     * @suppress PhanUnreferencedPublicProperty (Part of the protocol but Phan doesn't use it)
     */
    public $version;

    /**
     * The content of the opened text document.
     *
     * @var string
     */
    public $text;
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/DiagnosticSeverity.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
abstract class DiagnosticSeverity
{
    /**
     * Reports an error.
     */
    const ERROR = 1;

    /**
     * Reports a warning.
     */
    const WARNING = 2;

    /**
     * Reports an information.
     */
    const INFORMATION = 3;

    /**
     * Reports a hint.
     * @suppress PhanUnreferencedPublicClassConstant unused, but documented
     */
    const HINT = 4;
}
<?php
declare(strict_types = 1);

namespace Phan\LanguageServer\Protocol;

use AdvancedJsonRpc\Message as MessageBody;

/**
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/Message.php
 */
class Message
{
    /**
     * @var ?\AdvancedJsonRpc\Message
     */
    public $body;

    /**
     * @var array<string,string>
     */
    public $headers;

    /**
     * Parses a message
     *
     * @param string $msg
     * @return Message
     */
    public static function parse(string $msg): Message
    {
        $obj = new self;
        $parts = explode("\r\n", $msg);
        $obj->body = MessageBody::parse(array_pop($parts));
        foreach ($parts as $line) {
            if ($line) {
                $pair = explode(': ', $line);
                $obj->headers[$pair[0]] = $pair[1];
            }
        }
        return $obj;
    }

    /**
     * @param ?MessageBody $body
     * @param array<string,string> $headers
     */
    public function __construct(MessageBody $body = null, array $headers = [])
    {
        $this->body = $body;
        if (!isset($headers['Content-Type'])) {
            $headers['Content-Type'] = 'application/vscode-jsonrpc; charset=utf8';
        }
        $this->headers = $headers;
    }

    public function __toString(): string
    {
        $body = (string)$this->body;
        $contentLength = strlen($body);
        $this->headers['Content-Length'] = (string)$contentLength;
        $headers = '';
        foreach ($this->headers as $name => $value) {
            $headers .= "$name: $value\r\n";
        }
        return $headers . "\r\n" . $body;
    }
}
<?php

namespace Phan\LanguageServer\Protocol;

/**
 * Defines how the host (editor) should sync document changes to the language server.
 *
 * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/Protocol/TextDocumentSyncKind.php
 * See ../../../../LICENSE.LANGUAGE_SERVER
 */
abstract class TextDocumentSyncKind
{
    /**
     * Documents should not be synced at all.
     */
    const NONE = 0;

    /**
     * Documents are synced by always sending the full content of the document.
     */
    const FULL = 1;

    /**
     * Documents are synced by sending the full content on open. After that only
     * incremental updates to the document are sent.
     * @suppress PhanUnreferencedPublicClassConstant (unused)
     */
    const INCREMENTAL = 2;
}
<?php declare(strict_types = 1);

namespace Phan\LanguageServer;

/**
 * A class to keep track of overrides by language server clients with open files.
 * Created for Phan.
 *
 * TODO: remove all overrides when a language client disconnects.
 * (Right now, we only have a single client, and shut down when the client disconnects)
 */
class FileMapping
{
    /**
     * @var array<string,string> maps the absolute paths on disks to the currently edited versions of those files.
     * TODO: Won't work with more than one client.
     */
    private $overrides = [];

    /**
     * @var array<string,string> maps the absolute path on disk to the URI sent by the language server.
     * This may or may not help avoid creating duplicate requests for a given path.
     */
    private $uri_for_path = [];

    public function __construct()
    {
    }

    public function getOverrides()
    {
        return $this->overrides;
    }

    /**
     * @param string $uri
     * @param ?string $new_contents
     * @return void
     */
    public function addOverrideURI(string $uri, $new_contents)
    {
        $path = Utils::uriToPath($uri);
        if ($new_contents === null) {
            $this->removeOverride($path);
        }
        $this->uri_for_path[$path] = $uri;
        $this->addOverride($path, $new_contents);
    }

    /**
     * @param string $path
     * @param ?string $new_contents
     * @return void
     */
    public function addOverride(string $path, $new_contents)
    {
        if ($new_contents === null) {
            $this->removeOverride($path);
            return;
        }
        $this->overrides[$path] = $new_contents;
    }

    /**
     * @return void
     */
    public function removeOverrideURI(string $uri)
    {
        $path = Utils::uriToPath($uri);
        $this->removeOverride($path);
    }

    public function getURIForPath(string $path) : string
    {
        return $this->uri_for_path[$path] ?? Utils::pathToUri($path);
    }

    /**
     * @return void
     */
    public function removeOverride(string $path)
    {
        unset($this->uri_for_path[$path]);
        unset($this->overrides[$path]);
    }
}
<?php declare(strict_types=1);
namespace Phan;

/**
 * Program configuration.
 *
 * Many of the settings in this class can be overridden in .phan/config.php.
 *
 * Some configuration can be overridden on the command line.
 * See `./phan -h` for command line usage, or take a
 * look at \Phan\CLI.php for more details on CLI usage.
 */
class Config
{
    /**
     * The version of the AST (defined in php-ast) that we're using.
     * Other versions are likely to have edge cases we no longer support,
     * and version 50 got rid of Decl.
     */
    const AST_VERSION = 50;

    /**
     * The version of the Phan plugin system.
     * Plugin files that wish to be backwards compatible may check this and
     * return different classes based on its existence and
     * the results of version_compare.
     * PluginV2 will correspond to 2.x.y, PluginV3 will correspond to 3.x.y, etc.
     * New features increment minor versions, and bug fixes increment patch versions.
     * @suppress PhanUnreferencedPublicClassConstant
     */
    const PHAN_PLUGIN_VERSION = '2.3.0';

    /**
     * @var string|null
     * The root directory of the project. This is used to
     * store canonical path names and find project resources
     */
    private static $project_root_directory = null;

    /**
     * Configuration options
     */
    private static $configuration = self::DEFAULT_CONFIGURATION;

    // The most commonly accessed configs:
    /** @var bool */
    private static $null_casts_as_any_type = false;

    /** @var bool */
    private static $null_casts_as_array = false;

    /** @var bool */
    private static $array_casts_as_null = false;

    /** @var bool */
    private static $strict_param_checking = false;

    /** @var bool */
    private static $strict_property_checking = false;

    /** @var bool */
    private static $strict_return_checking = false;

    /** @var bool */
    private static $track_references = false;

    /** @var bool */
    private static $backward_compatibility_checks = false;

    /** @var bool */
    private static $quick_mode = false;
    // End of the 4 most commonly accessed configs.

    private static $closest_target_php_version_id = null;

    const DEFAULT_CONFIGURATION = [
        // Supported values: '7.0', '7.1', '7.2', null.
        // If this is set to null,
        // then Phan assumes the PHP version which is closest to the minor version
        // of the php executable used to execute phan.
        'target_php_version' => null,

        // Supported values: '7.0', '7.1', '7.2', null.
        // If this is set to null,
        // then Phan assumes the PHP version which is closest to the minor version
        // of the php executable used to execute phan.
        'polyfill_parse_all_element_doc_comments' => true,

        // A list of individual files to include in analysis
        // with a path relative to the root directory of the
        // project
        'file_list' => [],

        // A list of directories that should be parsed for class and
        // method information. After excluding the directories
        // defined in exclude_analysis_directory_list, the remaining
        // files will be statically analyzed for errors.
        //
        // Thus, both first-party and third-party code being used by
        // your application should be included in this list.
        'directory_list' => [],

        // List of case-insensitive file extensions supported by Phan.
        // (e.g. php, html, htm)
        'analyzed_file_extensions' => ['php'],

        // A regular expression to match files to be excluded
        // from parsing and analysis and will not be read at all.
        //
        // This is useful for excluding groups of test or example
        // directories/files, unanalyzable files, or files that
        // can't be removed for whatever reason.
        // (e.g. '@Test\.php$@', or '@vendor/.*/(tests|Tests)/@')
        'exclude_file_regex' => '',

        // A file list that defines files that will be excluded
        // from parsing and analysis and will not be read at all.
        //
        // This is useful for excluding hopelessly unanalyzable
        // files that can't be removed for whatever reason.
        'exclude_file_list' => [],

        // A directory list that defines files that will be excluded
        // from static analysis, but whose class and method
        // information should be included.
        //
        // Generally, you'll want to include the directories for
        // third-party code (such as "vendor/") in this list.
        //
        // n.b.: If you'd like to parse but not analyze 3rd
        //       party code, directories containing that code
        //       should be added to the `directory_list` as
        //       to `excluce_analysis_directory_list`.
        'exclude_analysis_directory_list' => [],

        // A file list that defines files that will be included
        // in static analysis, to the exclusion of others.
        'include_analysis_file_list' => [],

        // Backwards Compatibility Checking. This is slow
        // and expensive, but you should consider running
        // it before upgrading your version of PHP to a
        // new version that has backward compatibility
        // breaks.
        'backward_compatibility_checks' => true,

        // A set of fully qualified class-names for which
        // a call to parent::__construct() is required.
        'parent_constructor_required' => [],

        // If true, this run a quick version of checks that takes less
        // time at the cost of not running as thorough
        // an analysis. You should consider setting this
        // to true only when you wish you had more **undiagnosed** issues
        // to fix in your code base.
        //
        // In quick-mode the scanner doesn't rescan a function
        // or a method's code block every time a call is seen.
        // This means that the problem here won't be detected:
        //
        // ```php
        // <?php
        // function test($arg):int {
        //     return $arg;
        // }
        // test("abc");
        // ```
        //
        // This would normally generate:
        //
        // ```sh
        // test.php:3 TypeError return string but `test()` is declared to return int
        // ```
        //
        // The initial scan of the function's code block has no
        // type information for `$arg`. It isn't until we see
        // the call and rescan test()'s code block that we can
        // detect that it is actually returning the passed in
        // `string` instead of an `int` as declared.
        'quick_mode' => false,

        // If enabled, check all methods that override a
        // parent method to make sure its signature is
        // compatible with the parent's. This check
        // can add quite a bit of time to the analysis.
        // This will also check if final methods are overridden, etc.
        'analyze_signature_compatibility' => true,

        // Set this to true to allow contravariance in real parameter types of method overrides
        // (Users may enable this if analyzing projects that support only php 7.2+)
        // See https://secure.php.net/manual/en/migration72.new-features.php#migration72.new-features.param-type-widening
        // This is false by default. (Will warn if real parameter types are omitted in an override)
        // If this is null, this will be inferred from target_php_version.
        'allow_method_param_type_widening' => false,

        // Set this to true to make Phan guess that undocumented parameter types
        // (for optional parameters) have the same type as default values
        // (Instead of combining that type with `mixed`).
        // E.g. `function($x = 'val')` would make Phan infer that $x had a type of `string`, not `string|mixed`.
        // Phan will not assume it knows specific types if the default value is false or null.
        'guess_unknown_parameter_type_using_default' => false,

        // If enabled, inherit any missing phpdoc for types from
        // the parent method if none is provided.
        //
        // NOTE: This step will only be performed if analyze_signature_compatibility is also enabled.
        'inherit_phpdoc_types' => true,

        // The minimum severity level to report on. This can be
        // set to Issue::SEVERITY_LOW, Issue::SEVERITY_NORMAL or
        // Issue::SEVERITY_CRITICAL. Setting it to only
        // critical issues is a good place to start on a big
        // sloppy mature code base.
        'minimum_severity' => Issue::SEVERITY_LOW,

        // If enabled, missing properties will be created when
        // they are first seen. If false, we'll report an
        // error message if there is an attempt to write
        // to a class property that wasn't explicitly
        // defined.
        'allow_missing_properties' => false,

        // If enabled, allow null to be cast as any array-like type.
        // This is an incremental step in migrating away from null_casts_as_any_type.
        // If null_casts_as_any_type is true, this has no effect.
        'null_casts_as_array' => false,

        // If enabled, allow any array-like type to be cast to null.
        // This is an incremental step in migrating away from null_casts_as_any_type.
        // If null_casts_as_any_type is true, this has no effect.
        'array_casts_as_null' => false,

        // If enabled, null can be cast as any type and any
        // type can be cast to null. Setting this to true
        // will cut down on false positives.
        'null_casts_as_any_type' => false,

        // If enabled, Phan will warn if **any** type in the argument's type
        // cannot be cast to a type in the parameter's expected type.
        // Setting this to true will introduce a large number of false positives
        // (and reveal some bugs).
        'strict_param_checking' => false,

        // If enabled, Phan will warn if **any** type in the return value's type
        // cannot be cast to a type in the declared return type.
        // Setting this to true will introduce a large number of false positives
        // (and reveal some bugs).
        'strict_return_checking' => false,

        // If enabled, scalars (int, float, bool, string, null)
        // are treated as if they can cast to each other.
        // This does not affect checks of array keys. See scalar_array_key_cast.
        'scalar_implicit_cast' => false,

        // If enabled, any scalar array keys (int, string)
        // are treated as if they can cast to each other.
        // E.g. array<int,stdClass> can cast to array<string,stdClass> and vice versa.
        // Normally, a scalar type such as int could only cast to/from int and mixed.
        'scalar_array_key_cast' => false,

        // If this has entries, scalars (int, float, bool, string, null)
        // are allowed to perform the casts listed.
        // E.g. ['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']]
        // allows casting null to a string, but not vice versa.
        // (subset of scalar_implicit_cast)
        'scalar_implicit_partial' => [],

        // If true, seemingly undeclared variables in the global
        // scope will be ignored. This is useful for projects
        // with complicated cross-file globals that you have no
        // hope of fixing.
        'ignore_undeclared_variables_in_global_scope' => false,

        // If true, check to make sure the return type declared
        // in the doc-block (if any) matches the return type
        // declared in the method signature.
        'check_docblock_signature_return_type_match' => true,

        // If true, check to make sure the param types declared
        // in the doc-block (if any) matches the param types
        // declared in the method signature.
        'check_docblock_signature_param_type_match' => true,

        // (*Requires check_docblock_signature_param_type_match to be true*)
        // If true, make narrowed types from phpdoc params override
        // the real types from the signature, when real types exist.
        // (E.g. allows specifying desired lists of subclasses,
        //  or to indicate a preference for non-nullable types over nullable types)
        // Affects analysis of the body of the method and the param types passed in by callers.
        'prefer_narrowed_phpdoc_param_type' => true,

        // (*Requires check_docblock_signature_return_type_match to be true*)
        // If true, make narrowed types from phpdoc returns override
        // the real types from the signature, when real types exist.
        // (E.g. allows specifying desired lists of subclasses,
        //  or to indicate a preference for non-nullable types over nullable types)
        // Affects analysis of return statements in the body of the method and the return types passed in by callers.
        'prefer_narrowed_phpdoc_return_type' => true,

        // Set to true in order to attempt to detect dead
        // (unreferenced) code. Keep in mind that the
        // results will only be a guess given that classes,
        // properties, constants and methods can be referenced
        // as variables (like `$class->$property` or
        // `$class->$method()`) in ways that we're unable
        // to make sense of.
        'dead_code_detection' => false,

        // Set to true in order to force tracking references to elements
        // (functions/methods/consts/protected).
        // dead_code_detection is another option which also causes references
        // to be tracked.
        'force_tracking_references' => false,

        // If true, the dead code detection rig will
        // prefer false negatives (not report dead code) to
        // false positives (report dead code that is not
        // actually dead) which is to say that the graph of
        // references will create too many edges rather than
        // too few edges when guesses have to be made about
        // what references what.
        'dead_code_detection_prefer_false_negative' => true,

        // If true, then before analysis, try to simplify AST into a form
        // which improves Phan's type inference in edge cases.
        //
        // This may conflict with 'dead_code_detection'.
        // When this is true, this slows down analysis slightly.
        //
        // E.g. rewrites `if ($a = value() && $a > 0) {...}`
        // into $a = value(); if ($a) { if ($a > 0) {...}}`
        'simplify_ast' => true,

        // If true, Phan will read `class_alias` calls in the global scope,
        // then (1) create aliases from the *parsed* files if no class definition was found,
        // and (2) emit issues in the global scope if the source or target class is invalid.
        // (If there are multiple possible valid original classes for an aliased class name,
        //  the one which will be created is unspecified.)
        // NOTE: THIS IS EXPERIMENTAL, and the implementation may change.
        'enable_class_alias_support' => false,

        // If disabled, Phan will not read docblock type
        // annotation comments for @property.
        // @property-read and @property-write are treated exactly the
        // same as @property for now.
        // Note: read_type_annotations must also be enabled.
        'read_magic_property_annotations' => true,

        // If disabled, Phan will not read docblock type
        // annotation comments for @method.
        // Note: read_type_annotations must also be enabled.
        'read_magic_method_annotations' => true,

        // If disabled, Phan will not read docblock type
        // annotation comments (such as for @return, @param,
        // @var, @suppress, @deprecated) and only rely on
        // types expressed in code.
        'read_type_annotations' => true,

        // This setting maps case insensitive strings to union types.
        // This is useful if a project uses phpdoc that differs from the phpdoc2 standard.
        // If the corresponding value is the empty string, Phan will ignore that union type (E.g. can ignore 'the' in `@return the value`)
        // If the corresponding value is not empty, Phan will act as though it saw the corresponding unionTypes(s) when the keys show up in a UnionType of @param, @return, @var, @property, etc.
        //
        // This matches the **entire string**, not parts of the string.
        // (E.g. `@return the|null` will still look for a class with the name `the`, but `@return the` will be ignored with the below setting)
        //
        // (These are not aliases, this setting is ignored outside of doc comments).
        // (Phan does not check if classes with these names exist)
        //
        // Example setting: ['unknown' => '', 'number' => 'int|float', 'char' => 'string', 'long' => 'int', 'the' => '']
        'phpdoc_type_mapping' => [ ],

        // Set to true in order to ignore issue suppression.
        // This is useful for testing the state of your code, but
        // unlikely to be useful outside of that.
        'disable_suppression' => false,

        // If set to true, we'll dump the AST instead of
        // analyzing files
        'dump_ast' => false,

        // If set to a string, we'll dump the fully qualified lowercase
        // function and method signatures instead of analyzing files.
        'dump_signatures_file' => null,

        // If set to true, we'll dump the list of files to parse
        // to stdout instead of parsing and analyzing files.
        'dump_parsed_file_list' => false,

        // Include a progress bar in the output
        'progress_bar' => false,

        // The probability of actually emitting any progress
        // bar update. Setting this to something very low
        // is good for reducing network IO and filling up
        // your terminal's buffer when running phan on a
        // remote host.
        // Set this to 0 to use *only* progress_bar_sample_interval.
        'progress_bar_sample_rate' => 0.000,

        // If this much time (in seconds) has passed since the last update,
        // then update the progress bar (Ignores progress_bar_sample_rate).
        // Set this to INF to only use progress_bar_sample_rate.
        'progress_bar_sample_interval' => 0.1,

        // The number of processes to fork off during the analysis
        // phase.
        'processes' => 1,

        // Set to true to emit profiling data on how long various
        // parts of Phan took to run. You likely don't care to do
        // this.
        'profiler_enabled' => false,

        // Add any issue types (such as 'PhanUndeclaredMethod')
        // to this black-list to inhibit them from being reported.
        'suppress_issue_types' => [
            // 'PhanUndeclaredMethod',
        ],

        // If empty, no filter against issues types will be applied.
        // If this white-list is non-empty, only issues within the list
        // will be emitted by Phan.
        'whitelist_issue_types' => [
            // 'PhanAccessClassConstantInternal',
            // 'PhanAccessClassConstantPrivate',
            // 'PhanAccessClassConstantProtected',
            // 'PhanAccessClassInternal',
            // 'PhanAccessConstantInternal',
            // 'PhanAccessMethodInternal',
            // 'PhanAccessMethodPrivate',
            // 'PhanAccessMethodPrivateWithCallMagicMethod',
            // 'PhanAccessMethodProtected',
            // 'PhanAccessMethodProtectedWithCallMagicMethod',
            // 'PhanAccessNonStaticToStatic',
            // 'PhanAccessOwnConstructor',
            // 'PhanAccessPropertyInternal',
            // 'PhanAccessPropertyPrivate',
            // 'PhanAccessPropertyProtected',
            // 'PhanAccessPropertyStaticAsNonStatic',
            // 'PhanAccessSignatureMismatch',
            // 'PhanAccessSignatureMismatchInternal',
            // 'PhanAccessStaticToNonStatic',
            // 'PhanClassContainsAbstractMethod',
            // 'PhanClassContainsAbstractMethodInternal',
            // 'PhanCommentParamOnEmptyParamList',
            // 'PhanCommentParamWithoutRealParam',
            // 'PhanCompatibleExpressionPHP7',
            // 'PhanCompatiblePHP7',
            // 'PhanContextNotObject',
            // 'PhanDeprecatedClass',
            // 'PhanDeprecatedFunction',
            // 'PhanDeprecatedFunctionInternal',
            // 'PhanDeprecatedInterface',
            // 'PhanDeprecatedProperty',
            // 'PhanDeprecatedTrait',
            // 'PhanEmptyFile',
            // 'PhanGenericConstructorTypes',
            // 'PhanGenericGlobalVariable',
            // 'PhanIncompatibleCompositionMethod',
            // 'PhanIncompatibleCompositionProp',
            // 'PhanInvalidCommentForDeclarationType',
            // 'PhanMismatchVariadicComment',
            // 'PhanMismatchVariadicParam',
            // 'PhanMisspelledAnnotation',
            // 'PhanNonClassMethodCall',
            // 'PhanNoopArray',
            // 'PhanNoopClosure',
            // 'PhanNoopConstant',
            // 'PhanNoopProperty',
            // 'PhanNoopVariable',
            // 'PhanParamRedefined',
            // 'PhanParamReqAfterOpt',
            // 'PhanParamSignatureMismatch',
            // 'PhanParamSignatureMismatchInternal',
            // 'PhanParamSignaturePHPDocMismatchHasNoParamType',
            // 'PhanParamSignaturePHPDocMismatchHasParamType',
            // 'PhanParamSignaturePHPDocMismatchParamIsNotReference',
            // 'PhanParamSignaturePHPDocMismatchParamIsReference',
            // 'PhanParamSignaturePHPDocMismatchParamNotVariadic',
            // 'PhanParamSignaturePHPDocMismatchParamType',
            // 'PhanParamSignaturePHPDocMismatchParamVariadic',
            // 'PhanParamSignaturePHPDocMismatchReturnType',
            // 'PhanParamSignaturePHPDocMismatchTooFewParameters',
            // 'PhanParamSignaturePHPDocMismatchTooManyRequiredParameters',
            // 'PhanParamSignatureRealMismatchHasNoParamType',
            // 'PhanParamSignatureRealMismatchHasNoParamTypeInternal',
            // 'PhanParamSignatureRealMismatchHasParamType',
            // 'PhanParamSignatureRealMismatchHasParamTypeInternal',
            // 'PhanParamSignatureRealMismatchParamIsNotReference',
            // 'PhanParamSignatureRealMismatchParamIsNotReferenceInternal',
            // 'PhanParamSignatureRealMismatchParamIsReference',
            // 'PhanParamSignatureRealMismatchParamIsReferenceInternal',
            // 'PhanParamSignatureRealMismatchParamNotVariadic',
            // 'PhanParamSignatureRealMismatchParamNotVariadicInternal',
            // 'PhanParamSignatureRealMismatchParamType',
            // 'PhanParamSignatureRealMismatchParamTypeInternal',
            // 'PhanParamSignatureRealMismatchParamVariadic',
            // 'PhanParamSignatureRealMismatchParamVariadicInternal',
            // 'PhanParamSignatureRealMismatchReturnType',
            // 'PhanParamSignatureRealMismatchReturnTypeInternal',
            // 'PhanParamSignatureRealMismatchTooFewParameters',
            // 'PhanParamSignatureRealMismatchTooFewParametersInternal',
            // 'PhanParamSignatureRealMismatchTooManyRequiredParameters',
            // 'PhanParamSignatureRealMismatchTooManyRequiredParametersInternal',
            // 'PhanParamSpecial1',
            // 'PhanParamSpecial2',
            // 'PhanParamSpecial3',
            // 'PhanParamSpecial4',
            // 'PhanParamTooFew',
            // 'PhanParamTooFewInternal',
            // 'PhanParamTooMany',
            // 'PhanParamTooManyInternal',
            // 'PhanParamTypeMismatch',
            // 'PhanParentlessClass',
            // 'PhanRedefineClass',
            // 'PhanRedefineClassAlias',
            // 'PhanRedefineClassInternal',
            // 'PhanRedefineFunction',
            // 'PhanRedefineFunctionInternal',
            // 'PhanRequiredTraitNotAdded',
            // 'PhanStaticCallToNonStatic',
            // 'PhanSyntaxError',
            // 'PhanTemplateTypeConstant',
            // 'PhanTemplateTypeStaticMethod',
            // 'PhanTemplateTypeStaticProperty',
            // 'PhanTraitParentReference',
            // 'PhanTypeArrayOperator',
            // 'PhanTypeArraySuspicious',
            // 'PhanTypeComparisonFromArray',
            // 'PhanTypeComparisonToArray',
            // 'PhanTypeConversionFromArray',
            // 'PhanTypeInstantiateAbstract',
            // 'PhanTypeInstantiateInterface',
            // 'PhanTypeInvalidClosureScope',
            // 'PhanTypeInvalidLeftOperand',
            // 'PhanTypeInvalidRightOperand',
            // 'PhanTypeMismatchArgument',
            // 'PhanTypeMismatchArgumentInternal',
            // 'PhanTypeMismatchDeclaredParam',
            // 'PhanTypeMismatchDeclaredReturn',
            // 'PhanTypeMismatchDefault',
            // 'PhanTypeMismatchForeach',
            // 'PhanTypeMismatchProperty',
            // 'PhanTypeMismatchReturn',
            // 'PhanTypeMissingReturn',
            // 'PhanTypeNonVarPassByRef',
            // 'PhanTypeParentConstructorCalled',
            // 'PhanTypeSuspiciousIndirectVariable',
            // 'PhanTypeVoidAssignment',
            // 'PhanUnanalyzable',
            // 'PhanUndeclaredAliasedMethodOfTrait',
            // 'PhanUndeclaredClass',
            // 'PhanUndeclaredClassAliasOriginal',
            // 'PhanUndeclaredClassCatch',
            // 'PhanUndeclaredClassConstant',
            // 'PhanUndeclaredClassInstanceof',
            // 'PhanUndeclaredClassMethod',
            // 'PhanUndeclaredClassReference',
            // 'PhanUndeclaredClosureScope',
            // 'PhanUndeclaredConstant',
            // 'PhanUndeclaredExtendedClass',
            // 'PhanUndeclaredFunction',
            // 'PhanUndeclaredInterface',
            // 'PhanUndeclaredMethod',
            // 'PhanUndeclaredProperty',
            // 'PhanUndeclaredStaticMethod',
            // 'PhanUndeclaredStaticProperty',
            // 'PhanUndeclaredTrait',
            // 'PhanUndeclaredTypeParameter',
            // 'PhanUndeclaredTypeProperty',
            // 'PhanUndeclaredTypeReturnType',
            // 'PhanUndeclaredVariable',
            // 'PhanUndeclaredVariableDim',
            // 'PhanUnextractableAnnotation',
            // 'PhanUnextractableAnnotationPart',
            // 'PhanUnreferencedClass',
            // 'PhanUnreferencedConstant',
            // 'PhanUnreferencedPublicClassConstant',
            // 'PhanUnreferencedProtectedClassConstant',
            // 'PhanUnreferencedPrivateClassConstant',
            // 'PhanUnreferencedPublicMethod',
            // 'PhanUnreferencedProtectedMethod',
            // 'PhanUnreferencedPrivateMethod',
            // 'PhanUnreferencedPublicMethod',
            // 'PhanUnreferencedPublicProperty',
            // 'PhanUnreferencedProtectedProperty',
            // 'PhanUnreferencedPublicProperty',
            // 'PhanVariableUseClause',
        ],

        // Override if runkit.superglobal ini directive is used.
        // A custom list of additional superglobals and their types, for projects using runkit.
        // (Corresponding keys are declared in runkit.superglobal ini directive)
        // global_type_map should be set for entries.
        // E.g ['_FOO'];
        'runkit_superglobals' => [],

        // Override to hardcode existence and types of (non-builtin) globals in the global scope.
        // Class names should be prefixed with '\\'.
        // (E.g. ['_FOO' => '\\FooClass', 'page' => '\\PageClass', 'userId' => 'int'])
        'globals_type_map' => [],

        // Emit issue messages with markdown formatting
        'markdown_issue_messages' => false,

        // Emit colorized issue messages.
        // NOTE: it is strongly recommended to enable this via the --color CLI flag instead,
        // since this is incompatible with most output formatters.
        'color_issue_messages' => false,

        // Allow overriding color scheme in .phan/config.php for printing issues, for individual types.
        // See the keys of Phan\Output\Colorizing::styles for valid color names,
        // and the keys of Phan\Output\Colorizing::default_color_for_template for valid color names.
        // E.g. to change the color for the file(of an issue instance) to red, set this to ['FILE' => 'red']
        // E.g. to use the terminal's default color for the line(of an issue instance), set this to ['LINE' => 'none']
        'color_scheme' => [],

        // Enable or disable support for generic templated
        // class types.
        'generic_types_enabled' => true,

        // Assign files to be analyzed on random processes
        // in random order. You very likely don't want to
        // set this to true. This is meant for debugging
        // and fuzz testing purposes only.
        'randomize_file_order' => false,

        // Setting this to true makes the process assignment for file analysis
        // as predictable as possible, using consistent hashing.
        // Even if files are added or removed, or process counts change,
        // relatively few files will move to a different group.
        // (use when the number of files is much larger than the process count)
        // NOTE: If you rely on Phan parsing files/directories in the order
        // that they were provided in this config, don't use this)
        // See https://github.com/phan/phan/wiki/Different-Issue-Sets-On-Different-Numbers-of-CPUs
        'consistent_hashing_file_order' => false,

        // Set by --print-memory-usage-summary. Prints a memory usage summary to stderr after analysis.
        'print_memory_usage_summary' => false,

        // By default, Phan will log error messages to stdout if PHP is using options that slow the analysis.
        // (e.g. PHP is compiled with --enable-debug or when using XDebug)
        'skip_slow_php_options_warning' => false,

        // You can put paths to stubs of internal extensions in this config option.
        // If the corresponding extension is **not** loaded, then phan will use the stubs instead.
        // Phan will continue using its detailed type annotations,
        // but load the constants, classes, functions, and classes (and their Reflection types)
        // from these stub files (doubling as valid php files).
        // Use a different extension from php to avoid accidentally loading these.
        // The 'tools/make_stubs' script can be used to generate your own stubs (compatible with php 7.0+ right now)
        'autoload_internal_extension_signatures' => [
            // 'xdebug' => '.phan/internal_stubs/xdebug.phan_php',
        ],

        // Set this to false to emit PhanUndeclaredFunction issues for internal functions that Phan has signatures for,
        // but aren't available in the codebase, or the internal functions used to run phan
        // (may lead to false positives if an extension isn't loaded)
        // If this is true(default), then Phan will not warn.
        'ignore_undeclared_functions_with_known_signatures' => true,

        // If a file to be analyzed can't be parsed,
        // then use a slower PHP substitute for php-ast to try to parse the files.
        // This setting is ignored if a file is excluded from analysis.
        // NOTE: it is strongly recommended to enable this via the --allow-polyfill-parser CLI flag instead,
        // since this may result in strange error messages for invalid files (e.g. if parsed but not analyzed).
        'use_fallback_parser' => false,

        // Use the polyfill parser based on tolerant-php-parser instead of the possibly missing native implementation
        // NOTE: This makes parsing several times slower than the native implementation.
        // NOTE: it is strongly recommended to enable this via the --use-polyfill-parser or --force-polyfill-parser
        // since this may result in strange error messages for invalid files (e.g. if parsed but not analyzed).
        'use_polyfill_parser' => false,

        // Path to a unix socket for a daemon to listen to files to analyze. Use command line option instead.
        'daemonize_socket' => false,

        // TCP port(from 1024 to 65535) for a daemon to listen to files to analyze. Use command line option instead.
        'daemonize_tcp_port' => false,

        // If this is an array, it configures the way clients will communicate with the Phan language server.
        // Possibilities: Exactly one of
        // ['stdin' => true],
        // ['tcp-server' => string (address this server should listen on)],
        // ['tcp' => string (address client is listening on)
        'language_server_config' => false,

        // Valid values: false, true. Should only be set via CLI (--language-server-analyze-only-on-save)
        'language_server_analyze_only_on_save' => false,

        // Valid values: null, 'info'. Used when developing or debugging a language server client of Phan.
        'language_server_debug_level' => null,

        // Use the command line option instead
        'language_server_use_pcntl_fallback' => false,

        // Can be set to false to disable the plugins Phan uses to infer more accurate return types of array_map, array_filter, etc.
        // Phan is slightly faster when these are disabled.
        'enable_internal_return_type_plugins' => true,

        // A list of plugin files to execute
        // Plugins which are bundled with Phan can be added here by providing their name (e.g. 'AlwaysReturnPlugin')
        // Alternately, you can pass in the full path to a PHP file with the plugin's implementation (e.g. 'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php')
        'plugins' => [
        ],

        // E.g. this is used by InvokePHPNativeSyntaxCheckPlugin
        'plugin_config' => [
        ],
    ];

    /**
     * Disallow the constructor to force a singleton
     */
    private function __construct()
    {
    }

    /**
     * @return string
     * Get the root directory of the project that we're
     * scanning
     * @suppress PhanPossiblyFalseTypeReturn getcwd() can technically be false, but we should have checked earlier
     */
    public static function getProjectRootDirectory() : string
    {
        return self::$project_root_directory ?? getcwd();
    }

    /**
     * @param string $project_root_directory
     * Set the root directory of the project that we're
     * scanning
     *
     * @return void
     */
    public static function setProjectRootDirectory(
        string $project_root_directory
    ) {
        self::$project_root_directory = $project_root_directory;
    }

    /**
     * @return Config
     * Get a Configuration singleton
     */
    public static function get() : Config
    {
        static $instance;

        if ($instance) {
            return $instance;
        }

        $instance = new Config();
        $instance->init();
        return $instance;
    }

    /**
     * @return void
     */
    private function init()
    {
        // Trigger magic setters
        foreach (self::$configuration as $name => $v) {
            self::setValue($name, $v);
        }
    }

    /**
     * @return array<string,mixed>
     * A map of configuration keys and their values
     */
    public function toArray() : array
    {
        return self::$configuration;
    }

    // @codingStandardsIgnoreStart method naming is deliberate to make these getters easier to search.

    public static function get_null_casts_as_any_type() : bool
    {
        return self::$null_casts_as_any_type;
    }

    public static function get_strict_param_checking() : bool
    {
        return self::$strict_param_checking;
    }

    public static function get_strict_property_checking() : bool
    {
        return self::$strict_property_checking;
    }

    public static function get_strict_return_checking() : bool
    {
        return self::$strict_return_checking;
    }

    public static function get_null_casts_as_array() : bool
    {
        return self::$null_casts_as_array;
    }

    public static function get_array_casts_as_null() : bool
    {
        return self::$array_casts_as_null;
    }

    public static function get_track_references() : bool
    {
        return self::$track_references;
    }

    public static function get_backward_compatibility_checks() : bool
    {
        return self::$backward_compatibility_checks;
    }

    public static function get_quick_mode() : bool
    {
        return self::$quick_mode;
    }

    public static function get_closest_target_php_version_id() : int
    {
        return self::$closest_target_php_version_id;
    }
    // @codingStandardsIgnoreEnd

    /**
     * @return mixed
     * @deprecated
     */
    public function __get(string $name)
    {
        return self::getValue($name);
    }

    /**
     * @return mixed
     */
    public static function getValue(string $name)
    {
        return self::$configuration[$name];
    }

    /**
     * @param string $name
     * @param mixed $value
     * @return void
     * @deprecated
     */
    public function __set(string $name, $value)
    {
        self::setValue($name, $value);
    }

    /**
     * @param string $name
     * @param mixed $value
     * @return void
     */
    public static function setValue(string $name, $value)
    {
        self::$configuration[$name] = $value;
        switch ($name) {
            case 'null_casts_as_any_type':
                self::$null_casts_as_any_type = $value;
                break;
            case 'null_casts_as_array':
                self::$null_casts_as_array = $value;
                break;
            case 'array_casts_as_null':
                self::$array_casts_as_null = $value;
                break;
            case 'strict_param_checking':
                self::$strict_param_checking = $value;
                break;
            case 'strict_property_checking':
                self::$strict_property_checking = $value;
                break;
            case 'strict_return_checking':
                self::$strict_return_checking = $value;
                break;
            case 'dead_code_detection':
            case 'force_tracking_references':
                self::$track_references = self::getValue('dead_code_detection') || self::getValue('force_tracking_references');
                break;
            case 'backward_compatibility_checks':
                self::$backward_compatibility_checks = $value;
                break;
            case 'quick_mode':
                self::$quick_mode = $value;
                break;
            case 'allow_method_param_type_widening':
                self::$configuration['allow_method_param_type_widening_original'] = $value;
                if ($value === null) {
                    // If this setting is set to null, infer it based on the closest php version id.
                    self::$configuration[$name] = self::$closest_target_php_version_id >= 70200;
                }
                break;
            case 'target_php_version':
                if (is_float($value)) {
                    $value = sprintf("%.1f", $value);
                }
                $value = (string) ($value ?: PHP_VERSION);
                if (strtolower($value) === 'native') {
                    $value = PHP_VERSION;
                }

                if (version_compare($value, '7.1') < 0) {
                    self::$closest_target_php_version_id = 70000;
                } elseif (version_compare($value, '7.2') < 0) {
                    self::$closest_target_php_version_id = 70100;
                } else {
                    self::$closest_target_php_version_id = 70200;
                }
                if ((self::$configuration['allow_method_param_type_widening_original'] ?? null) === null) {
                    self::$configuration['allow_method_param_type_widening'] = self::$closest_target_php_version_id >= 70200;
                }
                break;
        }
    }

    /**
     * @return string
     * The relative path appended to the project root directory.
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public static function projectPath(string $relative_path)
    {
        // Make sure its actually relative
        if (\DIRECTORY_SEPARATOR === \substr($relative_path, 0, 1)) {
            return $relative_path;
        }
        // Check for absolute path in windows, e.g. C:\
        if (\DIRECTORY_SEPARATOR === "\\" &&
                \strlen($relative_path) > 3 &&
                \ctype_alpha($relative_path[0]) &&
                $relative_path[1] === ':' &&
                \strspn($relative_path, '/\\', 2, 1)) {
            return $relative_path;
        }

        return Config::getProjectRootDirectory() . DIRECTORY_SEPARATOR .  $relative_path;
    }
}

// Call init() to trigger the magic setters.
Config::get();
<?php declare(strict_types=1);
namespace Phan\Daemon\Transport;

/**
 * Instead of sending the data over a stream,
 * this just keeps the raw array
 */
class CapturerResponder implements Responder
{
    /** @var array<string,mixed> the data for getRequestData() */
    private $request_data;

    /** @var ?array the data sent via sendAndClose */
    private $response_data;

    /** @param array<string,mixed> $data the data for getRequestData() */
    public function __construct(array $data)
    {
        $this->request_data = $data;
    }

    /**
     * @return array<string,mixed> the request data
     */
    public function getRequestData()
    {
        return $this->request_data;
    }

    /**
     * @param array<string,mixed> $data
     * @return void
     * @throws \RuntimeException if called twice
     */
    public function sendResponseAndClose(array $data)
    {
        if (is_array($this->response_data)) {
            throw new \RuntimeException("Called sendResponseAndClose twice: data = " . json_encode($data, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE));
        }
        $this->response_data = $data;
    }

    /**
     * @return ?array
     */
    public function getResponseData()
    {
        return $this->response_data;
    }
}
<?php declare(strict_types=1);
namespace Phan\Daemon\Transport;

use Phan\Daemon;

/**
 * Sends json encoded data over a socket stream.
 */
class StreamResponder implements Responder
{
    /** @var ?resource a stream */
    private $connection;

    /** @var ?array the request data */
    private $request_data;

    private $did_read_request_data = false;

    /** @param resource $connection a stream */
    public function __construct($connection, bool $expect_request)
    {
        \assert(is_resource($connection));
        $this->connection = $connection;
        if (!$expect_request) {
            $this->did_read_request_data = true;
            $this->request_data = [];
        }
    }

    /**
     * @return ?array the request data(E.g. returns null if JSON is malformed)
     */
    public function getRequestData()
    {
        if (!$this->did_read_request_data) {
            $response_connection = $this->connection;
            if (!$response_connection) {
                Daemon::debugf("Should not happen, missing a response connection");  // debugging code
                return null;
            }
            Daemon::debugf("Got a connection");  // debugging code
            $request_bytes = '';
            while (!feof($response_connection)) {
                $request_bytes .= fgets($response_connection);
            }
            $request = json_decode($request_bytes, true);
            if (!is_array($request)) {
                Daemon::debugf("Received invalid request, expected JSON: %s", json_encode($request_bytes, JSON_UNESCAPED_SLASHES));
                $request = null;
            }
            $this->did_read_request_data = true;
            $this->request_data = $request;
        }
        return $this->request_data;
    }

    /**
     * @return void
     * @throws \RuntimeException if called twice
     */
    public function sendResponseAndClose(array $data)
    {
        $connection = $this->connection;
        if (!$this->did_read_request_data) {
            throw new \RuntimeException("Called sendAndClose before calling getRequestData");
        }
        if ($connection === null) {
            throw new \RuntimeException("Called sendAndClose twice: data = " . json_encode($data, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE));
        }
        fwrite($connection, json_encode($data, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE) . "\n");
        // disable further receptions and transmissions
        // Note: This is likely a giant hack,
        // and pcntl and sockets may break in the future if used together. (multiple processes owning a single resource).
        // Not sure how to do that safely.
        stream_socket_shutdown($connection, STREAM_SHUT_RDWR);
        fclose($connection);
        $this->connection = null;
    }
}
<?php declare(strict_types=1);
namespace Phan\Daemon\Transport;

/**
 * This is an interface abstracting the transport which the worker process uses to send a response.
 *
 * StreamResponder is used for pcntl when this process is the fork
 * CapturerResponder is used when a single process is run
 */
interface Responder
{

    /**
     * @return ?array the request data(E.g. returns null if JSON is malformed)
     */
    function getRequestData();

    /**
     * This must be called exactly once
     * @return void
     */
    function sendResponseAndClose(array $data);
}
<?php declare(strict_types=1);
namespace Phan\Daemon;

class ExitException extends \Exception
{
}
<?php declare(strict_types=1);
namespace Phan\Daemon;

use Closure;
use Phan\Analysis;
use Phan\CodeBase;
use Phan\Config;
use Phan\Daemon;
use Phan\Daemon\Transport\Responder;
use Phan\Language\FileRef;
use Phan\Language\Type;
use Phan\LanguageServer\FileMapping;
use Phan\Library\FileCache;
use Phan\Output\IssuePrinterInterface;
use Phan\Output\PrinterFactory;
use Symfony\Component\Console\Output\BufferedOutput;

/**
 * Represents the state of a client request to a daemon, and contains methods for sending formatted responses.
 */
class Request
{
    const METHOD_ANALYZE_FILES = 'analyze_files';  // has shorthand analyze_file with param 'file'

    const PARAM_METHOD = 'method';
    const PARAM_FILES  = 'files';
    const PARAM_FORMAT = 'format';
    const PARAM_TEMPORARY_FILE_MAPPING_CONTENTS = 'temporary_file_mapping_contents';

    // success codes
    const STATUS_OK = 'ok';  // unrecognized output format
    const STATUS_NO_FILES = 'no_files';  // none of the requested files were in this project's config directories

    // failure codes
    const STATUS_INVALID_FORMAT = 'invalid_format';  // unrecognized requested output "format"
    const STATUS_ERROR_UNKNOWN = 'error_unknown';
    const STATUS_INVALID_FILES = 'invalid_files';  // expected a valid string for 'files'/'file'
    const STATUS_INVALID_METHOD = 'invalid_method';  // expected 'method' to be analyze_files or
    const STATUS_INVALID_REQUEST = 'invalid_request';  // expected a valid string for 'files'/'file'

    /** @var Responder|null - Null after the response is sent. */
    private $responder;

    /** @var array{method:string,files:array<int,string>,format:string,temporary_file_mapping_contents:array<string,string>} */
    private $config;

    /** @var BufferedOutput */
    private $buffered_output;

    /** @var string */
    private $method;

    /** @var array<int,string>|null */
    private $files = null;

    private static $child_pids = [];

    private static $exited_pid_status = [];

    /** @var bool */
    private $should_exit;

    /**
     * @param array{method:string,files:array<int,string>,format:string,temporary_file_mapping_contents:array<string,string>} $config
     */
    private function __construct(Responder $responder, array $config, bool $should_exit)
    {
        $this->responder = $responder;
        $this->config = $config;
        $this->buffered_output = new BufferedOutput();
        $this->method = $config[self::PARAM_METHOD];
        if ($this->method === self::METHOD_ANALYZE_FILES) {
            $this->files = $config[self::PARAM_FILES];
        }
        $this->should_exit = $should_exit;
    }

    public function exit(int $exit_code)
    {
        if ($this->should_exit) {
            Daemon::debugf("Exiting");
            exit($exit_code);
        }
        throw new ExitException("done", $exit_code);
    }

    /**
     * @param Responder $responder (e.g. a socket to write a response on)
     * @param array<int,string> $filenames absolute path of file(s) to analyze
     * @param CodeBase $code_base (for refreshing parse state)
     * @param Closure $file_path_lister (for refreshing parse state)
     * @param FileMapping $file_mapping object tracking the overrides made by a client.
     * @param bool $should_exit - If this is true, calling $this->exit() will terminate the program. If false, ExitException will be thrown.
     */
    public static function makeLanguageServerAnalysisRequest(
        Responder $responder,
        array $filenames,
        CodeBase $code_base,
        Closure $file_path_lister,
        FileMapping $file_mapping,
        bool $should_exit
    ) : Request {
        FileCache::clear();
        $file_mapping_contents = self::normalizeFileMappingContents($file_mapping->getOverrides(), $error_message);
        // Use the temporary contents if they're available
        Request::reloadFilePathListForDaemon($code_base, $file_path_lister, $file_mapping_contents);
        if ($error_message !== null) {
            Daemon::debugf($error_message);
        };
        $result = new self(
            $responder,
            [
                self::PARAM_FORMAT => 'json',
                self::PARAM_METHOD => self::METHOD_ANALYZE_FILES,
                self::PARAM_FILES => $filenames,
                self::PARAM_TEMPORARY_FILE_MAPPING_CONTENTS => $file_mapping_contents,
            ],
            $should_exit
        );
        return $result;
    }

    public function getPrinter() : IssuePrinterInterface
    {
        // TODO: check $this->config['format']
        $factory = new PrinterFactory();
        $format = $this->config['format'] ?? 'json';
        if (!in_array($format, $factory->getTypes())) {
            $this->sendJSONResponse([
                "status" => self::STATUS_INVALID_FORMAT,
            ]);
            exit(0);
        }
        return $factory->getPrinter($format, $this->buffered_output);
    }

    /**
     * Respond with issues in the requested format
     * @return void
     */
    public function respondWithIssues(int $issueCount)
    {
        $raw_issues = $this->buffered_output->fetch();
        if (($this->config[self::PARAM_FORMAT] ?? null) === 'json') {
            $issues = json_decode($raw_issues, true);
            if (!\is_array($issues)) {
                $issues = "(Failed to decode) " . json_last_error_msg() . ': ' . $raw_issues;
            }
        } else {
            $issues = $raw_issues;
        }
        $this->sendJSONResponse([
            "status" => self::STATUS_OK,
            "issue_count" => $issueCount,
            "issues" => $issues,
        ]);
    }

    /**
     * @return void
     */
    public function respondWithNoFilesToAnalyze()
    {
        // The mentioned file wasn't in .phan/config.php's list of files to analyze.
        $this->sendJSONResponse([
            "status" => self::STATUS_NO_FILES,
        ]);
    }


    /**
     * @param array<int,string> $analyze_file_path_list
     * @return array<int,string>
     */
    public function filterFilesToAnalyze(array $analyze_file_path_list) : array
    {
        if (\is_null($this->files)) {
            Daemon::debugf("No files to filter in filterFilesToAnalyze");
            return $analyze_file_path_list;
        }

        $analyze_file_path_set = array_flip($analyze_file_path_list);
        $filteredFiles = [];
        foreach ($this->files as $file) {
            // Must be relative to project, allow absolute paths to be passed in.
            $file = FileRef::getProjectRelativePathForPath($file);

            if (\array_key_exists($file, $analyze_file_path_set)) {
                $filteredFiles[] = $file;
            } else {
                // TODO: Reload file list once before processing request?
                // TODO: Change this to also support analyzing files that would normally be parsed but not analyzed?
                Daemon::debugf("Failed to find requested file '%s' in parsed file list", $file, json_encode($analyze_file_path_list));
            }
        }
        Daemon::debugf("Returning file set: %s", json_encode($filteredFiles));
        return $filteredFiles;
    }

    /**
     * TODO: convert absolute path to relative paths.
     * @return array<string,string> - Maps original relative file paths to contents.
     * @suppress PhanPartialTypeMismatchArgumentInternal
     */
    public function getTemporaryFileMapping() : array
    {
        $mapping = $this->config[self::PARAM_TEMPORARY_FILE_MAPPING_CONTENTS] ?? [];
        if (!is_array($mapping)) {
            $mapping = [];
        }
        Daemon::debugf("Have the following files in mapping: %s", json_encode(array_keys($mapping)));
        return $mapping;
    }

    /**
     * Send a response and close the connection, for the given socket's protocol.
     * Currently supports only JSON.
     * TODO: HTTP protocol.
     *
     * @param array<string,mixed> $response
     * @return void
     */
    public function sendJSONResponse(array $response)
    {
        $responder = $this->responder;
        if (!$responder) {
            Daemon::debugf("Already sent response");
            return;
        }
        $responder->sendResponseAndClose($response);
        $this->responder = null;
    }

    public function __destruct()
    {
        $responder = $this->responder;
        if ($responder) {
            $responder->sendResponseAndClose([
                'status' => self::STATUS_ERROR_UNKNOWN,
                'message' => 'failed to send a response - Possibly encountered an exception. See daemon output.' . json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)),
            ]);
            $this->responder = null;
        }
    }

    /**
     * @param int $signo
     * @param int|null $status
     * @param int|null $pid
     * @return void
     */
    public static function childSignalHandler($signo, $status = null, $pid = null)
    {
        if ($signo !== SIGCHLD) {
            return;
        }
        if (!$pid) {
            $pid = pcntl_waitpid(-1, $status, WNOHANG);
        }
        Daemon::debugf("Got signal pid=%s", json_encode($pid));

        while ($pid > 0) {
            if (\array_key_exists($pid, self::$child_pids)) {
                $exit_code = pcntl_wexitstatus($status);
                if ($exit_code != 0) {
                    error_log(sprintf("child process %d exited with status %d\n", $pid, $exit_code));
                } else {
                    Daemon::debugf("child process %d completed successfully", $pid);
                }
                unset(self::$child_pids[$pid]);
            } elseif ($pid > 0) {
                self::$exited_pid_status[$pid] = $status;
            }
            $pid = pcntl_waitpid(-1, $status, WNOHANG);
        }
    }

    /**
     * @param array<string,string> $file_mapping_contents
     * @param ?string &$error_message @phan-output-reference
     */
    public static function normalizeFileMappingContents($file_mapping_contents, &$error_message) : array
    {
        $error_message = null;
        if (!\is_array($file_mapping_contents)) {
            $error_message = 'Invalid value of temporary_file_mapping_contents';
        }
        $new_file_mapping_contents = [];
        foreach ($file_mapping_contents ?? [] as $file => $contents) {
            if (!\is_string($file)) {
                $error_message = 'Passed non-string in list of files to map';
                return [];
            } elseif (!\is_string($contents)) {
                $error_message = 'Passed non-string in as new file contents';
                return [];
            }
            $new_file_mapping_contents[FileRef::getProjectRelativePathForPath($file)] = $contents;
        }
        return $new_file_mapping_contents;
    }
    /**
     * @param CodeBase $code_base
     * @param \Closure $file_path_lister
     * @param Responder $responder
     * @return ?Request - non-null if this is a worker process with work to do. null if request failed or this is the master.
     * @suppress PhanPartialTypeMismatchArgument pre-existing
     */
    public static function accept(CodeBase $code_base, \Closure $file_path_lister, Responder $responder, bool $fork)
    {
        FileCache::clear();

        $request = $responder->getRequestData();

        if (!\is_array($request)) {
            $responder->sendResponseAndClose([
                'status'  => self::STATUS_INVALID_REQUEST,
                'message' => 'malformed JSON',
            ]);
            return null;
        }
        $new_file_mapping_contents = [];
        $method = $request['method'] ?? '';
        switch ($method) {
            case 'analyze_all':
                // Analyze the default list of files. No expected params.
                break;
            case 'analyze_file':
                $method = 'analyze_files';
                $request = [
                self::PARAM_METHOD => $method,
                self::PARAM_FILES => [$request['file']],
                self::PARAM_FORMAT => $request[self::PARAM_FORMAT] ?? 'json',
                ];
                // Fall through, this is an alias of analyze_files
            case 'analyze_files':
                // Analyze the list of strings provided in "files"
                // TODO: Actually do that.
                $files = $request[self::PARAM_FILES] ?? null;
                $request[self::PARAM_FORMAT] = $request[self::PARAM_FORMAT] ?? 'json';
                $error_message = null;
                if (\is_array($files) && count($files)) {
                    foreach ($files as $file) {
                        if (!\is_string($file)) {
                            $error_message = 'Passed non-string in list of files';
                            break;
                        }
                    }
                } else {
                    $error_message = 'Must pass a non-empty array of file paths for field files';
                }
                if (\is_null($error_message)) {
                    $file_mapping_contents = $request[self::PARAM_TEMPORARY_FILE_MAPPING_CONTENTS] ?? [];
                    if (is_array($file_mapping_contents)) {
                        $new_file_mapping_contents = self::normalizeFileMappingContents($file_mapping_contents, $error_message);
                        $request[self::PARAM_TEMPORARY_FILE_MAPPING_CONTENTS] = $new_file_mapping_contents;
                    } else {
                        $error_message = 'Must pass an optional array or null for temporary_file_mapping_contents';
                    }
                }
                if ($error_message !== null) {
                    Daemon::debugf($error_message);
                    $responder->sendResponseAndClose([
                        'status'  => self::STATUS_INVALID_FILES,
                        'message' => $error_message,
                    ]);
                    return null;
                }
                break;
                // TODO(optional): add APIs to resolve types of variables/properties/etc (e.g. accept byte offset or line/column offset)
            default:
                $message = sprintf("expected method to be analyze_all or analyze_files, got %s", json_encode($method));
                Daemon::debugf($message);
                $responder->sendResponseAndClose([
                    'status'  => self::STATUS_INVALID_METHOD,
                    'message' => $message,
                ]);
                return null;
        }

        // Re-parse the file list
        self::reloadFilePathListForDaemon($code_base, $file_path_lister, $new_file_mapping_contents);

        // Analyze the files that are open in the IDE (If pcntl is available, the analysis is done in a forked process)

        if (!$fork) {
            Daemon::debugf("This is the main process pretending to be the fork");
            self::$child_pids = [];
            // This is running on the only thread, so configure $request_obj to throw ExitException instead of calling exit()
            $request_obj = new self($responder, $request, false);
            $temporary_file_mapping = $request_obj->getTemporaryFileMapping();
            if (count($temporary_file_mapping) > 0) {
                self::applyTemporaryFileMappingForParsePhase($code_base, $temporary_file_mapping);
            }
            return $request_obj;
        }

        $fork_result = pcntl_fork();
        if ($fork_result < 0) {
            error_log("The daemon failed to fork. Going to terminate");
        } elseif ($fork_result == 0) {
            Daemon::debugf("This is the fork");
            self::$child_pids = [];
            $request_obj = new self($responder, $request, true);
            $temporary_file_mapping = $request_obj->getTemporaryFileMapping();
            if (count($temporary_file_mapping) > 0) {
                self::applyTemporaryFileMappingForParsePhase($code_base, $temporary_file_mapping);
            }
            return $request_obj;
        } else {
            $pid = $fork_result;
            $status = self::$exited_pid_status[$pid] ?? null;
            if (isset($status)) {
                Daemon::debugf("child process %d already exited", $pid);
                self::childSignalHandler(SIGCHLD, $status, $pid);
                unset(self::$exited_pid_status[$pid]);
            } else {
                self::$child_pids[$pid] = true;
            }

            // TODO: Use http://php.net/manual/en/book.inotify.php if available, watch all directories if available.
            // Daemon continues to execute.
            self::$child_pids[] = $fork_result;
            Daemon::debugf("Created a child pid %d", $fork_result);
        }
        return null;
    }

    /**
     * Reloads the file path list.
     * @return void
     */
    public static function reloadFilePathListForDaemon(CodeBase $code_base, \Closure $file_path_lister, array $file_mapping_contents)
    {
        $old_count = $code_base->getParsedFilePathCount();

        $file_list = $file_path_lister(true);

        if (Config::getValue('consistent_hashing_file_order')) {
            // Parse the files in lexicographic order.
            // If there are duplicate class/function definitions,
            // this ensures they are added to the maps in the same order.
            sort($file_list, SORT_STRING);
        }

        $changed_or_added_files = $code_base->updateFileList($file_list, $file_mapping_contents);
        // Daemon::debugf("Parsing modified files: New files = %s", json_encode($changed_or_added_files));
        if (count($changed_or_added_files) > 0 || $code_base->getParsedFilePathCount() !== $old_count) {
            // Only clear memoizations if it is determined at least one file to parse was added/removed/modified.
            // - file path count changes if files were deleted or added
            // - changed_or_added_files has an entry for every added/modified file.
            // (would be 0 if a client analyzes one file, then analyzes a different file)
            Type::clearAllMemoizations();
        }
        // A progress bar doesn't make sense in a daemon which can theoretically process multiple requests at once.
        foreach ($changed_or_added_files as $file_path) {
            // Kick out anything we read from the former version
            // of this file
            $code_base->flushDependenciesForFile($file_path);

            // If the file is gone, no need to continue
            $real = realpath($file_path);
            if ($real === false || !file_exists($real)) {
                Daemon::debugf("file $file_path does not exist");
                continue;
            }
            Daemon::debugf("Parsing %s yet again", $file_path);
            try {
                // Parse the file
                Analysis::parseFile($code_base, $file_path, false, $file_mapping_contents[$file_path] ?? null);
            } catch (\Throwable $throwable) {
                error_log(sprintf("Analysis::parseFile threw %s for %s: %s\n%s", get_class($throwable), $file_path, $throwable->getMessage(), $throwable->getTraceAsString()));
            }
        }
        Daemon::debugf("Done parsing modified files");
    }

    /**
     * Substitutes files. We assume that the original file path exists already, and reject it if it doesn't.
     * (i.e. it was returned by $file_path_lister in the past)
     *
     * @param array<string,string> $temporary_file_mapping_contents
     * @return void
     */
    private static function applyTemporaryFileMappingForParsePhase(CodeBase $code_base, array $temporary_file_mapping_contents)
    {
        if (count($temporary_file_mapping_contents) === 0) {
            return;
        }

        // too verbose
        Daemon::debugf("Parsing temporary file mapping contents: New contents = %s", json_encode($temporary_file_mapping_contents));

        $changes_to_add = [];
        foreach ($temporary_file_mapping_contents as $file_name => $contents) {
            if ($code_base->beforeReplaceFileContents($file_name)) {
                $changes_to_add[$file_name] = $contents;
            }
        }
        Daemon::debugf("Done setting temporary file contents: Will replace contents of the following files: %s", json_encode(array_keys($changes_to_add)));
        if (count($changes_to_add) === 0) {
            return;
        }
        Type::clearAllMemoizations();

        foreach ($changes_to_add as $file_path => $new_contents) {
            // Kick out anything we read from the former version
            // of this file
            $code_base->flushDependenciesForFile($file_path);

            // If the file is gone, no need to continue
            $real = realpath($file_path);
            if ($real === false || !file_exists($real)) {
                Daemon::debugf("file $file_path no longer exists on disk, but we tried to replace it?");
                continue;
            }
            Daemon::debugf("Parsing temporary file instead of %s", $file_path);
            try {
                // Parse the file
                Analysis::parseFile($code_base, $file_path, false, $new_contents);
            } catch (\Throwable $throwable) {
                error_log(sprintf("Analysis::parseFile threw %s for %s: %s\n%s", get_class($throwable), $file_path, $throwable->getMessage(), $throwable->getTraceAsString()));
            }
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\Plugin;

use Phan\AST\Visitor\Element;

/**
 * Tracks the closures that need to be executed for the set of possible \ast\Node->kind values.
 * Once the plugin set is done adding closures, this will return an array
 * mapping the node kind to a single Closure to execute
 * (which will run all of the plugins)
 *
 * - If no closures were added for a given node kind, then there will be no entry in that array.
 */
class ClosuresForKind
{
    /**
     * @var array<int,array<int,\Closure>> Maps a node kind to a list of 1 or more (unflattened) closures to execute on nodes of that kind.
     */
    private $closures = [];

    public function __construct()
    {
    }

    /**
     * @param int $kind - A valid value of a node kind
     * @param \Closure $c
     */
    public function record(int $kind, \Closure $c)
    {
        \assert(\array_key_exists($kind, Element::VISIT_LOOKUP_TABLE));
        if (!isset($this->closures[$kind])) {
            $this->closures[$kind] = [];
        }
        $this->closures[$kind][] = $c;
    }

    /**
     * @param array<int,int> $kinds - A list of unique values of node kinds
     * @param \Closure $c - The closure to execute on each of those kinds
     *
     * Record the fact that a Closure needs to be the given subset of values of node->kind
     */
    public function recordForKinds(array $kinds, \Closure $c)
    {
        foreach ($kinds as $kind) {
            $this->record($kind, $c);
        }
    }

    /**
     * Record the fact that a Closure needs to be executed for all possible valid values of Node->kind
     * @param \Closure $c - The closure to execute on all valid values of Node->kind
     */
    public function recordForAllKinds(\Closure $c)
    {
        $this->recordForKinds(\array_keys(Element::VISIT_LOOKUP_TABLE), $c);
    }

    /**
     * @param \Closure $flattener
     * @return array<int,\Closure> (Maps a subset of node kinds to a closure to execute for that node kind.)
     * @suppress PhanPartialTypeMismatchReturn TODO: investigate
     */
    public function getFlattenedClosures(\Closure $flattener)
    {
        \ksort($this->closures);
        $merged_closures = [];
        foreach ($this->closures as $kind => $closure_list) {
            \assert(\count($closure_list) > 0);
            if (\count($closure_list) === 1) {
                // If there's exactly one closure for a given kind, then execute it directly.
                $merged_closures[$kind] = $closure_list[0];
            } else {
                // Create a closure which will execute 2 or more closures.
                $closure = $flattener($closure_list);
                \assert($closure instanceof \Closure);
                $merged_closures[$kind] = $closure;
            }
        }
        return $merged_closures;
    }
}
<?php declare(strict_types=1);
namespace Phan\Plugin;

use Phan\AST\Visitor\Element;
use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Func;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Method;
use Phan\Language\Element\Property;
use Phan\Plugin;
use Phan\Plugin\Internal\ArrayReturnTypeOverridePlugin;
use Phan\Plugin\Internal\CallableParamPlugin;
use Phan\Plugin\Internal\CompactPlugin;
use Phan\Plugin\Internal\ClosureReturnTypeOverridePlugin;
use Phan\Plugin\Internal\DependentReturnTypeOverridePlugin;
use Phan\Plugin\Internal\MiscParamPlugin;
use Phan\Plugin\Internal\StringFunctionPlugin;
use Phan\Plugin\PluginImplementation;
use Phan\PluginV2;
use Phan\PluginV2\AfterAnalyzeFileCapability;
use Phan\PluginV2\AnalyzeClassCapability;
use Phan\PluginV2\AnalyzeFunctionCallCapability;
use Phan\PluginV2\AnalyzeFunctionCapability;
use Phan\PluginV2\AnalyzeMethodCapability;
use Phan\PluginV2\AnalyzeNodeCapability;
use Phan\PluginV2\AnalyzePropertyCapability;
use Phan\PluginV2\BeforeAnalyzeFileCapability;
use Phan\PluginV2\FinalizeProcessCapability;
use Phan\PluginV2\LegacyAnalyzeNodeCapability;
use Phan\PluginV2\LegacyPostAnalyzeNodeCapability;
use Phan\PluginV2\LegacyPreAnalyzeNodeCapability;
use Phan\PluginV2\PluginAwareAnalysisVisitor;
use Phan\PluginV2\PluginAwarePostAnalysisVisitor;
use Phan\PluginV2\PluginAwarePreAnalysisVisitor;
use Phan\PluginV2\PostAnalyzeNodeCapability;
use Phan\PluginV2\PreAnalyzeNodeCapability;
use Phan\PluginV2\ReturnTypeOverrideCapability;

use ast\Closure;
use ast\Node;
use ReflectionException;
use ReflectionProperty;

/**
 * The root plugin that calls out each hook
 * on any plugins defined in the configuration.
 *
 * (Note: This is called almost once per each AST node being analyzed.
 * Speed is preferred over using Phan\Memoize.)
 */
final class ConfigPluginSet extends PluginV2 implements
    AfterAnalyzeFileCapability,
    AnalyzeClassCapability,
    AnalyzeFunctionCapability,
    AnalyzeFunctionCallCapability,
    AnalyzeMethodCapability,
    AnalyzePropertyCapability,
    BeforeAnalyzeFileCapability,
    FinalizeProcessCapability,
    LegacyPreAnalyzeNodeCapability,
    LegacyPostAnalyzeNodeCapability,
    ReturnTypeOverrideCapability
{

    /** @var array<int,Plugin>|null - Cached plugin set for this instance. Lazily generated. */
    private $pluginSet;

    /**
     * @var array<int,Closure>|null - plugins to analyze nodes in pre order.
     * @phan-var array<int,Closure(CodeBase,Context,Node):void>|null
     */
    private $preAnalyzeNodePluginSet;

    /**
     * @var array<int,Closure> - plugins to analyze files
     * @phan-var array<int,Closure(string,Node):void>|null
     */
    private $postAnalyzeNodePluginSet;

    /**
     * @var array<int,BeforeAnalyzeFileCapability> - plugins to analyze files before phan's analysis of that file is completed.
     */
    private $beforeAnalyzeFilePluginSet;

    /**
     * @var array<int,AfterAnalyzeFileCapability> - plugins to analyze files after phan's analysis of that file is completed.
     */
    private $afterAnalyzeFilePluginSet;

    /** @var array<int,AnalyzeClassCapability>|null - plugins to analyze class declarations. */
    private $analyzeClassPluginSet;

    /** @var array<int,AnalyzeFunctionCallCapability>|null - plugins to analyze invocations of subsets of functions and methods. */
    private $analyzeFunctionCallPluginSet;

    /** @var array<int,AnalyzeFunctionCapability>|null - plugins to analyze function declarations. */
    private $analyzeFunctionPluginSet;

    /** @var array<int,AnalyzePropertyCapability>|null - plugins to analyze property declarations. */
    private $analyzePropertyPluginSet;

    /** @var array<int,AnalyzeMethodCapability>|null - plugins to analyze method declarations.*/
    private $analyzeMethodPluginSet;

    /** @var array<int,FinalizeProcessCapability>|null - plugins to call finalize() on after analysis is finished. */
    private $finalizeProcessPluginSet;

    /** @var array<int,ReturnTypeOverrideCapability>|null - plugins which generate return UnionTypes of functions based on arguments. */
    private $returnTypeOverridePluginSet;

    /**
     * Call `ConfigPluginSet::instance()` instead.
     */
    private function __construct()
    {
    }

    /**
     * @return ConfigPluginSet
     * A shared single instance of this plugin
     * @suppress PhanDeprecatedInterface
     */
    public static function instance() : ConfigPluginSet
    {
        static $instance = null;
        if ($instance === null) {
            $instance = new self;
            $instance->ensurePluginsExist();
        }
        return $instance;
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * The context in which the node exits. This is
     * the context inside the given node rather than
     * the context outside of the given node
     *
     * @param Node $node
     * The php-ast Node being analyzed.
     *
     * @return void
     * @override
     */
    public function preAnalyzeNode(
        CodeBase $code_base,
        Context $context,
        Node $node
    ) {
        $plugin_callback = $this->preAnalyzeNodePluginSet[$node->kind] ?? null;
        if ($plugin_callback !== null) {
            $plugin_callback(
                $code_base,
                $context,
                $node
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * The context in which the node exits. This is
     * the context inside the given node rather than
     * the context outside of the given node
     *
     * @param Node $node
     * The php-ast Node being analyzed.
     *
     * @param array<int,Node> $parent_node_list
     * The parent node of the given node (if one exists).
     *
     * @return void
     * @override
     */
    public function postAnalyzeNode(
        CodeBase $code_base,
        Context $context,
        Node $node,
        array $parent_node_list = []
    ) {
        $plugin_callback = $this->postAnalyzeNodePluginSet[$node->kind] ?? null;
        if ($plugin_callback !== null) {
            $plugin_callback(
                $code_base,
                $context,
                $node,
                $parent_node_list
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * A context with the file name for $file_contents and the scope before analyzing $node.
     *
     * @param string $file_contents
     * @param Node $node
     * @return void
     * @override
     */
    public function beforeAnalyzeFile(
        CodeBase $code_base,
        Context $context,
        string $file_contents,
        Node $node
    ) {
        foreach ($this->beforeAnalyzeFilePluginSet as $plugin) {
            $plugin->beforeAnalyzeFile(
                $code_base,
                $context,
                $file_contents,
                $node
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * A context with the file name for $file_contents and the scope after analyzing $node.
     *
     * @param string $file_contents
     * @param Node $node
     * @return void
     * @override
     */
    public function afterAnalyzeFile(
        CodeBase $code_base,
        Context $context,
        string $file_contents,
        Node $node
    ) {
        foreach ($this->afterAnalyzeFilePluginSet as $plugin) {
            $plugin->afterAnalyzeFile(
                $code_base,
                $context,
                $file_contents,
                $node
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the class exists
     *
     * @param Clazz $class
     * A class being analyzed
     *
     * @return void
     * @override
     */
    public function analyzeClass(
        CodeBase $code_base,
        Clazz $class
    ) {
        foreach ($this->analyzeClassPluginSet as $plugin) {
            $plugin->analyzeClass(
                $code_base,
                $class
            );
        }
        if ($this->hasAnalyzePropertyPlugins()) {
            foreach ($class->getPropertyMap($code_base) as $property) {
                $this->analyzeProperty($code_base, $property);
            }
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the method exists
     *
     * @param Method $method
     * A method being analyzed
     *
     * @return void
     * @override
     */
    public function analyzeMethod(
        CodeBase $code_base,
        Method $method
    ) {
        foreach ($this->analyzeMethodPluginSet as $plugin) {
            $plugin->analyzeMethod(
                $code_base,
                $method
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the function exists
     *
     * @param Func $function
     * A function being analyzed
     *
     * @return void
     * @override
     */
    public function analyzeFunction(
        CodeBase $code_base,
        Func $function
    ) {
        foreach ($this->analyzeFunctionPluginSet as $plugin) {
            $plugin->analyzeFunction(
                $code_base,
                $function
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the property exists
     *
     * @param Property $property
     * A property being analyzed
     *
     * (Called by analyzeClass())
     *
     * @return void
     * @override
     */
    public function analyzeProperty(
        CodeBase $code_base,
        Property $property
    ) {
        foreach ($this->analyzePropertyPluginSet as $plugin) {
            try {
                $plugin->analyzeProperty(
                    $code_base,
                    $property
                );
            } catch (IssueException $exception) {
                // e.g. getUnionType() can throw, PropertyTypesAnalyzer is probably emitting duplicate issues
                Issue::maybeEmitInstance(
                    $code_base,
                    $property->getContext(),
                    $exception->getIssueInstance()
                );
                continue;
            }
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base used for previous analysis steps
     *
     * @return void
     * @override
     */
    public function finalizeProcess(
        CodeBase $code_base
    ) {
        foreach ($this->finalizeProcessPluginSet as $plugin) {
            $plugin->finalizeProcess($code_base);
        }
    }

    /**
     * Returns true if analyzeFunction() will execute any plugins.
     */
    public function hasAnalyzeFunctionPlugins() : bool
    {
        \assert(!\is_null($this->pluginSet));
        return \count($this->analyzeFunctionPluginSet) > 0;
    }

    /**
     * Returns true if analyzeMethod() will execute any plugins.
     */
    public function hasAnalyzeMethodPlugins() : bool
    {
        \assert(!\is_null($this->pluginSet));
        return \count($this->analyzeMethodPluginSet) > 0;
    }

    /**
     * @return array<string,\Closure> maps FQSEN string to closure
     */
    public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array
    {
        $result = [];
        \assert(!\is_null($this->pluginSet));
        foreach ($this->analyzeFunctionCallPluginSet as $plugin) {
            // TODO: Make this case insensitive.
            foreach ($plugin->getAnalyzeFunctionCallClosures($code_base) as $fqsen_name => $closure) {
                $other_closure = $result[$fqsen_name] ?? null;
                if ($other_closure !== null) {
                    $old_closure = $closure;
                    $closure = static function (CodeBase $code_base, Context $context, FunctionInterface $func, array $args) use ($old_closure, $other_closure) {
                        $other_closure($code_base, $context, $func, $args);
                        $old_closure($code_base, $context, $func, $args);
                    };
                }
                $result[$fqsen_name] = $closure;
            }
        }
        return $result;
    }

    /**
     * @return array<string,\Closure> maps FQSEN string to closure
     */
    public function getReturnTypeOverrides(CodeBase $code_base) : array
    {
        $result = [];
        \assert(!\is_null($this->pluginSet));
        foreach ($this->returnTypeOverridePluginSet as $plugin) {
            $result += $plugin->getReturnTypeOverrides($code_base);
        }
        return $result;
    }

    /**
     * Returns true if analyzeProperty() will execute any plugins.
     */
    private function hasAnalyzePropertyPlugins() : bool
    {
        \assert(!\is_null($this->pluginSet));
        return \count($this->analyzePropertyPluginSet) > 0;
    }

    /**
     * @return void
     * @suppress PhanPartialTypeMismatchProperty
     */
    private function ensurePluginsExist()
    {
        if (!\is_null($this->pluginSet)) {
            return;
        }
        // Add user-defined plugins.
        $plugin_set = array_map(
            function (string $plugin_file_name) : PluginV2 {
                // Allow any word/UTF-8 identifier as a php file name.
                // E.g. 'AlwaysReturnPlugin' becomes /path/to/phan/.phan/plugins/AlwaysReturnPlugin.php
                // (Useful when using phan.phar, etc.)
                if (\preg_match('@^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$@', $plugin_file_name) > 0) {
                    $plugin_file_name = __DIR__ . '/../../../.phan/plugins/' . $plugin_file_name . '.php';
                }

                $plugin_instance =
                    require($plugin_file_name);

                \assert(
                    !empty($plugin_instance),
                    "Plugins must return an instance of the plugin. The plugin at $plugin_file_name does not."
                );

                \assert(
                    $plugin_instance instanceof PluginV2,
                    "Plugins must extend \Phan\PluginV2. The plugin at $plugin_file_name does not."
                );

                return $plugin_instance;
            },
            Config::getValue('plugins')
        );
        // Add internal plugins. Can be disabled by disable_internal_return_type_plugins.
        if (Config::getValue('enable_internal_return_type_plugins')) {
            $internal_return_type_plugins = [
                new ArrayReturnTypeOverridePlugin(),
                new CallableParamPlugin(),
                new CompactPlugin(),
                new ClosureReturnTypeOverridePlugin(),
                new DependentReturnTypeOverridePlugin(),
                new StringFunctionPlugin(),
                new MiscParamPlugin(),
            ];
            $plugin_set = array_merge($internal_return_type_plugins, $plugin_set);
        }

        // Register the entire set.
        $this->pluginSet = $plugin_set;

        $this->preAnalyzeNodePluginSet      = self::filterPreAnalysisPlugins($plugin_set);
        $this->postAnalyzeNodePluginSet     = self::filterPostAnalysisPlugins($plugin_set);
        $this->beforeAnalyzeFilePluginSet   = self::filterByClass($plugin_set, BeforeAnalyzeFileCapability::class);
        $this->afterAnalyzeFilePluginSet    = self::filterByClass($plugin_set, AfterAnalyzeFileCapability::class);
        $this->analyzeMethodPluginSet       = self::filterOutEmptyMethodBodies(self::filterByClass($plugin_set, AnalyzeMethodCapability::class), 'analyzeMethod');
        $this->analyzeFunctionPluginSet     = self::filterOutEmptyMethodBodies(self::filterByClass($plugin_set, AnalyzeFunctionCapability::class), 'analyzeFunction');
        $this->analyzePropertyPluginSet     = self::filterOutEmptyMethodBodies(self::filterByClass($plugin_set, AnalyzePropertyCapability::class), 'analyzeProperty');
        $this->analyzeClassPluginSet        = self::filterOutEmptyMethodBodies(self::filterByClass($plugin_set, AnalyzeClassCapability::class), 'analyzeClass');
        $this->finalizeProcessPluginSet     = self::filterOutEmptyMethodBodies(self::filterByClass($plugin_set, FinalizeProcessCapability::class), 'finalizeProcess');
        $this->returnTypeOverridePluginSet  = self::filterByClass($plugin_set, ReturnTypeOverrideCapability::class);
        $this->analyzeFunctionCallPluginSet = self::filterByClass($plugin_set, AnalyzeFunctionCallCapability::class);
    }

    /**
     * @param array<int,PluginV2> $plugin_set
     * @return array<int,mixed> (TODO: Can't precisely annotate without improved (at)template analysis)
     */
    private static function filterOutEmptyMethodBodies(array $plugin_set, string $method_name) : array
    {
        return \array_values(\array_filter($plugin_set, function (PluginV2 $plugin) use ($method_name) : bool {
            if ($plugin instanceof PluginImplementation) {
                if (!$plugin->isDefinedInSubclass($method_name)) {
                    // PluginImplementation defines empty method bodies for each of the plugin $method_names
                    // Don't execute $method_name for a plugin during analysis if the subclass didn't override the implementation for $method_name.
                    return false;
                }
            }
            return true;
        }));
    }

    /**
     * @return array<int,Closure>
     *         Returned value maps ast\Node->kind to [function(CodeBase $code_base, Context $context, Node $node, array<int,Node> $parent_node_list = []): void]
     * @phan-return array<int,Closure(CodeBase,Context,Node,array<int,Node>=):void>
     */
    private static function filterPreAnalysisPlugins(array $plugin_set) : array
    {
        $closures_for_kind = new ClosuresForKind();
        foreach ($plugin_set as $plugin) {
            if ($plugin instanceof LegacyPreAnalyzeNodeCapability) {
                if ($plugin instanceof PreAnalyzeNodeCapability) {
                    throw new \TypeError(sprintf("plugin %s should implement only one of LegacyPreAnalyzeNodeCapability and PreAnalyzeNodeCapability, not both", get_class($plugin)));
                }
                if ($plugin instanceof PluginImplementation) {
                    if (!$plugin->isDefinedInSubclass('preAnalyzeNode')) {
                        continue;
                    }
                }
                $closure = (new \ReflectionMethod($plugin, 'preAnalyzeNode'))->getClosure($plugin);
                $closures_for_kind->recordForAllKinds($closure);
            } elseif ($plugin instanceof PreAnalyzeNodeCapability) {
                $plugin_analysis_class = $plugin->getPreAnalyzeNodeVisitorClassName();
                if (!\is_subclass_of($plugin_analysis_class, PluginAwarePreAnalysisVisitor::class)) {
                    throw new \TypeError(
                        sprintf(
                            "Result of %s::getAnalyzeNodeVisitorClassName must be the name of a subclass of '%s', but '%s' is not",
                            get_class($plugin),
                            PluginAwarePreAnalysisVisitor::class,
                            $plugin_analysis_class
                        )
                    );
                }
                /**
                 * Create an instance of $plugin_analysis_class and run the visit*() method corresponding to $node->kind.
                 *
                 * @phan-closure-scope PluginAwarePreAnalysisVisitor
                 */
                $closure = (static function (CodeBase $code_base, Context $context, Node $node) {
                    $fn_name = Element::VISIT_LOOKUP_TABLE[$node->kind];
                    return (new static($code_base, $context))->{$fn_name}($node);
                })->bindTo(null, $plugin_analysis_class);
                $handled_node_kinds = $plugin_analysis_class::getHandledNodeKinds();
                if (\count($handled_node_kinds) === 0) {
                    fprintf(
                        STDERR,
                        "Plugin %s has an preAnalyzeNode visitor %s (subclass of %s) which doesn't override any known visit<Suffix>() methods, but expected at least one method to be overridden\n",
                        get_class($plugin),
                        $plugin_analysis_class,
                        PluginAwarePreAnalysisVisitor::class
                    );
                }
                $closures_for_kind->recordForKinds($handled_node_kinds, $closure);
            }
        }
        return $closures_for_kind->getFlattenedClosures(static function (array $closure_list) : \Closure {
            return static function (CodeBase $code_base, Context $context, Node $node) use ($closure_list) {
                foreach ($closure_list as $closure) {
                    $closure($code_base, $context, $node);
                }
            };
        });
    }

    /**
     * @return array<int,\Closure> - [function(CodeBase $code_base, Context $context, Node $node, array<int,Node> $parent_node_list = []): void]
     */
    private static function filterPostAnalysisPlugins(array $plugin_set) : array
    {
        $closures_for_kind = new ClosuresForKind();
        foreach ($plugin_set as $plugin) {
            $implemented_count = 0;
            if ($plugin instanceof LegacyAnalyzeNodeCapability) {
                $implemented_count++;
            }
            if ($plugin instanceof AnalyzeNodeCapability) {
                $implemented_count++;
            }
            if ($plugin instanceof PostAnalyzeNodeCapability) {
                $implemented_count++;
            }
            if ($plugin instanceof LegacyPostAnalyzeNodeCapability) {
                $implemented_count++;
            }
            if ($implemented_count > 1) {
                throw new \TypeError(
                    sprintf(
                        "plugin %s should implement only one of LegacyAnalyzeNodeCapability, AnalyzeNodeCapability, LegacyPostAnalyzeNodeCapability, or PostAnalyzeNodeCapability. PostAnalyzeNodeCapability is preferred.",
                        get_class($plugin)
                    )
                );
            }
            // TODO: Get rid of LegacyAnalyzeNodeCapability and AnalyzeNodeCapability.
            if ($plugin instanceof LegacyAnalyzeNodeCapability) {
                if ($plugin instanceof PluginImplementation) {
                    if (!$plugin->isDefinedInSubclass('analyzeNode')) {
                        continue;
                    }
                }
                $closure = (new \ReflectionMethod($plugin, 'analyzeNode'))->getClosure($plugin);
                $closures_for_kind->recordForAllKinds(function (CodeBase $code_base, Context $context, Node $node, array $parent_node_list) use ($closure) {
                    $closure($code_base, $context, $node, \end($parent_node_list) ?: null);
                });
            } elseif ($plugin instanceof LegacyPostAnalyzeNodeCapability) {
                if ($plugin instanceof PluginImplementation) {
                    if (!$plugin->isDefinedInSubclass('analyzeNode')) {
                        continue;
                    }
                }
                $closure = (new \ReflectionMethod($plugin, 'analyzeNode'))->getClosure($plugin);
                $closures_for_kind->recordForAllKinds($closure);
            } elseif ($plugin instanceof AnalyzeNodeCapability) {
                $plugin_analysis_class = $plugin->getAnalyzeNodeVisitorClassName();
                if (!\is_subclass_of($plugin_analysis_class, PluginAwareAnalysisVisitor::class)) {
                    throw new \TypeError(
                        sprintf(
                            "Result of %s::getAnalyzeNodeVisitorClassName must be the name of a subclass of '%s', but '%s' is not",
                            get_class($plugin),
                            PluginAwareAnalysisVisitor::class,
                            $plugin_analysis_class
                        )
                    );
                }
                /**
                 * Create an instance of $plugin_analysis_class and run the visit*() method corresponding to $node->kind.
                 *
                 * @suppress PhanParamTooMany
                 * @suppress PhanUndeclaredProperty
                 * @suppress PhanDeprecatedInterface (TODO: Fix bugs in PhanClosureScope)
                 */
                $closure = (static function (CodeBase $code_base, Context $context, Node $node, array $parent_node_list = []) {
                    $visitor = new static($code_base, $context);
                    $visitor->parent_node = \end($parent_node_list) ?: null;
                    $fn_name = Element::VISIT_LOOKUP_TABLE[$node->kind];
                    $visitor->{$fn_name}($node);
                })->bindTo(null, $plugin_analysis_class);

                $handled_node_kinds = $plugin_analysis_class::getHandledNodeKinds();
                if (\count($handled_node_kinds) === 0) {
                    fprintf(
                        STDERR,
                        "Plugin %s has an analyzeNode visitor %s (subclass of %s) which doesn't override any known visit<Suffix>() methods, but expected at least one method to be overridden\n",
                        get_class($plugin),
                        $plugin_analysis_class,
                        PluginAwareAnalysisVisitor::class
                    );
                }
                $closures_for_kind->recordForKinds($handled_node_kinds, $closure);
            } elseif ($plugin instanceof PostAnalyzeNodeCapability) {
                $plugin_analysis_class = $plugin->getPostAnalyzeNodeVisitorClassName();
                if (!\is_subclass_of($plugin_analysis_class, PluginAwarePostAnalysisVisitor::class)) {
                    throw new \TypeError(
                        sprintf(
                            "Result of %s::getAnalyzeNodeVisitorClassName must be the name of a subclass of '%s', but '%s' is not",
                            get_class($plugin),
                            PluginAwarePostAnalysisVisitor::class,
                            $plugin_analysis_class
                        )
                    );
                }

                // @see PostAnalyzeNodeCapability (magic to create parent_node_list)
                try {
                    new ReflectionProperty($plugin_analysis_class, 'parent_node_list');
                    $has_parent_node_list = true;
                } catch (ReflectionException $e) {
                    $has_parent_node_list = false;
                }

                if ($has_parent_node_list) {
                    /**
                     * Create an instance of $plugin_analysis_class and run the visit*() method corresponding to $node->kind.
                     *
                     * @suppress PhanParamTooMany
                     * @suppress PhanUndeclaredProperty
                     * @suppress PhanDeprecatedInterface (TODO: Fix bugs in PhanClosureScope)
                     */
                    $closure = (static function (CodeBase $code_base, Context $context, Node $node, array $parent_node_list = []) {
                        $visitor = new static($code_base, $context);
                        $visitor->parent_node_list = $parent_node_list;
                        $fn_name = Element::VISIT_LOOKUP_TABLE[$node->kind];
                        $visitor->{$fn_name}($node);
                    })->bindTo(null, $plugin_analysis_class);
                } else {
                    /**
                     * Create an instance of $plugin_analysis_class and run the visit*() method corresponding to $node->kind.
                     *
                     * @suppress PhanParamTooMany
                     * @suppress PhanTypeInstantiateInterface
                     * @suppress PhanDeprecatedInterface (TODO: Fix bugs in PhanClosureScope)
                     * @phan-closure-scope PostAnalyzeNodeCapability
                     */
                    $closure = (static function (CodeBase $code_base, Context $context, Node $node, array $unused_parent_node_list = []) {
                        $visitor = new static($code_base, $context);
                        $fn_name = Element::VISIT_LOOKUP_TABLE[$node->kind];
                        $visitor->{$fn_name}($node);
                    })->bindTo(null, $plugin_analysis_class);
                }

                $handled_node_kinds = $plugin_analysis_class::getHandledNodeKinds();
                if (\count($handled_node_kinds) === 0) {
                    fprintf(
                        STDERR,
                        "Plugin %s has an analyzeNode visitor %s (subclass of %s) which doesn't override any known visit<Suffix>() methods, but expected at least one method to be overridden\n",
                        get_class($plugin),
                        $plugin_analysis_class,
                        PluginAwarePostAnalysisVisitor::class
                    );
                }
                $closures_for_kind->recordForKinds($handled_node_kinds, $closure);
            }
        }
        return $closures_for_kind->getFlattenedClosures(static function (array $closure_list) : \Closure {
            return static function (CodeBase $code_base, Context $context, Node $node, array $parent_node_list = []) use ($closure_list) {
                foreach ($closure_list as $closure) {
                    $closure($code_base, $context, $node, $parent_node_list);
                }
            };
        });
    }

    private static function filterByClass(array $plugin_set, string $interface_name) : array
    {
        $result = [];
        foreach ($plugin_set as $plugin) {
            if ($plugin instanceof $interface_name) {
                $result[] = $plugin;
            }
        }
        return $result;
    }
}
<?php declare(strict_types=1);
namespace Phan\Plugin\Internal;

use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\CallableType;
use Phan\Language\Type\StringType;
use Phan\Language\UnionType;
use Phan\PluginV2\AnalyzeFunctionCallCapability;
use Phan\PluginV2\StopParamAnalysisException;
use Phan\PluginV2;
use Closure;

/**
 * NOTE: This is automatically loaded by phan. Do not include it in a config.
 *
 * TODO: Analyze returning callables (function() : callable) for any callables that are returned as literals?
 * This would be difficult.
 */
final class MiscParamPlugin extends PluginV2 implements
    AnalyzeFunctionCallCapability
{
    /**
     * @return array<string,Closure>
     * @phan-return array<string,Closure(CodeBase,Context,FunctionInterface,array):void>
     */
    private function getAnalyzeFunctionCallClosuresStatic(CodeBase $code_base) : array
    {
        $stop_exception = new StopParamAnalysisException();

        /**
         * @return void
         */
        $min_max_callback = function (
            CodeBase $code_base,
            Context $context,
            FunctionInterface $function,
            array $args
        ) {
            if (\count($args) !== 1) {
                return;
            }
            self::analyzeNodeUnionTypeCast(
                $args[0],
                $context,
                $code_base,
                ArrayType::instance(false)->asUnionType(),
                function (UnionType $node_type) use ($context, $function) {
                // "arg#1(values) is %s but {$function->getFQSEN()}() takes array when passed only one arg"
                    return Issue::fromType(Issue::ParamSpecial2)(
                        $context->getFile(),
                        $context->getLineNumberStart(),
                        [
                        1,
                        'values',
                        (string)$node_type,
                        $function->getName(),
                        'array'
                        ]
                    );
                }
            );
        };
        /**
         * @return void
         */
        $array_udiff_callback = function (
            CodeBase $code_base,
            Context $context,
            FunctionInterface $function,
            array $args
        ) {
            $argcount = \count($args);
            if ($argcount < 3) {
                return;
            }
            self::analyzeNodeUnionTypeCast(
                $args[$argcount - 1],
                $context,
                $code_base,
                CallableType::instance(false)->asUnionType(),
                function (UnionType $unused_node_type) use ($context, $function) {
                    // "The last argument to {$function->getFQSEN()} must be a callable"
                    return Issue::fromType(Issue::ParamSpecial3)(
                        $context->getFile(),
                        $context->getLineNumberStart(),
                        [
                        (string)$function->getFQSEN(),
                        'callable'
                        ]
                    );
                }
            );

            for ($i=0; $i < ($argcount - 1); $i++) {
                self::analyzeNodeUnionTypeCast(
                    $args[$i],
                    $context,
                    $code_base,
                    ArrayType::instance(false)->asUnionType(),
                    function (UnionType $node_type) use ($context, $function, $i) {
                        // "arg#".($i+1)." is %s but {$function->getFQSEN()}() takes array"
                        return Issue::fromType(Issue::ParamTypeMismatch)(
                            $context->getFile(),
                            $context->getLineNumberStart(),
                            [
                            ($i+1),
                            (string)$node_type,
                            (string)$function->getFQSEN(),
                            'array'
                            ]
                        );
                    }
                );
            }
        };
        /**
         * @return void
         */
        $join_callback = function (
            CodeBase $code_base,
            Context $context,
            FunctionInterface $function,
            array $args
        ) use ($stop_exception) {
            $argcount = \count($args);
            // (string glue, array pieces),
            // (array pieces, string glue) or
            // (array pieces)
            if ($argcount == 1) {
                self::analyzeNodeUnionTypeCast(
                    $args[0],
                    $context,
                    $code_base,
                    ArrayType::instance(false)->asUnionType(),
                    function (UnionType $unused_node_type) use ($context, $function) {
                        // "arg#1(pieces) is %s but {$function->getFQSEN()}() takes array when passed only 1 arg"
                        return Issue::fromType(Issue::ParamSpecial2)(
                            $context->getFile(),
                            $context->getLineNumberStart(),
                            [
                                1,
                                'pieces',
                                (string)$function->getFQSEN(),
                                'string',
                                'array'
                            ]
                        );
                    }
                );
            } elseif ($argcount == 2) {
                $arg1_type = UnionTypeVisitor::unionTypeFromNode(
                    $code_base,
                    $context,
                    $args[0]
                );

                $arg2_type = UnionTypeVisitor::unionTypeFromNode(
                    $code_base,
                    $context,
                    $args[1]
                );

                // TODO: better array checks
                if ($arg1_type->isExclusivelyArray()) {
                    if (!$arg2_type->canCastToUnionType(
                        StringType::instance(false)->asUnionType()
                    )) {
                        Issue::maybeEmit(
                            $code_base,
                            $context,
                            Issue::ParamSpecial1,
                            $context->getLineNumberStart(),
                            2,
                            'glue',
                            (string)$arg2_type,
                            (string)$function->getFQSEN(),
                            'string',
                            1,
                            'array'
                        );
                    }
                    throw $stop_exception;
                } elseif ($arg1_type->isType(StringType::instance(false))) {
                    if (!$arg2_type->canCastToUnionType(
                        ArrayType::instance(false)->asUnionType()
                    )) {
                        Issue::maybeEmit(
                            $code_base,
                            $context,
                            Issue::ParamSpecial1,
                            $context->getLineNumberStart(),
                            2,
                            'pieces',
                            (string)$arg2_type,
                            (string)$function->getFQSEN(),
                            'array',
                            1,
                            'string'
                        );
                    }
                }
            }
        };
        /**
         * @return void
         */
        $array_uintersect_uassoc_callback = function (
            CodeBase $code_base,
            Context $context,
            FunctionInterface $function,
            array $args
        ) {
            $argcount = \count($args);
            if ($argcount < 4) {
                return;
            }

            // The last 2 arguments must be a callable and there
            // can be a variable number of arrays before it
            self::analyzeNodeUnionTypeCast(
                $args[$argcount - 1],
                $context,
                $code_base,
                CallableType::instance(false)->asUnionType(),
                function (UnionType $unused_node_type) use ($context, $function) {
                    // "The last argument to {$function->getFQSEN()} must be a callable"
                    return Issue::fromType(Issue::ParamSpecial3)(
                        $context->getFile(),
                        $context->getLineNumberStart(),
                        [
                        (string)$function->getFQSEN(),
                        'callable'
                        ]
                    );
                }
            );

            self::analyzeNodeUnionTypeCast(
                $args[$argcount - 2],
                $context,
                $code_base,
                CallableType::instance(false)->asUnionType(),
                function (UnionType $unused_node_type) use ($context, $function) {
                    // "The second last argument to {$function->getFQSEN()} must be a callable"
                    return Issue::fromType(Issue::ParamSpecial4)(
                        $context->getFile(),
                        $context->getLineNumberStart(),
                        [
                        (string)$function->getFQSEN(),
                        'callable'
                        ]
                    );
                }
            );

            for ($i=0; $i < ($argcount-2); $i++) {
                self::analyzeNodeUnionTypeCast(
                    $args[$i],
                    $context,
                    $code_base,
                    ArrayType::instance(false)->asUnionType(),
                    function (UnionType $node_type) use ($context, $function, $i) {
                    // "arg#".($i+1)." is %s but {$function->getFQSEN()}() takes array"
                        return Issue::fromType(Issue::ParamTypeMismatch)(
                            $context->getFile(),
                            $context->getLineNumberStart(),
                            [
                            ($i+1),
                            (string)$node_type,
                            (string)$function->getFQSEN(),
                            'array'
                            ]
                        );
                    }
                );
            }
        };

        return [
            'array_udiff' => $array_udiff_callback,
            'array_diff_uassoc' => $array_udiff_callback,
            'array_uintersect_assoc' => $array_udiff_callback,
            'array_intersect_ukey' => $array_udiff_callback,

            'array_uintersect_uassoc' => $array_uintersect_uassoc_callback,

            'join' => $join_callback,
            'implode' => $join_callback,

            'min' => $min_max_callback,
            'max' => $min_max_callback,

        ];
    }

    /**
     * @return array<string,Closure>
     * @phan-return array<string,Closure(CodeBase,Context,FunctionInterface,array):void>
     */
    public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array
    {
        // Unit tests invoke this repeatedly. Cache it.
        static $analyzers = null;
        if ($analyzers === null) {
            $analyzers = self::getAnalyzeFunctionCallClosuresStatic($code_base);
        }
        return $analyzers;
    }

    private static function analyzeNodeUnionTypeCast(
        $node,
        Context $context,
        CodeBase $code_base,
        UnionType $cast_type,
        \Closure $issue_instance
    ) : bool {

        // Get the type of the node
        $node_type = UnionTypeVisitor::unionTypeFromNode(
            $code_base,
            $context,
            $node,
            true
        );

        // See if it can be cast to the given type
        $can_cast = $node_type->canCastToUnionType(
            $cast_type
        );

        // If it can't, emit the log message
        if (!$can_cast) {
            Issue::maybeEmitInstance(
                $code_base,
                $context,
                $issue_instance($node_type)
            );
        }

        return $can_cast;
    }
}
<?php declare(strict_types=1);
namespace Phan\Plugin\Internal;

use Phan\CodeBase;
use Phan\AST\ContextNode;
use Phan\AST\UnionTypeVisitor;
use Phan\Language\Context;
use Phan\Language\Element\Func;
use Phan\Language\Type\StringType;
use Phan\Language\Type\TrueType;
use Phan\Language\Type\VoidType;
use Phan\Language\UnionType;
use Phan\PluginV2;
use Phan\PluginV2\ReturnTypeOverrideCapability;
use Closure;

/**
 * NOTE: This is automatically loaded by phan. Do not include it in a config.
 *
 * This internal plugin will warn if calls to internal string functions
 * or regex function appear to have arguments in the wrong order,
 *
 * e.g. explode($var, ':') or strpos(':', $x)
 */
final class DependentReturnTypeOverridePlugin extends PluginV2 implements
    ReturnTypeOverrideCapability
{
    /**
     * @return array<string,\Closure>
     * @phan-return array<string, Closure(CodeBase,Context,Func,array):UnionType>
     */
    private static function getReturnTypeOverridesStatic(CodeBase $code_base) : array
    {
        $string_union_type = StringType::instance(false)->asUnionType();
        $true_union_type = TrueType::instance(false)->asUnionType();
        $string_or_true_union_type = $string_union_type->withUnionType($true_union_type);
        $void_union_type = VoidType::instance(false)->asUnionType();
        $nullable_string_union_type = StringType::instance(true)->asUnionType();

        /**
         * @phan-return Closure(CodeBase,Context,Func,array):UnionType
         */
        $make_dependent_type_method = static function (int $expected_bool_pos, $type_if_true, $type_if_false, $type_if_unknown) : Closure {
            /**
             * @return UnionType
             * @suppress PhanPluginUnusedClosureArgument
             */
            return static function (
                CodeBase $code_base,
                Context $context,
                Func $function,
                array $args
            ) use (
                $type_if_true,
                $type_if_unknown,
                $type_if_false,
                $expected_bool_pos
) : UnionType {
                if (\count($args) <= $expected_bool_pos) {
                    return $type_if_false;
                }
                $result = (new ContextNode($code_base, $context, $args[$expected_bool_pos]))->getEquivalentPHPScalarValue();
                if (\is_int($result)) {
                    $result = (bool)$result;
                }
                if ($result === true) {
                    return $type_if_true;
                } elseif ($result === false) {
                    return $type_if_false;
                } else {
                    // unable to determine.
                    return $type_if_unknown;
                }
            };
        };

        $json_decode_array_types = UnionType::fromFullyQualifiedString('array|string|float|int|bool|null');
        $json_decode_object_types = UnionType::fromFullyQualifiedString('\stdClass|array<int,mixed>|string|float|int|bool|null');
        $json_decode_array_or_object_types = UnionType::fromFullyQualifiedString('\stdClass|array|string|float|int|bool|null');

        $string_if_2_true           = $make_dependent_type_method(1, $string_union_type, $void_union_type, $nullable_string_union_type);
        $string_if_2_true_else_true = $make_dependent_type_method(1, $string_union_type, $true_union_type, $string_or_true_union_type);

        /**
         * @return UnionType
         * @suppress PhanPluginUnusedClosureArgument
         */
        $json_decode_return_type_handler = static function (
            CodeBase $code_base,
            Context $context,
            Func $function,
            array $args
        ) use (
            $json_decode_array_types,
            $json_decode_object_types,
            $json_decode_array_or_object_types
) : UnionType {
            //  mixed json_decode ( string $json [, bool $assoc = FALSE [, int $depth = 512 [, int $options = 0 ]]] )
            //  $options can include JSON_OBJECT_AS_ARRAY in a bitmask
            // TODO: reject `...` operator? (Low priority)
            if (\count($args) < 2) {
                return $json_decode_object_types;
            }
            $result = (new ContextNode($code_base, $context, $args[1]))->getEquivalentPHPScalarValue();
            if (\is_int($result)) {
                // We are already warning about the param type. E.g. var_export($arg, 1) returns a string
                $result = (bool)$result;
            }
            if ($result === true) {
                return $json_decode_array_types;
            }
            if ($result !== false) {
                return $json_decode_array_or_object_types;
            }
            if (\count($args) < 4) {
                return $json_decode_object_types;
            }
            $options_result = (new ContextNode($code_base, $context, $args[3]))->getEquivalentPHPScalarValue();
            if (!\is_int($options_result)) {
                // unable to resolve value. TODO: Support bitmask operators in getEquivalentPHPScalarValue
                return $json_decode_array_or_object_types;
            }
            return ($options_result & JSON_OBJECT_AS_ARRAY) !== 0 ? $json_decode_array_types : $json_decode_object_types;
        };

        $str_replace_types = UnionType::fromFullyQualifiedString('string|string[]');
        $str_array_type = UnionType::fromFullyQualifiedString('string[]');

        $third_argument_string_or_array_handler = static function (
            CodeBase $code_base,
            Context $context,
            Func $unused_function,
            array $args
        ) use (
            $string_union_type,
            $str_replace_types,
            $str_array_type
        ) : UnionType {
            //  mixed json_decode ( string $json [, bool $assoc = FALSE [, int $depth = 512 [, int $options = 0 ]]] )
            //  $options can include JSON_OBJECT_AS_ARRAY in a bitmask
            // TODO: reject `...` operator? (Low priority)
            if (\count($args) < 3) {
                return $str_replace_types;
            }
            $union_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[2]);
            $has_array = $union_type->hasArray();
            if ($union_type->canCastToUnionType($string_union_type)) {
                return $has_array ? $str_replace_types : $string_union_type;
            }
            return $has_array ? $str_array_type : $str_replace_types;
        };

        return [
            // commonly used functions where the return type depends on a passed in boolean
            'var_export'                => $string_if_2_true,
            'print_r'                   => $string_if_2_true_else_true,
            'json_decode'               => $json_decode_return_type_handler,
            // Functions with dependent return types
            'str_replace'                 => $third_argument_string_or_array_handler,
            'preg_replace'                => $third_argument_string_or_array_handler,
            'preg_replace_callback'       => $third_argument_string_or_array_handler,
            'preg_replace_callback_array' => $third_argument_string_or_array_handler,
        ];
    }

    /**
     * @return array<string,\Closure>
     * @override
     */
    public function getReturnTypeOverrides(CodeBase $code_base) : array
    {
        // Unit tests invoke this repeatedly. Cache it.
        static $overrides = null;
        if ($overrides === null) {
            $overrides = self::getReturnTypeOverridesStatic($code_base);
        }
        return $overrides;
    }
}
<?php declare(strict_types=1);
namespace Phan\Plugin\Internal;

use Phan\CodeBase;
use Phan\Analysis\ArgumentType;
use Phan\Analysis\PostOrderAnalysisVisitor;
use Phan\AST\UnionTypeVisitor;
use Phan\Config;
use Phan\Language\Context;
use Phan\Language\Element\Func;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Method;
use Phan\Language\Type\ClosureType;
use Phan\Language\Type;
use Phan\Language\UnionType;
use Phan\PluginV2;
use Phan\PluginV2\AnalyzeFunctionCallCapability;
use Phan\PluginV2\ReturnTypeOverrideCapability;
use ast\Node;
use Closure;

/**
 * NOTE: This is automatically loaded by phan. Do not include it in a config.
 *
 * TODO: Refactor this.
 */
final class ClosureReturnTypeOverridePlugin extends PluginV2 implements
    AnalyzeFunctionCallCapability,
    ReturnTypeOverrideCapability
{

    /**
     * @param Node|int|string $arg_array_node
     * @return ?array
     */
    private static function extractArrayArgs($arg_array_node)
    {
        if (($arg_array_node instanceof Node) && $arg_array_node->kind === \ast\AST_ARRAY) {
            $arguments = [];
            // TODO: Sanity check keys.
            foreach ($arg_array_node->children as $child) {
                $arguments[] = $child->children['value'];
            }
            return $arguments;
        } else {
            return null;
        }
    }

    /**
     * @return array<string,\Closure>
     */
    private static function getReturnTypeOverridesStatic(CodeBase $code_base) : array
    {
        $call_user_func_callback = static function (
            CodeBase $code_base,
            Context $context,
            Func $unused_function,
            array $args
        ) : UnionType {
            $element_types = UnionType::empty();
            if (\count($args) < 1) {
                return $element_types;
            }
            $function_like_list = UnionTypeVisitor::functionLikeListFromNodeAndContext($code_base, $context, $args[0], true);
            if (\count($function_like_list) === 0) {
                return $element_types;
            }
            foreach ($function_like_list as $function_like) {
                if ($function_like->hasDependentReturnType()) {
                    $element_types = $element_types->withUnionType($function_like->getDependentReturnType($code_base, $context, \array_slice($args, 1)));
                } else {
                    $element_types = $element_types->withUnionType($function_like->getUnionType());
                }
            }
            if (Config::get_track_references()) {
                foreach ($function_like_list as $function_like) {
                    $function_like->addReference($context);
                }
            }
            return $element_types;
        };
        $call_user_func_array_callback = static function (
            CodeBase $code_base,
            Context $context,
            Func $unused_function,
            array $args
        ) : UnionType {
            $element_types = UnionType::empty();
            if (\count($args) < 2) {
                return $element_types;
            }
            // Currently, only analyze calls of the form call_user_func_array(callable expression, [$arg1, $arg2...])
            $function_like_list = UnionTypeVisitor::functionLikeListFromNodeAndContext($code_base, $context, $args[0], true);
            if (\count($function_like_list) === 0) {
                return $element_types;
            }
            $arguments = self::extractArrayArgs($args[1]);
            $element_types = UnionType::empty();

            foreach ($function_like_list as $function_like) {
                if ($arguments !== null && $function_like->hasDependentReturnType()) {
                    $element_types = $element_types->withUnionType($function_like->getDependentReturnType($code_base, $context, $arguments));
                } else {
                    $element_types = $element_types->withUnionType($function_like->getUnionType());
                }
            }
            if (Config::get_track_references()) {
                foreach ($function_like_list as $function_like) {
                    $function_like->addReference($context);
                }
            }
            if ($arguments !== null) {
                self::analyzeFunctionAndNormalArgumentList($code_base, $context, $function_like_list, $arguments);
            }
            return $element_types;
        };
        $from_callable_callback = static function (
            CodeBase $code_base,
            Context $context,
            Method $unused_method,
            array $args
        ) : UnionType {
            if (\count($args) < 1) {
                return ClosureType::instance(false)->asUnionType();
            }
            $function_like_list = UnionTypeVisitor::functionLikeListFromNodeAndContext($code_base, $context, $args[0], true);
            if (\count($function_like_list) === 0) {
                return ClosureType::instance(false)->asUnionType();
            }
            $closure_types = UnionType::empty();
            foreach ($function_like_list as $function_like) {
                $closure_types = $closure_types->withType(ClosureType::instanceWithClosureFQSEN($function_like->getFQSEN(), $function_like));
            }
            return $closure_types;
        };
        $from_closure_callback = static function (
            CodeBase $code_base,
            Context $context,
            Method $unused_method,
            array $args
        ) : UnionType {
            if (\count($args) < 1) {
                return ClosureType::instance(false)->asUnionType();
            }
            $types = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[0], true);
            $types = $types->makeFromFilter(function (Type $type) : bool {
                if ($type instanceof ClosureType) {
                    return $type->hasKnownFQSEN();
                }
                return false;
            });

            if ($types->isEmpty()) {
                return ClosureType::instance(false)->asUnionType();
            }
            return $types;
        };
        return [
            // call
            'call_user_func'            => $call_user_func_callback,
            'forward_static_call'       => $call_user_func_callback,
            'call_user_func_array'      => $call_user_func_array_callback,
            'forward_static_call_array' => $call_user_func_array_callback,
            'Closure::fromCallable'     => $from_callable_callback,
            'Closure::bind'             => $from_closure_callback,
        ];
    }

    /**
     * @return array<string,\Closure>
     */
    private static function getAnalyzeFunctionCallClosuresStatic(CodeBase $code_base) : array
    {
        /**
         * @return void
         */
        $call_user_func_callback = static function (
            CodeBase $code_base,
            Context $context,
            Func $unused_function,
            array $args
        ) {
            if (\count($args) < 1) {
                return;
            }
            $function_like_list = UnionTypeVisitor::functionLikeListFromNodeAndContext($code_base, $context, $args[0], true);
            if (\count($function_like_list) === 0) {
                return;
            }
            $arguments = \array_slice($args, 1);
            self::analyzeFunctionAndNormalArgumentList($code_base, $context, $function_like_list, $arguments);
        };

        /**
         * @return void
         */
        $call_user_func_array_callback = static function (
            CodeBase $code_base,
            Context $context,
            Func $unused_function,
            array $args
        ) {
            if (\count($args) < 2) {
                return;
            }
            // Currently, only analyze calls of the form call_user_func_array(callable expression, [$arg1, $arg2...])
            $function_like_list = UnionTypeVisitor::functionLikeListFromNodeAndContext($code_base, $context, $args[0], true);
            if (\count($function_like_list) === 0) {
                return;
            }
            $arguments = self::extractArrayArgs($args[1] ?? null);
            if ($arguments === null) {
                return;
            }

            self::analyzeFunctionAndNormalArgumentList($code_base, $context, $function_like_list, $arguments);
        };
        return [
            // call
            'call_user_func'            => $call_user_func_callback,
            'forward_static_call'       => $call_user_func_callback,
            'call_user_func_array'      => $call_user_func_array_callback,
            'forward_static_call_array' => $call_user_func_array_callback,
        ];
    }

    /**
     * @return array<string,\Closure>
     * @override
     */
    public function getReturnTypeOverrides(CodeBase $code_base) : array
    {
        // Unit tests invoke this repeatedly. Cache it.
        static $overrides = null;
        if ($overrides === null) {
            $overrides = self::getReturnTypeOverridesStatic($code_base);
        }
        return $overrides;
    }

    /**
     * @return array<string,\Closure>
     * @override
     */
    public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array
    {
        // Unit tests invoke this repeatedly. Cache it.
        static $analyzers = null;
        if ($analyzers === null) {
            $analyzers = self::getAnalyzeFunctionCallClosuresStatic($code_base);
        }
        return $analyzers;
    }

    /**
     * @phan-return Closure(mixed,int):UnionType
     */
    public static function createNormalArgumentCache(CodeBase $code_base, Context $context) : Closure
    {
        $cache = [];
        return function ($argument, int $i) use ($code_base, $context, &$cache) : UnionType {
            $argument_type = $cache[$i] ?? null;
            if (isset($argument_type)) {
                return $argument_type;
            }
            $argument_type = UnionTypeVisitor::unionTypeFromNode(
                $code_base,
                $context,
                $argument,
                true
            );
            $cache[$i] = $argument_type;
            return $argument_type;
        };
    }

    /**
     * Analyze a function which is called with the un-transformed types from $arguments.
     *
     * @param CodeBase $code_base
     * @param Context $context
     * @param array<int,FunctionInterface> $function_like_list
     * @param array<int,Node|string|int|float> $arguments
     *
     * @return void
     */
    private static function analyzeFunctionAndNormalArgumentList(CodeBase $code_base, Context $context, array $function_like_list, array $arguments)
    {
        $get_argument_type = self::createNormalArgumentCache($code_base, $context);
        foreach ($function_like_list as $function_like) {
            ArgumentType::analyzeForCallback($function_like, $arguments, $context, $code_base, $get_argument_type);
        }
        if (Config::get_quick_mode()) {
            // Keep it fast, don't recurse.
            return;
        }

        $argument_types = [];
        foreach ($arguments as $i => $argument) {
            $argument_types[] = $get_argument_type($argument, $i);
        }
        $analyzer = new PostOrderAnalysisVisitor($code_base, $context, []);
        foreach ($function_like_list as $function_like) {
            $analyzer->analyzeCallableWithArgumentTypes($argument_types, $function_like);
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\Plugin\Internal;

use Phan\CodeBase;
use Phan\AST\UnionTypeVisitor;
use Phan\Config;
use Phan\Language\Context;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Type\CallableType;
use Phan\Language\Type\CallableDeclarationType;
use Phan\Language\Type;
use Phan\PluginV2\AnalyzeFunctionCallCapability;
use Phan\PluginV2;
use Closure;

/**
 * NOTE: This is automatically loaded by phan. Do not include it in a config.
 *
 * TODO: Analyze returning callables (function() : callable) for any callables that are returned as literals?
 * This would be difficult.
 */
final class CallableParamPlugin extends PluginV2 implements
    AnalyzeFunctionCallCapability
{

    /**
     * @param array<int,int> $params
     * @phan-return Closure(CodeBase,Context,FunctionInterface,array):void
     */
    private static function generateClosure(array $params) : Closure
    {
        $key = \json_encode($params);
        static $cache = [];
        $closure = $cache[$key] ?? null;
        if ($closure !== null) {
            return $closure;
        }
        $closure = function (
            CodeBase $code_base,
            Context $context,
            FunctionInterface $function,
            array $args
        ) use ($params) {
            // TODO: Implement support for variadic callable arguments.
            foreach ($params as $i) {
                $arg = $args[$i] ?? null;

                // Fetch possible functions. As a side effect, this warns about invalid callables.
                // TODO: Check if the signature allows non-array callables? Not sure of desired semantics.
                $function_like_list = UnionTypeVisitor::functionLikeListFromNodeAndContext($code_base, $context, $arg, true);
                if (\count($function_like_list) === 0) {
                    // Nothing to do
                    return;
                }

                if (Config::get_track_references()) {
                    foreach ($function_like_list as $function) {
                        $function->addReference($context);
                    }
                }
                // self::analyzeFunctionAndNormalArgumentList($code_base, $context, $function_like_list, $arguments);
            }
        };

        $cache[$key] = $closure;
        return $closure;
    }

    /**
     * @return array<string,\Closure>
     * @phan-return array<string,Closure(CodeBase,Context,FunctionInterface,array):void>
     */
    private function getAnalyzeFunctionCallClosuresStatic(CodeBase $code_base) : array
    {
        $result = [];
        $add_callable_checker_closure = function (FunctionInterface $function) use (&$result) {
            $params = [];
            foreach ($function->getParameterList() as $i => $param) {
                // If there's a type such as Closure|string|int, don't automatically assume that any string or array passed in is meant to be a callable.
                // Explicitly require at least one type to be `callable`
                if ($param->getUnionType()->hasTypeMatchingCallback(function (Type $type) : bool {
                    // TODO: More specific closure for CallableDeclarationType
                    return $type instanceof CallableType || $type instanceof CallableDeclarationType;
                })) {
                    $params[] = $i;
                }
            }
            if (\count($params) === 0) {
                return;
            }
            // Generate a de-duplicated closure.
            // fqsen can be global_function or ClassName::method
            $result[(string)$function->getFQSEN()] = self::generateClosure($params);
        };

        foreach ($code_base->getFunctionMap() as $function) {
            $add_callable_checker_closure($function);
        }
        foreach ($code_base->getMethodSet() as $function) {
            $add_callable_checker_closure($function);
        }

        // new ReflectionFunction('my_func') is a usage of my_func()
        // See https://github.com/phan/phan/issues/1204 for note on function_exists() (not supported right now)
        $result['\\ReflectionFunction::__construct'] = self::generateClosure([0]);

        // When a codebase calls function_exists(string|callable) is to **check** if a function exists,
        // don't emit PhanUndeclaredFunctionInCallable as a side effect.
        unset($result['\\function_exists']);

        // Don't do redundant work extracting function definitions for commonly invoked functions.
        // TODO: Get actual statistics on how frequently used these are
        unset($result['\\call_user_func']);
        unset($result['\\call_user_func_array']);
        unset($result['\\array_map']);
        unset($result['\\array_filter']);
        // End of commonly used functions.

        return $result;
    }

    /**
     * @phan-return array<string,Closure(CodeBase,Context,FunctionInterface,array):void>
     */
    public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array
    {
        // Unit tests invoke this repeatedly. Cache it.
        static $analyzers = null;
        if ($analyzers === null) {
            $analyzers = self::getAnalyzeFunctionCallClosuresStatic($code_base);
        }
        return $analyzers;
    }
}
<?php declare(strict_types=1);
namespace Phan\Plugin\Internal;

use Phan\AST\ContextNode;
use Phan\CodeBase;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\Func;
use Phan\PluginV2;
use Phan\PluginV2\AnalyzeFunctionCallCapability;
use ast\Node;

/**
 * NOTE: This is automatically loaded by phan. Do not include it in a config.
 */
final class CompactPlugin extends PluginV2 implements
    AnalyzeFunctionCallCapability
{

    public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array
    {
        // Unit tests invoke this repeatedly. Cache it.
        static $analyzers = null;
        if ($analyzers === null) {
            $analyzers = self::getAnalyzeFunctionCallClosuresStatic($code_base);
        }
        return $analyzers;
    }

    /**
     * @return array<string,\Closure>
     */
    private static function getAnalyzeFunctionCallClosuresStatic(CodeBase $code_base) : array
    {
        /**
         * @return void
         */
        $compact_callback = static function (
            CodeBase $code_base,
            Context $context,
            Func $unused_func,
            array $args
        ) {
            $maybe_emit_issue = function (string $variable_name, $arg = null) use ($code_base, $context) {
                if (!$context->getScope()->hasVariableWithName($variable_name)) {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::UndeclaredVariable,
                        $arg->lineno ?? $context->getLineNumberStart(),
                        $variable_name
                    );
                }
            };
            foreach ($args as $arg) {
                if (\is_string($arg)) {
                    $maybe_emit_issue($arg);
                    // NOTE: compact is **not** aware of superglobals
                    continue;
                }
                if (!($arg instanceof Node)) {
                    continue;
                }
                $value = (new ContextNode($code_base, $context, $arg))->getEquivalentPHPValue(ContextNode::RESOLVE_DEFAULT & ~ContextNode::RESOLVE_ARRAY_KEYS);
                if (\is_string($value)) {
                    $maybe_emit_issue($value, $arg);
                    continue;
                }
                if (\is_array($value)) {
                    foreach ($value as $value_element) {
                        if (\is_string($value_element)) {
                            $maybe_emit_issue($value_element, $arg);
                        }
                    }
                }
            }
        };
        return [
            'compact' => $compact_callback,
        ];
    }
}
<?php declare(strict_types=1);
namespace Phan\Plugin\Internal;

use Phan\CodeBase;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\FunctionInterface;
use Phan\PluginV2;
use Phan\PluginV2\AnalyzeFunctionCallCapability;
use ast;
use ast\Node;
use Closure;

/**
 * NOTE: This is automatically loaded by phan. Do not include it in a config.
 *
 * This internal plugin will warn if calls to internal string functions
 * or regex function appear to have arguments in the wrong order,
 *
 * e.g. explode($var, ':') or strpos(':', $x)
 */
final class StringFunctionPlugin extends PluginV2 implements
    AnalyzeFunctionCallCapability
{
    /**
     * @param Node|string|float|int|null $arg
     * @return bool true if the expression is simple to look up.
     */
    private static function isSimpleExpression($arg) : bool
    {
        if (\is_scalar($arg)) {
            return true;
        }
        if ($arg === null) {
            return true;
        }
        if ($arg instanceof Node) {
            $kind = $arg->kind;
            if ($kind === ast\AST_CONST || $kind === ast\AST_CLASS_CONST) {
                return true;
            }
            if ($kind === ast\AST_BINARY_OP) {
                // E.g. flags == BINARY_CONCAT
                return self::isSimpleExpression($arg->children['left']) && self::isSimpleExpression($arg->children['right']);
            } elseif ($kind === ast\AST_ARRAY) {
                foreach ($arg->children as $child) {
                    if (!($child instanceof Node)) {
                        continue;
                    }
                    if (!self::isSimpleExpression($child->children['key'])) {
                        return false;
                    }
                    if (!self::isSimpleExpression($child->children['value'])) {
                        return false;
                    }
                }
                return true;
            }
        }
        return false;
    }

    /**
     * @return array<string,Closure>
     * @phan-return array<string,Closure(CodeBase,Context,FunctionInterface,array):void>
     */
    private static function getAnalyzeFunctionCallClosuresStatic() : array
    {
        $make_order_warner = static function (int $expected_const_pos, int $expected_variable_pos) : Closure {
            $expected_arg_count = 1 + (int)max($expected_const_pos, $expected_variable_pos);
            /**
             * @return void
             */
            return static function (
                CodeBase $code_base,
                Context $context,
                FunctionInterface $function,
                array $args
            ) use (
                $expected_const_pos,
                $expected_variable_pos,
                $expected_arg_count
) {
                if (\count($args) < $expected_arg_count) {
                    return;
                }
                if (!self::isSimpleExpression($args[$expected_const_pos])) {
                    if (self::isSimpleExpression($args[$expected_variable_pos])) {
                        Issue::maybeEmit(
                            $code_base,
                            $context,
                            Issue::ParamSuspiciousOrder,
                            $context->getLineNumberStart(),
                            $expected_const_pos + 1,
                            $function->getFQSEN(),
                            $expected_variable_pos + 1
                        );
                    }
                }
            };
        };
        $var_1_const_2 = $make_order_warner(1, 0);
        $var_2_const_1 = $make_order_warner(0, 1);
        $var_3_const_1 = $make_order_warner(0, 2);
        $var_1_const_3 = $make_order_warner(2, 0);

        return [
            // Start of string functions from https://secure.php.net/manual/en/ref.strings.php
            'addcslashes'               => $var_1_const_2,
            'addslashes'                => $var_1_const_2,
            'chunk_split'               => $var_1_const_3,
            'convert_cyr_string'        => $var_1_const_2,
            'crypt'                     => $var_1_const_2,
            'explode'                   => $var_2_const_1,
            'htmlspecialchars_decode'   => $var_1_const_2,
            'htmlspecialchars_encode'   => $var_1_const_2,
            'implode'                   => $var_2_const_1,
            'join'                      => $var_2_const_1,
            'ltrim'                     => $var_1_const_2,
            'md5_file'                  => $var_1_const_2,
            'md5'                       => $var_1_const_2,
            'metaphone'                 => $var_1_const_2,
            'mb_convert_case'           => $var_1_const_3,
            'mb_convert_encoding'       => $var_1_const_2,
            'mb_convert_kana'           => $var_1_const_2,
            'mb_convert_variables'      => $var_3_const_1,
            'mb_detect_encoding'        => $var_1_const_2,
            'mb_encoding_mimeheader'    => $var_1_const_2,
            'mb_ereg_match'             => $var_2_const_1,
            'mb_ereg_replace_callback'  => $var_3_const_1,
            'mb_ereg_replace'           => $var_3_const_1,
            'mb_ereg_search_init'       => $var_1_const_2,
            'mb_ereg_search_pos'        => $var_1_const_2,
            'mb_ereg'                   => $var_2_const_1,
            'mb_eregi_replace'          => $var_3_const_1,
            'mb_eregi'                  => $var_2_const_1,
            'mb_split'                  => $var_2_const_1,
            'mb_strcut'                 => $var_1_const_2,
            'mb_strimwidth'             => $var_1_const_2,
            'mb_stripos'                => $var_1_const_2,
            'mb_stristr'                => $var_1_const_2,
            'mb_strlen'                 => $var_1_const_2,
            'mb_strpos'                 => $var_1_const_2,
            'mb_strrchr'                => $var_1_const_2,  // what about mb_strchr?
            'mb_strrichr'               => $var_1_const_2,
            'mb_strripos'               => $var_1_const_2,
            'mb_strtolower'             => $var_1_const_2,
            'mb_strtoupper'             => $var_1_const_2,
            'mb_strwidth'               => $var_1_const_2,
            'mb_substr_count'           => $var_1_const_2,
            'mb_substr'                 => $var_1_const_2,
            'money_format'              => $var_2_const_1,
            'nl2br'                     => $var_1_const_2,
            'number_format'             => $var_1_const_2,
            'rtrim'                     => $var_1_const_2,
            'sha1_file'                 => $var_1_const_2,
            'sha1'                      => $var_1_const_2,
            'sscanf'                    => $var_1_const_2,
            'strchr'                    => $var_1_const_2,
            'strcspn'                   => $var_1_const_2,
            'str_getcsv'                => $var_1_const_2,
            'stripos'                   => $var_1_const_2,
            'strip_tags'                => $var_1_const_2,
            'str_ireplace'              => $var_3_const_1,
            'stristr'                   => $var_1_const_2,
            'str_pad'                   => $var_1_const_2,
            'strpbrk'                   => $var_1_const_2,
            'strpos'                    => $var_1_const_2,
            'str_replace'               => $var_3_const_1,
            'strripos'                  => $var_1_const_2,
            'strrpos'                   => $var_1_const_2,
            'str_split'                 => $var_1_const_2,
            'strspn'                    => $var_1_const_2,
            'strstr'                    => $var_1_const_2,
            'strtr'                     => $var_1_const_2,
            'str_word_count'            => $var_1_const_2,
            'substr_count'              => $var_1_const_2,
            'substr_replace'            => $var_1_const_2,
            'trim'                      => $var_1_const_2,
            'ucwords'                   => $var_1_const_2,
            'wordwrap'                  => $var_1_const_2,
            // End of string functions from https://secure.php.net/manual/en/ref.strings.php

            // Start of PCRE preg_* functions
            'preg_filter'               => $var_3_const_1,
            'preg_grep'                 => $var_2_const_1,
            'preg_match_all'            => $var_2_const_1,
            'preg_match'                => $var_2_const_1,
            'preg_quote'                => $var_1_const_2,
            'preg_replace_callback'     => $var_2_const_1,
            'preg_replace'              => $var_3_const_1,
            'preg_split'                => $var_2_const_1,
            // End of PCRE preg_* functions

        ];
    }

    /**
     * @return array<string,\Closure>
     * @override
     * @suppress PhanPluginUnusedPublicFinalMethodArgument
     */
    public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array
    {
        // Unit tests invoke this repeatedly. Cache it.
        static $analyzers = null;
        if ($analyzers === null) {
            $analyzers = self::getAnalyzeFunctionCallClosuresStatic();
        }
        return $analyzers;
    }
}
<?php declare(strict_types=1);
namespace Phan\Plugin\Internal;

use Phan\CodeBase;
use Phan\Analysis\ArgumentType;
use Phan\Analysis\PostOrderAnalysisVisitor;
use Phan\AST\UnionTypeVisitor;
use Phan\Config;
use Phan\Language\Context;
use Phan\Language\Element\Func;
use Phan\Language\Type\ArrayShapeType;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\FalseType;
use Phan\Language\Type\IntType;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\MixedType;
use Phan\Language\Type\StringType;
use Phan\Language\UnionType;
use Phan\PluginV2\ReturnTypeOverrideCapability;
use Phan\PluginV2;

/**
 * NOTE: This is automatically loaded by phan. Do not include it in a config.
 *
 * TODO: Refactor this.
 *
 * @phan-file-suppress PhanPluginUnusedClosureArgument
 */
final class ArrayReturnTypeOverridePlugin extends PluginV2 implements
    ReturnTypeOverrideCapability
{

    /**
     * @return array<string,\Closure>
     */
    private static function getReturnTypeOverridesStatic(CodeBase $code_base) : array
    {
        $mixed_type  = MixedType::instance(false);
        $false_type  = FalseType::instance(false);
        $array_type  = ArrayType::instance(false);
        $int_type    = IntType::instance(false);
        $string_type = StringType::instance(false);
        $int_or_string_or_false = new UnionType([$int_type, $string_type, $false_type]);
        $int_or_string = new UnionType([$int_type, $string_type]);

        $get_element_type_of_first_arg = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($mixed_type, $false_type) : UnionType {
            if (\count($args) >= 1) {
                $array_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[0]);
                $element_types = $array_type->genericArrayElementTypes();
                if (!$element_types->isEmpty()) {
                    return $element_types->withType($false_type);
                }
            }
            return $mixed_type->asUnionType();
        };
        $get_key_type_of_first_arg = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($int_or_string_or_false, $false_type) : UnionType {
            if (\count($args) >= 1) {
                $array_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[0]);
                $key_type_enum = GenericArrayType::keyTypeFromUnionTypeKeys($array_type);
                if ($key_type_enum !== GenericArrayType::KEY_MIXED) {
                    $key_type = GenericArrayType::unionTypeForKeyType($key_type_enum);
                    return $key_type->withType($false_type);
                }
            }
            return $int_or_string_or_false;
        };
        $get_key_type_of_second_arg = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($int_or_string_or_false, $false_type) : UnionType {
            if (\count($args) >= 2) {
                $array_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[1]);
                $key_type_enum = GenericArrayType::keyTypeFromUnionTypeKeys($array_type);
                if ($key_type_enum !== GenericArrayType::KEY_MIXED) {
                    $key_type = GenericArrayType::unionTypeForKeyType($key_type_enum);
                    return $key_type->withType($false_type);
                }
            }
            return $int_or_string_or_false;
        };
        $get_first_array_arg = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($array_type) : UnionType {
            if (\count($args) >= 1) {
                $element_types = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[0])->genericArrayTypes();
                if (!$element_types->isEmpty()) {
                    return $element_types->withFlattenedArrayShapeTypeInstances();
                }
            }
            return $array_type->asUnionType();
        };
        $get_second_array_arg = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($array_type) : UnionType {
            if (\count($args) >= 2) {
                $element_types = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[1])->genericArrayTypes();
                if (!$element_types->isEmpty()) {
                    return $element_types->withFlattenedArrayShapeTypeInstances();
                }
            }
            return $array_type->asUnionType();
        };
        $array_fill_keys_callback = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($array_type) : UnionType {
            if (\count($args) == 2) {
                $element_types = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[1]);
                if (!$element_types->isEmpty()) {
                    $key_types = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[0]);
                    $key_type_enum = GenericArrayType::keyTypeFromUnionTypeValues($key_types);
                    return $element_types->asNonEmptyGenericArrayTypes($key_type_enum);
                }
            }
            return $array_type->asUnionType();
        };

        $array_fill_callback = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($array_type) : UnionType {
            if (\count($args) == 3) {
                $element_types = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[2]);
                return $element_types->asNonEmptyGenericArrayTypes(GenericArrayType::KEY_INT);
            }
            return $array_type->asUnionType();
        };

        $array_filter_callback = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($array_type) : UnionType {
            if (\count($args) >= 1) {
                $passed_array_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[0]);
                $generic_passed_array_type = $passed_array_type->genericArrayTypes();
                if (!$generic_passed_array_type->isEmpty()) {
                    if (\count($args) >= 2) {
                        // As a side effect of getting the list of callables, this warns about invalid callables
                        $filter_function_list = UnionTypeVisitor::functionLikeListFromNodeAndContext($code_base, $context, $args[1], true);
                        if (Config::get_track_references()) {
                            foreach ($filter_function_list as $filter_function) {
                                $filter_function->addReference($context);
                            }
                        }
                        if (count($args) === 2) {
                            foreach ($filter_function_list as $filter_function) {
                                // Analyze that the individual elements passed to array_filter()'s callback make sense.
                                // TODO: analyze ARRAY_FILTER_USE_KEY, ARRAY_FILTER_USE_BOTH
                                $passed_array_element_types = $passed_array_type->genericArrayElementTypes();
                                ArgumentType::analyzeParameter($code_base, $context, $filter_function, $passed_array_element_types, $context->getLineNumberStart(), 0);
                                if (!Config::get_quick_mode()) {
                                    $analyzer = new PostOrderAnalysisVisitor($code_base, $context, []);
                                    $analyzer->analyzeCallableWithArgumentTypes([$passed_array_element_types], $filter_function);
                                }
                            }
                        }
                        // TODO: Handle 3 args?
                        //
                        // ARRAY_FILTER_USE_KEY - pass key as the only argument to callback instead of the value
                        // ARRAY_FILTER_USE_BOTH - pass both value and key as arguments to callback instead of the value
                    }
                    // TODO: Analyze if it and the flags are compatible with the arguments to the closure provided.
                    // TODO: withFlattenedArrayShapeTypeInstances() for other values
                    return $generic_passed_array_type->withFlattenedArrayShapeTypeInstances();
                }
            }
            return $array_type->asUnionType();
        };

        $array_reduce_callback = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($mixed_type) : UnionType {
            if (\count($args) < 2) {
                return $mixed_type->asUnionType();
            }
            $function_like_list = UnionTypeVisitor::functionLikeListFromNodeAndContext($code_base, $context, $args[1], true);
            if (\count($function_like_list) === 0) {
                return $mixed_type->asUnionType();
            }
            $function_return_types = UnionType::empty();
            foreach ($function_like_list as $function_like) {
                // TODO: Support analysis of map/reduce functions with dependent union types?
                $function_return_types = $function_return_types->withUnionType($function_like->getUnionType());
            }
            if ($function_return_types->isEmpty()) {
                $function_return_types = $function_return_types->withType($mixed_type);
            }
            return $function_return_types;
        };

        $merge_array_types_callback = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($array_type) : UnionType {
            $types = UnionType::empty();
            foreach ($args as $arg) {
                $passed_array_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $arg);
                $types = $types->withUnionType($passed_array_type->genericArrayTypes()->withFlattenedArrayShapeTypeInstances());
            }
            if ($types->isEmpty()) {
                $types = $types->withType($array_type);
            }
            return $types;
        };

        /**
         * @suppress PhanPartialTypeMismatchArgumentInternal TODO: better inference of array_slice return type keys/values
         * @suppress PhanPartialTypeMismatchArgument TODO: better inference of array_slice return type keys/values
         */
        $array_map_callback = static function (
            CodeBase $code_base,
            Context $context,
            Func $function,
            array $args
        ) use ($array_type) : UnionType {
            if (\count($args) < 2) {
                return $array_type->asUnionType();
            }
            $function_like_list = UnionTypeVisitor::functionLikeListFromNodeAndContext($code_base, $context, $args[0], true);
            if (\count($function_like_list) === 0) {
                return $array_type->asUnionType();
            }
            $arguments = \array_slice($args, 1);
            $possible_return_types = UnionType::empty();
            $cache_outer = [];
            $get_argument_type = function ($argument, int $i) use ($code_base, $context, &$cache_outer) : UnionType {
                if (isset($cache_outer[$i])) {
                    return $cache_outer[$i];
                }
                $argument_type = UnionTypeVisitor::unionTypeFromNode(
                    $code_base,
                    $context,
                    $argument,
                    true
                );
                $cache_outer[$i] = $argument_type;
                return $argument_type;
            };
            $cache = [];
            // Don't calculate argument types more than once.
            $get_argument_type_for_array_map = function ($argument, int $i) use ($get_argument_type, &$cache) : UnionType {
                if (isset($cache[$i])) {
                    return $cache[$i];
                }
                // Convert T[] to T
                $argument_type = $get_argument_type($argument, $i)->genericArrayElementTypes();
                $cache[$i] = $argument_type;
                return $argument_type;
            };
            foreach ($function_like_list as $map_function) {
                ArgumentType::analyzeForCallback(
                    $map_function,
                    $arguments,
                    $context,
                    $code_base,
                    $get_argument_type_for_array_map
                );
                if ($map_function->hasDependentReturnType()) {
                    $possible_return_types = $possible_return_types->withUnionType($map_function->getDependentReturnType($code_base, $context, $arguments));
                } else {
                    $possible_return_types = $possible_return_types->withUnionType($map_function->getUnionType());
                }
            }
            if (Config::get_track_references()) {
                foreach ($function_like_list as $map_function) {
                    $map_function->addReference($context);
                }
            }
            if (!Config::get_quick_mode()) {
                $argument_types = [];
                foreach ($arguments as $i => $node) {
                    $argument_types[] = $get_argument_type_for_array_map($node, $i);
                }
                foreach ($function_like_list as $map_function) {
                    $analyzer = new PostOrderAnalysisVisitor($code_base, $context, []);
                    $analyzer->analyzeCallableWithArgumentTypes($argument_types, $map_function);
                }
            }
            if ($possible_return_types->isEmpty()) {
                return $array_type->asUnionType();
            }
            $key_type_enum = GenericArrayType::keyTypeFromUnionTypeKeys($get_argument_type($arguments[0], 0));

            return $possible_return_types->elementTypesToGenericArray($key_type_enum);
        };
        $array_pad_callback = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($array_type) : UnionType {
            if (\count($args) != 3) {
                return $array_type->asUnionType();
            }
            $padded_array_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[0]);
            $result_types = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[2])->asGenericArrayTypes(GenericArrayType::KEY_INT);
            $result_types = $result_types->withUnionType($padded_array_type->genericArrayTypes());
            if ($result_types->isEmpty()) {
                $result_types = $result_types->withType($array_type);
            }
            return $result_types;
        };
        $array_keys_callback = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($array_type, $int_type, $string_type) : UnionType {
            if (\count($args) != 1) {
                return $array_type->asUnionType();
            }
            $key_union_type = UnionTypeVisitor::unionTypeOfArrayKeyForNode($code_base, $context, $args[0]);
            if ($key_union_type === null) {
                $key_union_type = new UnionType([$int_type, $string_type], true);
            }
            return $key_union_type->asGenericArrayTypes(GenericArrayType::KEY_INT);
        };
        $each_callback = static function (CodeBase $code_base, Context $context, Func $function, array $args) use ($mixed_type, $false_type, $int_or_string) : UnionType {
            if (\count($args) >= 1) {
                $array_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $args[0]);
                $element_types = $array_type->genericArrayElementTypes();
                $key_type_enum = GenericArrayType::keyTypeFromUnionTypeKeys($array_type);
                if ($key_type_enum !== GenericArrayType::KEY_MIXED) {
                    $key_type = GenericArrayType::unionTypeForKeyType($key_type_enum);
                } else {
                    $key_type = $int_or_string;
                }
                $array_shape_type = ArrayShapeType::fromFieldTypes([$key_type, $element_types], false);

                return new UnionType([$array_shape_type, $false_type]);
            }
            return $mixed_type->asUnionType();
        };
        return [
            // Gets the element types of the first
            'array_pop'   => $get_element_type_of_first_arg,
            'array_shift' => $get_element_type_of_first_arg,
            'current'     => $get_element_type_of_first_arg,
            'end'         => $get_element_type_of_first_arg,
            'next'        => $get_element_type_of_first_arg,
            'pos'         => $get_element_type_of_first_arg,  // alias of 'current'
            'prev'        => $get_element_type_of_first_arg,
            'reset'       => $get_element_type_of_first_arg,
            'each'        => $each_callback,

            'key'          => $get_key_type_of_first_arg,
            'array_search' => $get_key_type_of_second_arg,

            // array_filter and array_map
            'array_map'    => $array_map_callback,
            'array_filter' => $array_filter_callback,
            'array_reduce' => $array_reduce_callback,

            // misc
            'array_change_key_case'     => $get_first_array_arg,
            'array_combine'             => $get_second_array_arg,  // combines keys with values
            'array_diff'                => $get_first_array_arg,
            'array_diff_assoc'          => $get_first_array_arg,
            'array_diff_uassoc'         => $get_first_array_arg,
            'array_diff_ukey'           => $get_first_array_arg,
            'array_fill_keys'           => $array_fill_keys_callback,
            'array_fill'                => $array_fill_callback,
            'array_intersect'           => $merge_array_types_callback,
            'array_intersect_assoc'     => $merge_array_types_callback,
            'array_intersect_key'       => $merge_array_types_callback,
            'array_intersect_uassoc'    => $merge_array_types_callback,
            'array_intersect_ukey'      => $merge_array_types_callback,
            'array_keys'                => $array_keys_callback,
            'array_merge'               => $merge_array_types_callback,
            'array_merge_recursive'     => $merge_array_types_callback,
            'array_pad'                 => $array_pad_callback,
            'array_replace'             => $merge_array_types_callback,
            'array_replace_recursive'   => $merge_array_types_callback,
            'array_reverse'             => $get_first_array_arg,
            // 'array_splice' probably used more often by reference
            'array_udiff'               => $get_first_array_arg,
            'array_udiff_assoc'         => $get_first_array_arg,
            'array_udiff_uassoc'        => $get_first_array_arg,
            'array_uintersect'          => $merge_array_types_callback,
            'array_uintersect_assoc'    => $merge_array_types_callback,
            'array_uintersect_uassoc'   => $merge_array_types_callback,
            'array_unique'              => $get_first_array_arg,
        ];
    }

    /**
     * @return array<string,\Closure>
     */
    public function getReturnTypeOverrides(CodeBase $code_base) : array
    {
        // Unit tests invoke this repeatedly. Cache it.
        static $overrides = null;
        if ($overrides === null) {
            $overrides = self::getReturnTypeOverridesStatic($code_base);
        }
        return $overrides;
    }
}
<?php declare(strict_types=1);
namespace Phan\Plugin;

use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Func;
use Phan\Language\Element\Method;
use Phan\Plugin;
use ast\Node;

/**
 * Plugins can be defined in the config and will have
 * their hooks called at appropriate times during analysis
 * of each file, class, method and function.
 *
 * Plugins must extends this class and return an instance
 * of themselves.
 *
 * @suppress PhanUnreferencedClass
 */
class PluginImplementation extends Plugin
{
    /**
     * Do a first-pass analysis of a node before Phan
     * does its full analysis. This hook allows you to
     * update types in the CodeBase before analysis
     * happens.
     *
     * @param CodeBase $code_base (@phan-unused-param)
     * The code base in which the node exists
     *
     * @param Context $context (@phan-unused-param)
     * The context in which the node exits. This is
     * the context inside the given node rather than
     * the context outside of the given node
     *
     * @param Node $node (@phan-unused-param)
     * The php-ast Node being analyzed.
     *
     * @return void
     */
    public function preAnalyzeNode(
        CodeBase $code_base,
        Context $context,
        Node $node
    ) {
    }

    /**
     * @param CodeBase $code_base (@phan-unused-param)
     * The code base in which the node exists
     *
     * @param Context $context (@phan-unused-param)
     * The context in which the node exits. This is
     * the context inside the given node rather than
     * the context outside of the given node
     *
     * @param Node $node (@phan-unused-param)
     * The php-ast Node being analyzed.
     *
     * @param ?Node $parent_node (@phan-unused-param)
     * The parent node of the given node (if one exists).
     *
     * @return void
     */
    public function analyzeNode(
        CodeBase $code_base,
        Context $context,
        Node $node,
        Node $parent_node = null
    ) {
    }

    /**
     * @param CodeBase $code_base (@phan-unused-param)
     * The code base in which the class exists
     *
     * @param Clazz $class (@phan-unused-param)
     * A class being analyzed
     *
     * @return void
     */
    public function analyzeClass(
        CodeBase $code_base,
        Clazz $class
    ) {
    }

    /**
     * @param CodeBase $code_base (@phan-unused-param)
     * The code base in which the method exists
     *
     * @param Method $method (@phan-unused-param)
     * A method being analyzed
     *
     * @return void
     */
    public function analyzeMethod(
        CodeBase $code_base,
        Method $method
    ) {
    }

    /**
     * @param CodeBase $code_base (@phan-unused-param)
     * The code base in which the function exists
     *
     * @param Func $function (@phan-unused-param)
     * A function being analyzed
     *
     * @return void
     */
    public function analyzeFunction(
        CodeBase $code_base,
        Func $function
    ) {
    }

    // Internal methods, for use by ConfigPluginSet

    /**
     * @return bool true if $method_name is defined by the subclass of PluginAwareAnalysisVisitor or PluginAwarePostAnalysisVisitor,
     * and not by PluginAware*AnalysisVisitor or one of its parents.
     */
    final public static function isDefinedInSubclass(string $method_name) : bool
    {
        $method = new \ReflectionMethod(static::class, $method_name);
        return is_subclass_of($method->getDeclaringClass()->name, self::class);
    }
    // End of internal methods.
}
<?php declare(strict_types=1);

use Phan\CodeBase;

// Listen for all errors
error_reporting(E_ALL);

// Take as much memory as we need
ini_set("memory_limit", '-1');

// Add the root to the include path
define('CLASS_DIR', __DIR__ . '/../');
set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR);

// Use the composer autoloader
foreach ([
    __DIR__.'/../../vendor/autoload.php',          // autoloader is in this project
    __DIR__.'/../../../../../vendor/autoload.php', // autoloader is in parent project
    ] as $file) {
    if (file_exists($file)) {
        require_once($file);
        break;
    }
}

define('EXIT_SUCCESS', 0);
define('EXIT_FAILURE', 1);
define('EXIT_ISSUES_FOUND', EXIT_FAILURE);

// Throw exceptions so asserts can be linked to the code being analyzed
ini_set('assert.exception', '1');

// Explicitly set each option in case INI is set otherwise
assert_options(ASSERT_ACTIVE, true);
assert_options(ASSERT_WARNING, false);
assert_options(ASSERT_BAIL, false);
assert_options(ASSERT_QUIET_EVAL, false);
assert_options(ASSERT_CALLBACK, '');  // Can't explicitly set ASSERT_CALLBACK to null?

// Print more of the backtrace than is done by default
set_exception_handler(function (Throwable $throwable) {
    error_log("$throwable\n");
    exit(EXIT_FAILURE);
});

/**
 * @return mixed
 * @suppress PhanPluginUnusedVariable
 */
function with_disabled_phan_error_handler(Closure $closure)
{
    global $__no_echo_phan_errors;
    $__no_echo_phan_errors = true;
    try {
        return $closure();
    } finally {
        $__no_echo_phan_errors = false;
    }
}

/**
 * The error handler for PHP notices, etc.
 * This is a named function instead of a closure to make stack traces easier to read.
 *
 * @suppress PhanUnreferencedFunction
 * @suppress PhanAccessMethodInternal
 */
function phan_error_handler($errno, $errstr, $errfile, $errline)
{
    global $__no_echo_phan_errors;
    if ($__no_echo_phan_errors) {
        return false;
    }
    error_log("$errfile:$errline [$errno] $errstr\n");
    if (class_exists(CodeBase::class, false)) {
        $most_recent_file = CodeBase::getMostRecentlyParsedOrAnalyzedFile();
        if (is_string($most_recent_file)) {
            error_log("(Crashed when parsing/analyzing '$most_recent_file')\n");
        }
    }
    if (error_reporting() === 0) {
        // https://secure.php.net/manual/en/language.operators.errorcontrol.php
        // Don't make Phan terminate if the @-operator was being used on an expression.
        return false;
    }

    ob_start();
    debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
    error_log(ob_get_clean());

    exit(EXIT_FAILURE);
}
set_error_handler('phan_error_handler');
<?php declare(strict_types=1);
namespace Phan\PluginV2;

/**
 * For plugins which define their own pre-order analysis behaviors in the analysis phase.
 * Called on a node before PluginAwarePreAnalysisVisitor implementations.
 *
 * Public APIs for use by plugins:
 *
 * - visit<VisitSuffix>(...) (Override these methods)
 * - emitPluginIssue(CodeBase $code_base, Config $config, ...) (Call these methods)
 * - emit(...)
 * - Public methods from Phan\AST\AnalysisVisitor
 *
 * NOTE: Subclasses should not implement the visit() method unless they absolutely need to.
 * (E.g. if the body would be empty, or if it could be replaced with a small number of more specific methods such as visitFuncDecl, visitVar, etc.)
 *
 * - Phan is able to figure out which methods a subclass implements, and only call the plugin's visitor for those types,
 *   but only when the plugin's visitor does not override the fallback visit() method.
 */
abstract class PluginAwarePreAnalysisVisitor extends PluginAwareBaseAnalysisVisitor
{
    // For backwards compatibility reasons, parent_node isn't available in PreAnalysis visitors
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;
use Phan\Language\Context;
use ast\Node;

/**
 * BeforeAnalyzeFileCapability is used when you want to perform checks before analyzing a file
 * NOTE: This does not run on empty files.
 */
interface BeforeAnalyzeFileCapability
{
    /**
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * A context with the file name for $file_contents and the scope before analyzing $node.
     *
     * @param string $file_contents the unmodified file contents
     * @param Node $node the node parsed from $file_contents
     * @return void
     */
    public function beforeAnalyzeFile(
        CodeBase $code_base,
        Context $context,
        string $file_contents,
        Node $node
    );
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\AST\AnalysisVisitor;
use Phan\AST\Visitor\Element;
use Phan\Issue;
use ast\Node;

/**
 * This augments AnalysisVisitor with public and internal methods.
 */
abstract class PluginAwareBaseAnalysisVisitor extends AnalysisVisitor
{
    use IssueEmitter;  // defines emitPluginIssue

    /**
     * This is an empty visit() body.
     * Don't override this unless you need to, analysis is more efficient if Phan knows it doesn't need to call a plugin on a node type.
     * @see self::isDefinedInSubclass
     * @param $node @phan-unused-param (unused because the body is empty)
     *
     * @return void
     */
    public function visit(Node $node)
    {
    }

    /**
     * @param string $issue_type
     * A name for the type of issue such as 'PhanPluginMyIssue'
     *
     * @param string $issue_message_fmt
     * The complete issue message format string to emit such as
     * 'class with fqsen {CLASS} is broken in some fashion' (preferred)
     * or 'class with fqsen %s is broken in some fashion'
     * The list of placeholders for between braces can be found
     * in \Phan\Issue::uncolored_format_string_for_template.
     *
     * @param array<int,string> $issue_message_args
     * The arguments for this issue format.
     * If this array is empty, $issue_message_args is kept in place
     *
     * @param int $severity
     * A value from the set {Issue::SEVERITY_LOW,
     * Issue::SEVERITY_NORMAL, Issue::SEVERITY_HIGH}.
     *
     * @param int $remediation_difficulty
     * A guess at how hard the issue will be to fix from the
     * set {Issue:REMEDIATION_A, Issue:REMEDIATION_B, ...
     * Issue::REMEDIATION_F} with F being the hardest.
     * @suppress PhanUnreferencedPublicMethod (this plugin type is deprecated)
     */
    public function emit(
        string $issue_type,
        string $issue_message_fmt,
        array $issue_message_args = [],
        int $severity = Issue::SEVERITY_NORMAL,
        int $remediation_difficulty = Issue::REMEDIATION_B,
        int $issue_type_id = Issue::TYPE_ID_UNKNOWN
    ) {
        $this->emitPluginIssue(
            $this->code_base,
            $this->context,
            $issue_type,
            $issue_message_fmt,
            $issue_message_args,
            $severity,
            $remediation_difficulty,
            $issue_type_id
        );
    }

    // Internal methods used by ConfigPluginSet are below.
    // They aren't useful for plugins.

    /**
     * @return array<int,int> The list of $node->kind values this plugin is capable of analyzing.
     */
    final public static function getHandledNodeKinds() : array
    {
        $defines_visit = self::isDefinedInSubclass('visit');
        $kinds = [];
        foreach (Element::VISIT_LOOKUP_TABLE as $kind => $method_name) {
            if ($defines_visit || self::isDefinedInSubclass($method_name)) {
                $kinds[] = $kind;
            }
        }
        return $kinds;
    }

    /**
     * @return bool true if $method_name is defined by the subclass of PluginAwareBaseAnalysisVisitor,
     * and not by PluginAwareBaseAnalysisVisitor or one of its parents.
     */
    private static function isDefinedInSubclass(string $method_name) : bool
    {
        $method = new \ReflectionMethod(static::class, $method_name);
        return is_subclass_of($method->getDeclaringClass()->name, self::class);
    }
    // End of methods for internal use.
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;

/**
 * AnalyzeFunctionCallCapability is used when you want to analyze the parameters passed to a function or method, whether or not the return value is used.
 * (e.g. for analyzing `my_printf($fmtstr, ...$args)`)
 *
 * @see AnalyzeFunctionCallCapability for making the return type depend on the passed in parameters.
 */
interface AnalyzeFunctionCallCapability
{
    /**
     * @return array<string,\Closure>
     * maps FQSEN of function or method to a closure used to analyze the function in question.
     * '\A::foo' or 'A::foo' as a key will override a method, and '\foo' or 'foo' as a key will override a function.
     * Closure Type: function(CodeBase $code_base, Context $context, Func|Method $function, array $args) : void {...}
     */
    public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array;
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\Language\Context;
use Phan\CodeBase;
use Phan\Issue;
use Phan\IssueInstance;

/**
 * A trait which allows plugins to emit issues with custom error messages
 */
trait IssueEmitter
{
    /**
     * Emit an issue if it is not suppressed
     *
     * @param CodeBase $code_base
     * The code base in which the issue was found
     *
     * @param Context $context
     * The context in which the issue was found
     *
     * @param string $issue_type
     * A name for the type of issue such as 'PhanPluginMyIssue'
     *
     * @param string $issue_message_fmt
     * The complete issue message format string to emit such as
     * 'class with fqsen {CLASS} is broken in some fashion' (preferred)
     * or 'class with fqsen %s is broken in some fashion'
     * The list of placeholders for between braces can be found
     * in \Phan\Issue::uncolored_format_string_for_template.
     *
     * @param array<int,string> $issue_message_args
     * The arguments for this issue format.
     * If this array is empty, $issue_message_args is kept in place
     *
     * @param int $severity
     * A value from the set {Issue::SEVERITY_LOW,
     * Issue::SEVERITY_NORMAL, Issue::SEVERITY_HIGH}.
     *
     * @param int $remediation_difficulty
     * A guess at how hard the issue will be to fix from the
     * set {Issue:REMEDIATION_A, Issue:REMEDIATION_B, ...
     * Issue::REMEDIATION_F} with F being the hardest.
     *
     * @param int $issue_type_id An issue id for pylint
     */
    public function emitPluginIssue(
        CodeBase $code_base,
        Context $context,
        string $issue_type,
        string $issue_message_fmt,
        array $issue_message_args = [],
        int $severity = Issue::SEVERITY_NORMAL,
        int $remediation_difficulty = Issue::REMEDIATION_B,
        int $issue_type_id = Issue::TYPE_ID_UNKNOWN
    ) {
        $issue = new Issue(
            $issue_type,
            Issue::CATEGORY_PLUGIN,
            $severity,
            $issue_message_fmt,
            $remediation_difficulty,
            $issue_type_id
        );

        $issue_instance = new IssueInstance(
            $issue,
            $context->getFile(),
            $context->getLineNumberStart(),
            $issue_message_args
        );

        Issue::maybeEmitInstance(
            $code_base,
            $context,
            $issue_instance
        );
    }
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;
use Phan\Language\Element\Func;

interface AnalyzeFunctionCapability
{
    /**
     * @param CodeBase $code_base
     * The code base in which the function exists
     *
     * @param Func $function
     * A function being analyzed
     *
     * @return void
     */
    public function analyzeFunction(
        CodeBase $code_base,
        Func $function
    );
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;
use Phan\Language\Element\Method;

interface AnalyzeMethodCapability
{
    /**
     * @param CodeBase $code_base
     * The code base in which the method exists
     *
     * @param Method $method
     * A method being analyzed
     *
     * @return void
     */
    public function analyzeMethod(
        CodeBase $code_base,
        Method $method
    );
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;
use Phan\Language\Element\Clazz;

interface AnalyzeClassCapability
{
    /**
     * @param CodeBase $code_base
     * The code base in which the class exists
     *
     * @param Clazz $class
     * A class being analyzed
     *
     * @return void
     */
    public function analyzeClass(
        CodeBase $code_base,
        Clazz $class
    );
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Exception;

/**
 * This is thrown to prevent Phan from running its own analysis after analyzeFunctionCall.
 *
 * This is used for tricky signatures such as join, which can take (array, string) or (string, array) when there are two arguments.
 */
class StopParamAnalysisException extends Exception
{
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\Issue;
use ast\Node;

/**
 * For plugins which define their own post-order analysis behaviors in the analysis phase.
 * Called on a node after PluginAwarePreAnalysisVisitor implementations.
 *
 * - visit<VisitSuffix>(...) (Override these methods)
 * - emitPluginIssue(CodeBase $code_base, Config $config, ...) (Call these methods)
 * - emit(...)
 * - Public methods from Phan\AST\AnalysisVisitor
 *
 * NOTE: Subclasses should not implement the visit() method unless they absolutely need to.
 * (E.g. if the body would be empty, or if it could be replaced with a small number of more specific methods such as visitFuncDecl, visitVar, etc.)
 *
 * - Phan is able to figure out which methods a subclass implements, and only call the plugin's visitor for those types,
 *   but only when the plugin's visitor does not override the fallback visit() method.
 */
abstract class PluginAwareAnalysisVisitor extends PluginAwareBaseAnalysisVisitor
{
    /**
     * @var Node|null - Set after the constructor is called
     * @suppress PhanUnreferencedProtectedProperty (Some plugins might use this. Note that this class is deprecated.)
     */
    protected $parent_node;

    // Implementations should omit the constructor or call parent::__construct(CodeBase $code_base, Context $context)

    /**
     * @param string $issue_type
     * A name for the type of issue such as 'PhanPluginMyIssue'
     *
     * @param string $issue_message_fmt
     * The complete issue message format string to emit such as
     * 'class with fqsen {CLASS} is broken in some fashion' (preferred)
     * or 'class with fqsen %s is broken in some fashion'
     * The list of placeholders for between braces can be found
     * in \Phan\Issue::uncolored_format_string_for_template.
     *
     * @param array<int,string> $issue_message_args
     * The arguments for this issue format.
     * If this array is empty, $issue_message_args is kept in place
     *
     * @param int $severity
     * A value from the set {Issue::SEVERITY_LOW,
     * Issue::SEVERITY_NORMAL, Issue::SEVERITY_HIGH}.
     *
     * @param int $remediation_difficulty
     * A guess at how hard the issue will be to fix from the
     * set {Issue:REMEDIATION_A, Issue:REMEDIATION_B, ...
     * Issue::REMEDIATION_F} with F being the hardest.
     */
    public function emit(
        string $issue_type,
        string $issue_message_fmt,
        array $issue_message_args = [],
        int $severity = Issue::SEVERITY_NORMAL,
        int $remediation_difficulty = Issue::REMEDIATION_B,
        int $issue_type_id = Issue::TYPE_ID_UNKNOWN
    ) {
        $this->emitPluginIssue(
            $this->code_base,
            $this->context,
            $issue_type,
            $issue_message_fmt,
            $issue_message_args,
            $severity,
            $remediation_difficulty,
            $issue_type_id
        );
    }
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

/**
 * @deprecated - Use PostAnalyzeNodeCapability instead
 */
interface AnalyzeNodeCapability
{
    /**
     * Returns the name of the visitor class to be instantiated and invoked to analyze a node in the analysis phase.
     * (To analyze a node. AnalyzeNodeCapability is run after PreAnalyzeNodeCapability)
     * The class should be created by the plugin visitor, and must extend PluginAwareAnalysisVisitor.
     *
     * If state needs to be shared with a visitor and a plugin, a plugin author may use static variables of that plugin.
     *
     * @return string - The name of a class extending PluginAwareAnalysisVisitor
     */
    public static function getAnalyzeNodeVisitorClassName() : string;
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;
use Phan\Language\Element\Property;

interface AnalyzePropertyCapability
{
    /**
     * @param CodeBase $code_base
     * The code base in which the property exists
     *
     * @param Property $property
     * A property being analyzed
     *
     * @return void
     */
    public function analyzeProperty(
        CodeBase $code_base,
        Property $property
    );
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

interface PreAnalyzeNodeCapability
{
    /**
     * Returns the name of the visitor class to be instantiated and invoked to pre-analyze a node in the analysis phase.
     * (To pre-analyze a node)
     * (PreAnalyzeNodeCapability is run before AnalyzeNodeCapability)
     * The class should be created by the plugin visitor, and must extend PluginAwarePreAnalysisVisitor.
     *
     * If state needs to be shared with a visitor and a plugin, a plugin author may use static variables of that plugin.
     *
     * @return string - The name of a class extending PluginAwarePreAnalysisVisitor
     */
    public static function getPreAnalyzeNodeVisitorClassName() : string;
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;
use Phan\Language\Context;
use ast\Node;

/**
 * AfterAnalyzeFileCapability is used when you want to perform checks after analyzing a file
 * NOTE: This does not run on empty files.
 */
interface AfterAnalyzeFileCapability
{
    /**
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * A context with the file name for $file_contents and the scope after analyzing $node.
     *
     * @param string $file_contents the unmodified file contents
     * @param Node $node the node parsed from $file_contents
     * @return void
     */
    public function afterAnalyzeFile(
        CodeBase $code_base,
        Context $context,
        string $file_contents,
        Node $node
    );
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;
use Phan\Language\Context;

use ast\Node;

/**
 * @deprecated - New plugins should use PostAnalyzeNodeCapability
 */
interface LegacyAnalyzeNodeCapability
{
    /**
     * Analyze the given node in the given context after
     * Phan has analyzed the node.
     *
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * The context in which the node exits. This is
     * the context inside the given node rather than
     * the context outside of the given node
     *
     * @param Node $node
     * The php-ast Node being analyzed.
     *
     * @param ?Node $parent_node
     * The parent node of the given node (if any exist).
     *
     * @return void
     *
     * @deprecated
     */
    public function analyzeNode(
        CodeBase $code_base,
        Context $context,
        Node $node,
        Node $parent_node = null
    );
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;

/**
 * ReturnTypeOverrideCapability is used when you want to get a return type of a function or method that is dependent on the arguments.
 * (e.g. for analyzing `my_printf`)
 *
 * @see AnalyzeFunctionCallCapability for analyzing the parameters whether or not return types are used.
 */
interface ReturnTypeOverrideCapability
{
    /**
     * @return array<string,\Closure>
     *         maps FQSEN of function or method to a closure used to override the returned UnionType.
     *         The returned type is not validated.
     *         '\A::foo' as a key will override a method, and '\foo' as a key will override a function.
     *         Closure type: function(CodeBase $code_base, Context $context, Func|Method $function, array $args) : UnionType {...}
     */
    public function getReturnTypeOverrides(CodeBase $code_base) : array;
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;
use Phan\Language\Context;

use ast\Node;

/**
 * @deprecated - New plugins should use PostAnalyzeNodeCapability
 */
interface LegacyPostAnalyzeNodeCapability
{
    /**
     * Analyze the given node in the given context after
     * Phan has analyzed the node.
     *
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * The context in which the node exits. This is
     * the context inside the given node rather than
     * the context outside of the given node
     *
     * @param Node $node
     * The php-ast Node being analyzed.
     *
     * @param array<int,Node> $parent_node_list
     * The parent node of the given node (if any exist).
     *
     * @return void
     *
     * @deprecated
     */
    public function postAnalyzeNode(
        CodeBase $code_base,
        Context $context,
        Node $node,
        array $parent_node_list = []
    );
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\Issue;

// use ast\Node;

/**
 * For plugins which define their own post-order analysis behaviors in the analysis phase.
 * Called on a node after PluginAwarePreAnalysisVisitor implementations.
 *
 * - visit<VisitSuffix>(...) (Override these methods)
 * - emitPluginIssue(CodeBase $code_base, Config $config, ...) (Call these methods)
 * - emit(...)
 * - Public methods from Phan\AST\AnalysisVisitor
 *
 * NOTE: Subclasses should not implement the visit() method unless they absolutely need to.
 * (E.g. if the body would be empty, or if it could be replaced with a small number of more specific methods such as visitFuncDecl, visitVar, etc.)
 *
 * - Phan is able to figure out which methods a subclass implements, and only call the plugin's visitor for those types,
 *   but only when the plugin's visitor does not override the fallback visit() method.
 */
abstract class PluginAwarePostAnalysisVisitor extends PluginAwareBaseAnalysisVisitor
{
    // Subclasses should declare protected $parent_node_list as an instance property if they need to know the list.

    // @var array<int,Node> - Set after the constructor is called if an instance property with this name is declared
    // protected $parent_node_list;

    // Implementations should omit the constructor or call parent::__construct(CodeBase $code_base, Context $context)

    /**
     * @param string $issue_type
     * A name for the type of issue such as 'PhanPluginMyIssue'
     *
     * @param string $issue_message_fmt
     * The complete issue message format string to emit such as
     * 'class with fqsen {CLASS} is broken in some fashion' (preferred)
     * or 'class with fqsen %s is broken in some fashion'
     * The list of placeholders for between braces can be found
     * in \Phan\Issue::uncolored_format_string_for_template.
     *
     * @param array<int,string> $issue_message_args
     * The arguments for this issue format.
     * If this array is empty, $issue_message_args is kept in place
     *
     * @param int $severity
     * A value from the set {Issue::SEVERITY_LOW,
     * Issue::SEVERITY_NORMAL, Issue::SEVERITY_HIGH}.
     *
     * @param int $remediation_difficulty
     * A guess at how hard the issue will be to fix from the
     * set {Issue:REMEDIATION_A, Issue:REMEDIATION_B, ...
     * Issue::REMEDIATION_F} with F being the hardest.
     */
    public function emit(
        string $issue_type,
        string $issue_message_fmt,
        array $issue_message_args = [],
        int $severity = Issue::SEVERITY_NORMAL,
        int $remediation_difficulty = Issue::REMEDIATION_B,
        int $issue_type_id = Issue::TYPE_ID_UNKNOWN
    ) {
        $this->emitPluginIssue(
            $this->code_base,
            $this->context,
            $issue_type,
            $issue_message_fmt,
            $issue_message_args,
            $severity,
            $remediation_difficulty,
            $issue_type_id
        );
    }
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;

interface FinalizeProcessCapability
{
    /**
     * This is called after the other forms of analysis are finished running.
     * Useful if a PluginV2 needs to aggregate results of analysis.
     * This may be used to emit additional issues.
     *
     * This is run once per forked analysis process.
     * Some plugins using this, such as UnusedSuppressionPlugin,
     * will not work as expected with more than one process.
     * If possible, write plugins to emit issues immediately.
     *
     * @return void
     */
    public function finalizeProcess(CodeBase $code_base);
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

use Phan\CodeBase;
use Phan\Language\Context;

use ast\Node;

/**
 * @deprecated - New plugins should use PreAnalyzeNodeCapability
 */
interface LegacyPreAnalyzeNodeCapability
{
    /**
     * Do a first-pass analysis of a node before Phan
     * does its full analysis. This hook allows you to
     * update types in the CodeBase before analysis
     * happens.
     *
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * The context in which the node exits. This is
     * the context inside the given node rather than
     * the context outside of the given node
     *
     * @param Node $node
     * The php-ast Node being analyzed.
     * The parent node of the given node (if one exists).
     *
     * @return void
     *
     * @deprecated
     */
    public function preAnalyzeNode(
        CodeBase $code_base,
        Context $context,
        Node $node
    );
}
<?php declare(strict_types=1);
namespace Phan\PluginV2;

/**
 * This is the same as AnalyzeNodeCapability.
 *
 * If $this->parent_node_list is an instance property, then that will automatically get set.
 * $this->parent_node_list will have the elements closest to the current node at the end.
 *
 * - If that property is absent, it will not be set.
 */
interface PostAnalyzeNodeCapability
{
    /**
     * Returns the name of the visitor class to be instantiated and invoked to analyze a node in the analysis phase.
     * (To analyze a node. PostAnalyzeNodeCapability is run after PreAnalyzeNodeCapability)
     * The class should be created by the plugin visitor, and must extend PluginAwarePostAnalysisVisitor.
     *
     * If state needs to be shared with a visitor and a plugin, a plugin author may use static variables of that plugin.
     *
     * @return string - The name of a class extending PluginAwarePostAnalysisVisitor
     */
    public static function getPostAnalyzeNodeVisitorClassName() : string;
}
<?php declare(strict_types=1);
namespace Phan\Parse;

use Phan\AST\ContextNode;
use Phan\Analysis\ScopeVisitor;
use Phan\CodeBase;
use Phan\Config;
use Phan\Daemon;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\ClassConstant;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Comment;
use Phan\Language\Element\Func;
use Phan\Language\Element\FunctionFactory;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\GlobalConstant;
use Phan\Language\Element\Method;
use Phan\Language\Element\Property;
use Phan\Language\FQSEN\FullyQualifiedClassConstantName;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedGlobalConstantName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\FQSEN\FullyQualifiedPropertyName;
use Phan\Language\FutureUnionType;
use Phan\Language\Type;
use Phan\Language\Type\ArrayShapeType;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\CallableType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\StringType;
use Phan\Language\UnionType;
use Phan\Library\None;
use ast\Node;

/**
 * The class is a visitor for AST nodes that does parsing. Each
 * visitor populates the $code_base with any
 * globally accessible structural elements and will return a
 * possibly new context as modified by the given node.
 *
 * @property-read CodeBase $code_base
 *
 * @phan-file-suppress PhanPluginUnusedPublicMethodArgument implementing faster no-op methods for common visit*
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 * @phan-file-suppress PhanPartialTypeMismatchArgumentInternal
 */
class ParseVisitor extends ScopeVisitor
{

    /**
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     *
     * @param CodeBase $code_base
     * The global code base in which we store all
     * state
     */
    /*
    public function __construct(
        CodeBase $code_base,
        Context $context
    ) {
        parent::__construct($code_base, $context);
    }
     */

    /**
     * Visit a node with kind `\ast\AST_CLASS`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitClass(Node $node) : Context
    {
        if ($node->flags & \ast\flags\CLASS_ANONYMOUS) {
            $class_name = (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ))->getUnqualifiedNameForAnonymousClass();
        } else {
            $class_name = (string)$node->children['name'];
        }

        // This happens now and then and I have no idea
        // why.
        if (empty($class_name)) {
            return $this->context;
        }

        $class_fqsen = FullyQualifiedClassName::fromStringInContext(
            $class_name,
            $this->context
        );

        \assert(
            $class_fqsen instanceof FullyQualifiedClassName,
            "The class FQSEN must be a FullyQualifiedClassName"
        );

        // Hunt for an available alternate ID if necessary
        $alternate_id = 0;
        while ($this->code_base->hasClassWithFQSEN($class_fqsen)) {
            $class_fqsen = $class_fqsen->withAlternateId(++$alternate_id);
        }

        if ($alternate_id > 0) {
            Daemon::debugf("Using an alternate for %s: %d\n", $class_fqsen, $alternate_id);
        }

        // Build the class from what we know so far
        $class_context = $this->context
            ->withLineNumberStart($node->lineno ?? 0)
            ->withLineNumberEnd($node->endLineno ?? 0);

        $class = new Clazz(
            $class_context,
            $class_name,
            $class_fqsen->asUnionType(),
            $node->flags ?? 0,
            $class_fqsen
        );
        $class->setDidFinishParsing(false);
        try {
            // Set the scope of the class's context to be the
            // internal scope of the class
            $class_context = $class_context->withScope(
                $class->getInternalScope()
            );

            // Get a comment on the class declaration
            $comment = Comment::fromStringInContext(
                $node->children['docComment'] ?? '',
                $this->code_base,
                $this->context,
                $node->lineno ?? 0,
                Comment::ON_CLASS
            );

            // Add any template types parameterizing a generic class
            foreach ($comment->getTemplateTypeList() as $template_type) {
                $class->getInternalScope()->addTemplateType($template_type);
            }

            $class->setIsDeprecated($comment->isDeprecated());
            $class->setIsNSInternal($comment->isNSInternal());

            $class->setSuppressIssueList(
                $comment->getSuppressIssueList()
            );

            // Add the class to the code base as a globally
            // accessible object
            $this->code_base->addClass($class);

            // Depends on code_base for checking existence of __get and __set.
            // TODO: Add a check in analyzeClasses phase that magic @property declarations
            // are limited to classes with either __get or __set declared (or interface/abstract
            $class->setMagicPropertyMap(
                $comment->getMagicPropertyMap(),
                $this->code_base
            );

            // Depends on code_base for checking existence of __call or __callStatic.
            // TODO: Add a check in analyzeClasses phase that magic @method declarations
            // are limited to classes with either __get or __set declared (or interface/abstract)
            $class->setMagicMethodMap(
                $comment->getMagicMethodMap(),
                $this->code_base
            );

            // usually used together with magic @property annotations
            $class->setForbidUndeclaredMagicProperties($comment->getForbidUndeclaredMagicProperties());

            // usually used together with magic @method annotations
            $class->setForbidUndeclaredMagicMethods($comment->getForbidUndeclaredMagicMethods());

            // Look to see if we have a parent class
            $extends_node = $node->children['extends'] ?? null;
            if ($extends_node instanceof Node) {
                $parent_class_name =
                    (string)$extends_node->children['name'];

                // Check to see if the name isn't fully qualified
                if ($extends_node->flags & \ast\flags\NAME_NOT_FQ) {
                    if ($this->context->hasNamespaceMapFor(
                        \ast\flags\USE_NORMAL,
                        $parent_class_name
                    )) {
                        // Get a fully-qualified name
                        $parent_class_name =
                            (string)($this->context->getNamespaceMapFor(
                                \ast\flags\USE_NORMAL,
                                $parent_class_name
                            ));
                    } else {
                        $parent_class_name =
                            $this->context->getNamespace() . '\\' . $parent_class_name;
                    }
                } elseif ($extends_node->flags & \ast\flags\NAME_RELATIVE) {
                    $parent_class_name =
                        $this->context->getNamespace() . '\\' . $parent_class_name;
                }
                // $extends_node->flags is 0 when it is fully qualified?

                // The name is fully qualified. Make sure it looks
                // like it is
                if (0 !== \strpos($parent_class_name, '\\')) {
                    $parent_class_name = '\\' . $parent_class_name;
                }

                $parent_fqsen = FullyQualifiedClassName::fromStringInContext(
                    $parent_class_name,
                    $this->context
                );

                // Set the parent for the class
                $class->setParentType($parent_fqsen->asType());
            }

            // If the class explicitly sets its overriding extension type,
            // set that on the class
            $inherited_type_option = $comment->getInheritedTypeOption();
            if ($inherited_type_option->isDefined()) {
                $class->setParentType($inherited_type_option->get());
            }

            // Add any implemented interfaces
            if (!empty($node->children['implements'])) {
                $interface_list = (new ContextNode(
                    $this->code_base,
                    $this->context,
                    $node->children['implements']
                ))->getQualifiedNameList();

                foreach ($interface_list as $name) {
                    $class->addInterfaceClassFQSEN(
                        FullyQualifiedClassName::fromFullyQualifiedString(
                            $name
                        )
                    );
                }
            }
        } finally {
            $class->setDidFinishParsing(true);
        }

        return $class_context;
    }

    /**
     * Visit a node with kind `\ast\AST_USE_TRAIT`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitUseTrait(Node $node) : Context
    {
        // Bomb out if we're not in a class context
        $class = $this->getContextClass();

        $trait_fqsen_list = (new ContextNode(
            $this->code_base,
            $this->context,
            $node->children['traits']
        ))->getTraitFQSENList();

        // Add each trait to the class
        foreach ($trait_fqsen_list as $trait_fqsen) {
            $class->addTraitFQSEN($trait_fqsen);
        }

        // Get the adaptations for those traits
        // Pass in the corresponding FQSENs for those traits.
        $trait_adaptations_map = (new ContextNode(
            $this->code_base,
            $this->context,
            $node->children['adaptations']
        ))->getTraitAdaptationsMap($trait_fqsen_list);

        foreach ($trait_adaptations_map as $trait_adaptations) {
            $class->addTraitAdaptations($trait_adaptations);
        }

        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_METHOD`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitMethod(Node $node) : Context
    {
        // Bomb out if we're not in a class context
        $class = $this->getContextClass();
        $context = $this->context;
        $code_base = $this->code_base;

        $method_name = (string)$node->children['name'];

        $method_fqsen = FullyQualifiedMethodName::fromStringInContext(
            $method_name,
            $context
        );

        // Hunt for an available alternate ID if necessary
        $alternate_id = 0;
        while ($code_base->hasMethodWithFQSEN($method_fqsen)) {
            $method_fqsen =
                $method_fqsen->withAlternateId(++$alternate_id);
        }

        $method = Method::fromNode(
            clone($context),
            $code_base,
            $node,
            $method_fqsen
        );

        if ($context->isPHPInternal()) {
            // only for stubs
            foreach (FunctionFactory::functionListFromFunction($method) as $method_variant) {
                \assert($method_variant instanceof Method);
                $class->addMethod($code_base, $method_variant, new None);
            }
        } else {
            $class->addMethod($code_base, $method, new None);
        }

        if ('__construct' === $method_name) {
            $class->setIsParentConstructorCalled(false);
        } elseif ('__invoke' === $method_name) {
            // TODO: More precise callable shape
            $class->addAdditionalType(CallableType::instance(false));
        } elseif ('__toString' === $method_name
            && !$this->context->getIsStrictTypes()
        ) {
            $class->addAdditionalType(StringType::instance(false));
        }


        // Create a new context with a new scope
        return $this->context->withScope(
            $method->getInternalScope()
        );
    }

    /**
     * Visit a node with kind `\ast\AST_PROP_DECL`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitPropDecl(Node $node) : Context
    {
        // Bomb out if we're not in a class context
        $class = $this->getContextClass();
        $docComment = '';
        $first_child_node = $node->children[0] ?? null;
        if ($first_child_node instanceof Node) {
            $docComment = $first_child_node->children['docComment'] ?? '';
        }
        // Get a comment on the property declaration
        $comment = Comment::fromStringInContext(
            $docComment,
            $this->code_base,
            $this->context,
            $node->lineno ?? 0,
            Comment::ON_PROPERTY
        );

        foreach ($node->children as $i => $child_node) {
            // Ignore children which are not property elements
            if (!$child_node
                || $child_node->kind != \ast\AST_PROP_ELEM
            ) {
                continue;
            }
            \assert($child_node instanceof Node, 'expected property element to be Node');

            // If something goes wrong will getting the type of
            // a property, we'll store it as a future union
            // type and try to figure it out later
            $future_union_type = null;

            $default_node = $child_node->children['default'];

            $context_for_property = clone($this->context)->withLineNumberStart($child_node->lineno ?? 0);

            if (!($default_node instanceof Node)) {
                // Get the type of the default
                if ($default_node !== null) {
                    $union_type = Type::fromObject($default_node)->asUnionType();
                } else {
                    // This is a declaration such as `public $x;` with no $default_node
                    // (we don't assume the property is always null, to reduce false positives)
                    $union_type = UnionType::empty();
                }
            } else {
                $future_union_type = new FutureUnionType(
                    $this->code_base,
                    $context_for_property,
                    $default_node
                );
                $union_type = UnionType::empty();
            }

            $property_name = $child_node->children['name'];

            \assert(
                \is_string($property_name),
                'Property name must be a string. '
                . 'Got '
                . print_r($property_name, true)
                . ' at '
                . $context_for_property
            );

            $property_fqsen = FullyQualifiedPropertyName::make(
                $class->getFQSEN(),
                $property_name
            );

            $property = new Property(
                $context_for_property,
                $property_name,
                $union_type,
                $node->flags ?? 0,
                $property_fqsen
            );

            // Add the property to the class
            $class->addProperty($this->code_base, $property, new None);

            $property->setSuppressIssueList(
                $comment->getSuppressIssueList()
            );

            // Look for any @var declarations
            if ($variable = $comment->getVariableList()[$i] ?? null) {
                $original_union_type = $union_type;
                // We try to avoid resolving $future_union_type except when necessary,
                // to avoid issues such as https://github.com/phan/phan/issues/311 and many more.
                if ($future_union_type !== null) {
                    try {
                        $original_union_type = $future_union_type->get();
                        // We successfully resolved the union type. We no longer need $future_union_type
                        $future_union_type = null;
                    } catch (IssueException $e) {
                        // Do nothing
                    }
                    if ($future_union_type === null) {
                        if ($original_union_type->isType(ArrayShapeType::empty())) {
                            $union_type = ArrayType::instance(false)->asUnionType();
                        } elseif ($original_union_type->isType(NullType::instance(false))) {
                            $union_type = UnionType::empty();
                        } else {
                            $union_type = $original_union_type;
                        }
                        // Replace the empty union type with the resolved union type.
                        $property->setUnionType($union_type);
                    }
                }

                if (!$original_union_type->isType(NullType::instance(false))
                    && !$original_union_type->canCastToUnionType($variable->getUnionType())
                    && !$property->hasSuppressIssue(Issue::TypeMismatchProperty)
                ) {
                    $this->emitIssue(
                        Issue::TypeMismatchProperty,
                        $child_node->lineno ?? 0,
                        (string)$original_union_type,
                        (string)$property->getFQSEN(),
                        (string)$variable->getUnionType()
                    );
                }

                // Set the declared type to the doc-comment type and add
                // |null if the default value is null
                $property->setUnionType($property->getUnionType()->withUnionType(
                    $variable->getUnionType()
                ));
            }

            // Don't set 'null' as the type if that's the default
            // given that its the default default.
            if ($union_type->isType(NullType::instance(false))) {
                $union_type = UnionType::empty();
            }

            $property->setIsDeprecated($comment->isDeprecated());
            $property->setIsNSInternal($comment->isNSInternal());

            // Wait until after we've added the (at)var type
            // before setting the future so that calling
            // $property->getUnionType() doesn't force the
            // future to be reified.
            if ($future_union_type instanceof FutureUnionType) {
                $property->setFutureUnionType($future_union_type);
            }
        }

        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_CLASS_CONST_DECL`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     *
     */
    public function visitClassConstDecl(Node $node) : Context
    {
        $class = $this->getContextClass();

        foreach ($node->children as $child_node) {
            \assert($child_node instanceof Node, 'expected class const element to be a Node');
            $name = $child_node->children['name'];
            \assert(\is_string($name));

            $fqsen = FullyQualifiedClassConstantName::fromStringInContext(
                $name,
                $this->context
            );

            // Get a comment on the declaration
            $comment = Comment::fromStringInContext(
                $child_node->children['docComment'] ?? '',
                $this->code_base,
                $this->context,
                $child_node->lineno ?? 0,
                Comment::ON_CONST
            );

            $line_number_start = $child_node->lineno ?? 0;
            $constant = new ClassConstant(
                $this->context
                    ->withLineNumberStart($line_number_start)
                    ->withLineNumberEnd($child_node->endLineno ?? $line_number_start),
                $name,
                UnionType::empty(),
                $node->flags ?? 0,
                $fqsen
            );

            $constant->setIsDeprecated($comment->isDeprecated());
            $constant->setIsNSInternal($comment->isNSInternal());
            $constant->setIsOverrideIntended($comment->isOverrideIntended());
            $constant->setSuppressIssueList($comment->getSuppressIssueList());

            $value_node = $child_node->children['value'];
            if ($value_node instanceof Node) {
                $constant->setFutureUnionType(
                    new FutureUnionType(
                        $this->code_base,
                        $this->context,
                        $value_node
                    )
                );
            } else {
                $constant->setUnionType(Type::fromObject($value_node)->asUnionType());
            }
            $constant->setNodeForValue($value_node);

            $class->addConstant(
                $this->code_base,
                $constant
            );
        }

        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_CONST`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitConstDecl(Node $node) : Context
    {
        foreach ($node->children as $child_node) {
            \assert($child_node instanceof Node);

            $this->addConstant(
                $child_node,
                $child_node->children['name'],
                $child_node->children['value'],
                $child_node->flags ?? 0,
                $child_node->children['docComment'] ?? ''
            );
        }

        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_FUNC_DECL`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitFuncDecl(Node $node) : Context
    {
        $function_name = (string)$node->children['name'];
        $context = $this->context;
        $code_base = $this->code_base;

        // Hunt for an un-taken alternate ID
        $alternate_id = 0;
        $function_fqsen = null;
        do {
            $function_fqsen =
                FullyQualifiedFunctionName::fromStringInContext(
                    $function_name,
                    $context
                )
                ->withNamespace($context->getNamespace())
                ->withAlternateId($alternate_id++);
        } while ($code_base->hasFunctionWithFQSEN(
            $function_fqsen
        ));

        $func = Func::fromNode(
            $context
                ->withLineNumberStart($node->lineno ?? 0)
                ->withLineNumberEnd($node->endLineno ?? 0),
            $code_base,
            $node,
            $function_fqsen
        );

        if ($context->isPHPInternal()) {
            // only for stubs
            foreach (FunctionFactory::functionListFromFunction($func) as $func_variant) {
                \assert($func_variant instanceof Func);
                $code_base->addFunction($func_variant);
            }
        } else {
            $code_base->addFunction($func);
        }

        // Send the context into the function and reset the scope
        $context = $this->context->withScope(
            $func->getInternalScope()
        );

        return $context;
    }

    /**
     * Visit a node with kind `\ast\AST_CLOSURE`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitClosure(Node $node) : Context
    {
        $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext(
            $this->context->withLineNumberStart($node->lineno ?? 0),
            $node
        );

        $func = Func::fromNode(
            $this->context,
            $this->code_base,
            $node,
            $closure_fqsen
        );

        $this->code_base->addFunction($func);

        // Send the context into the function and reset the scope
        // (E.g. to properly check for the presence of `return` statements.
        $context = $this->context->withScope(
            $func->getInternalScope()
        );

        return $context;
    }

    /**
     * Visit a node with kind `\ast\AST_CALL`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitCall(Node $node) : Context
    {
        // If this is a call to a method that indicates that we
        // are treating the method in scope as a varargs method,
        // then set its optional args to something very high so
        // it can be called with anything.
        $expression = $node->children['expr'];

        if ($expression instanceof Node && $expression->kind === \ast\AST_NAME) {
            $function_name = \strtolower($expression->children['name']);
            if (\in_array($function_name, [
                'func_get_args', 'func_get_arg', 'func_num_args'
            ], true)) {
                if ($this->context->isInFunctionLikeScope()) {
                    $this->context->getFunctionLikeInScope($this->code_base)
                                  ->setNumberOfOptionalParameters(FunctionInterface::INFINITE_PARAMETERS);
                }
            } elseif ($function_name === 'define') {
                // TODO: infer constant type from literal, string concatenation operators, etc?
                $args = $node->children['args'];
                if ($args->kind === \ast\AST_ARG_LIST
                    && isset($args->children[0])
                    && \is_string($args->children[0])
                ) {
                    $this->addConstant(
                        $node,
                        $args->children[0],
                        $args->children[1] ?? null,
                        0,
                        ''
                    );
                }
            } elseif ($function_name === 'class_alias') {
                if (Config::getValue('enable_class_alias_support') && $this->context->isInGlobalScope()) {
                    $this->recordClassAlias($node);
                }
            }
        }
        if (Config::get_backward_compatibility_checks()) {
            $this->analyzeBackwardCompatibility($node);

            foreach ($node->children['args']->children as $arg_node) {
                if ($arg_node instanceof Node) {
                    $this->analyzeBackwardCompatibility($arg_node);
                }
            }
        }
        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_STATIC_CALL`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitStaticCall(Node $node) : Context
    {
        $call = $node->children['class'];

        if ($call instanceof Node && $call->kind == \ast\AST_NAME) {
            $func_name = \strtolower($call->children['name']);
            if ($func_name == 'parent') {
                // Make sure it is not a crazy dynamic parent method call
                if (!($node->children['method'] instanceof Node)) {
                    $meth = \strtolower($node->children['method']);

                    if ($meth == '__construct') {
                        $class = $this->getContextClass();
                        $class->setIsParentConstructorCalled(true);
                    }
                }
            }
        }

        return $this->context;
    }

    /**
     * Analyze a node for syntax backward compatibility, if that option is enabled
     * @return void
     */
    private function analyzeBackwardCompatibility(Node $node)
    {
        if (Config::get_backward_compatibility_checks()) {
            (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ))->analyzeBackwardCompatibility();
        }
    }

    /**
     * Visit a node with kind `\ast\AST_RETURN`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     *
     * TODO: Defer analysis of the inside of methods until the class gets hydrated.
     */
    public function visitReturn(Node $node) : Context
    {
        $this->analyzeBackwardCompatibility($node);

        // Make sure we're actually returning from a method.
        if (!$this->context->isInFunctionLikeScope()) {
            return $this->context;
        }

        // Get the method/function/closure we're in
        $method = $this->context->getFunctionLikeInScope(
            $this->code_base
        );

        \assert(
            !empty($method),
            "We're supposed to be in either method or closure scope."
        );

        // Mark the method as returning something
        if (($node->children['expr'] ?? null) !== null) {
            $method->setHasReturn(true);
        }

        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_YIELD`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     *
     * TODO: Defer analysis of the inside of methods until the method/function gets hydrated.
     */
    public function visitYield(Node $node) : Context
    {
        return $this->analyzeYield($node);
    }

    /**
     * Visit a node with kind `\ast\AST_YIELD_FROM`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitYieldFrom(Node $node) : Context
    {
        return $this->analyzeYield($node);
    }


    /**
     * Visit a node with kind `\ast\AST_YIELD_FROM` or kind `\ast_YIELD`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    private function analyzeYield(Node $node) : Context
    {
        $this->analyzeBackwardCompatibility($node);

        // Make sure we're actually returning from a method.
        if (!$this->context->isInFunctionLikeScope()) {
            return $this->context;
        }

        // Get the method/function/closure we're in
        $method = $this->context->getFunctionLikeInScope(
            $this->code_base
        );

        \assert(
            !empty($method),
            "We're supposed to be in either method or closure scope."
        );

        // Mark the method as yielding something (and returning a generator)
        $method->setHasYield(true);
        $method->setHasReturn(true);

        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_PRINT`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitPrint(Node $node) : Context
    {
        // Analyze backward compatibility for the arguments of this print statement.
        $this->analyzeBackwardCompatibility($node);
        return $this->context;
    }
    /**
     * Visit a node with kind `\ast\AST_ECHO`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitEcho(Node $node) : Context
    {
        // Analyze backward compatibility for the arguments of this echo statement.
        $this->analyzeBackwardCompatibility($node);
        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_METHOD_CALL`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitMethodCall(Node $node) : Context
    {
        // Analyze backward compatibility for the arguments of this method call
        $this->analyzeBackwardCompatibility($node);
        return $this->context;
    }

    public function visitAssign(Node $node) : Context
    {
        if (!Config::get_backward_compatibility_checks()) {
            return $this->context;
        }
        // Analyze the assignment for compatibility with some
        // breaking changes betweeen PHP5 and PHP7.
        $var_node = $node->children['var'];
        if ($var_node instanceof Node) {
            $this->analyzeBackwardCompatibility($var_node);
        }
        $expr_node = $node->children['expr'];
        if ($expr_node instanceof Node) {
            $this->analyzeBackwardCompatibility($expr_node);
        }
        return $this->context;
    }

    public function visitDim(Node $node) : Context
    {
        if (!Config::get_backward_compatibility_checks()) {
            return $this->context;
        }

        if (!($node->children['expr'] instanceof Node
            && ($node->children['expr']->children['name'] ?? null) instanceof Node)
        ) {
            return $this->context;
        }

        // check for $$var[]
        if ($node->children['expr']->kind == \ast\AST_VAR
            && $node->children['expr']->children['name']->kind == \ast\AST_VAR
        ) {
            $temp = $node->children['expr']->children['name'];
            $depth = 1;
            while ($temp instanceof Node) {
                \assert(
                    isset($temp->children['name']),
                    "Expected to find a name in context, something else found."
                );
                $temp = $temp->children['name'];
                $depth++;
            }
            $dollars = str_repeat('$', $depth);
            $ftemp = new \SplFileObject($this->context->getFile());
            $ftemp->seek($node->lineno-1);
            $line = $ftemp->current();
            \assert(\is_string($line));
            unset($ftemp);
            if (\strpos($line, '{') === false
                || \strpos($line, '}') === false
            ) {
                $this->emitIssue(
                    Issue::CompatibleExpressionPHP7,
                    $node->lineno ?? 0,
                    "{$dollars}{$temp}[]"
                );
            }

        // $foo->$bar['baz'];
        } elseif (!empty($node->children['expr']->children[1])
            && ($node->children['expr']->children[1] instanceof Node)
            && ($node->children['expr']->kind == \ast\AST_PROP)
            && ($node->children['expr']->children[0]->kind == \ast\AST_VAR)
            && ($node->children['expr']->children[1]->kind == \ast\AST_VAR)
        ) {
            $ftemp = new \SplFileObject($this->context->getFile());
            $ftemp->seek($node->lineno-1);
            $line = $ftemp->current();
            \assert(\is_string($line));
            unset($ftemp);
            if (\strpos($line, '{') === false
                || \strpos($line, '}') === false
            ) {
                $this->emitIssue(
                    Issue::CompatiblePHP7,
                    $node->lineno ?? 0
                );
            }
        }

        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_DECLARE`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitDeclare(Node $node) : Context
    {
        $declares = $node->children['declares'];
        $name = $declares->children[0]->children['name'];
        $value = $declares->children[0]->children['value'];
        if ('strict_types' === $name) {
            return $this->context->withStrictTypes($value);
        }

        return $this->context;
    }

    /**
     * @param Node $node
     * The node where the constant was found
     *
     * @param string $name
     * The name of the constant
     *
     * @param Node|mixed $value
     * Either a node or a constant to be used as the value of
     * the constant.
     *
     * @param int $flags
     * Any flags on the definition of the constant
     *
     * @param string $comment_string
     * A possibly empty comment string on the declaration
     *
     * @return void
     */
    private function addConstant(
        Node $node,
        string $name,
        $value,
        int $flags,
        string $comment_string
    ) {
        // Give it a fully-qualified name
        $fqsen = FullyQualifiedGlobalConstantName::fromStringInContext(
            $name,
            $this->context
        );

        // Create the constant
        $constant = new GlobalConstant(
            $this->context
                ->withLineNumberStart($node->lineno ?? 0),
            $name,
            UnionType::empty(),
            $flags,
            $fqsen
        );

        // Get a comment on the declaration
        $comment = Comment::fromStringInContext(
            $comment_string,
            $this->code_base,
            $this->context,
            $node->lineno ?? 0,
            Comment::ON_CONST
        );

        if ($value instanceof Node) {
            $constant->setFutureUnionType(
                new FutureUnionType(
                    $this->code_base,
                    $this->context,
                    $value
                )
            );
        } else {
            $constant->setUnionType(Type::fromObject($value)->asUnionType());
        }

        $constant->setNodeForValue($value);

        $constant->setIsDeprecated($comment->isDeprecated());
        $constant->setIsNSInternal($comment->isNSInternal());

        $this->code_base->addGlobalConstant(
            $constant
        );
    }

    /**
     * @return Clazz
     * Get the class on this scope or fail real hard
     */
    private function getContextClass() : Clazz
    {
        \assert(
            $this->context->isInClassScope(),
            "Must be in class scope"
        );
        return $this->context->getClassInScope($this->code_base);
    }

    /**
     * Return the existence of a class_alias from one FQSEN to the other.
     * Modifies $this->codebase if successful.
     *
     * Supports 'MyClass' and MyClass::class
     *
     * @param Node $node - An AST_CALL node with name 'class_alias' to attempt to resolve
     * @return void
     */
    private function recordClassAlias(Node $node)
    {
        $args = $node->children['args']->children;
        if (\count($args) < 2 || \count($args) > 3) {
            return;
        }
        $code_base = $this->code_base;
        $context = $this->context;
        try {
            $original_fqsen = (new ContextNode($code_base, $context, $args[0]))->resolveClassNameInContext();
            $alias_fqsen = (new ContextNode($code_base, $context, $args[1]))->resolveClassNameInContext();
        } catch (IssueException $exception) {
            Issue::maybeEmitInstance(
                $code_base,
                $context,
                $exception->getIssueInstance()
            );
            return;
        }

        if ($original_fqsen === null || $alias_fqsen === null) {
            return;
        }

        // Add the class alias during parse phase.
        // Figure out if any of the aliases are wrong after analysis phase.
        $this->code_base->addClassAlias($original_fqsen, $alias_fqsen, $context, $node->lineno ?? 0);
    }

    /**
     * Visit a node with kind `\ast\AST_NAMESPACE`
     * Store the maps for use statements in the CodeBase to use later during analysis.
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new context resulting from parsing the node
     *
     * @suppress PhanAccessMethodInternal
     */
    public function visitNamespace(Node $node) : Context
    {
        $context = $this->context;
        $this->code_base->addParsedNamespaceMap($context->getFile(), $context->getNamespace(), $context->getNamespaceId(), $context->getNamespaceMap());
        return parent::visitNamespace($node);
    }

    // common no-ops
    /** @return Context */
    public function visitArrayElem(Node $node)
    {
        return $this->context;
    }
    /** @return Context */
    public function visitVar(Node $node)
    {
        return $this->context;
    }
    /** @return Context */
    public function visitName(Node $node)
    {
        return $this->context;
    }
    /** @return Context */
    public function visitArgList(Node $node)
    {
        return $this->context;
    }
    /** @return Context */
    public function visitStmtList(Node $node)
    {
        return $this->context;
    }
    /** @return Context */
    public function visitProp(Node $node)
    {
        return $this->context;
    }
    /** @return Context */
    public function visitArray(Node $node)
    {
        return $this->context;
    }
    /** @return Context */
    public function visitBinaryOp(Node $node)
    {
        return $this->context;
    }
}
<?php declare(strict_types=1);
namespace Phan;

use Phan\Library\Hasher\Consistent;
use Phan\Library\Hasher\Sequential;

/**
 * This determines the order in which files will be analyzed.
 * Affected by `consistent_hashing_file_order` and `randomize_file_order`.
 * By default, files are analyzed in the same order as `.phan/config.php`
 */
class Ordering
{
    /** @var CodeBase */
    private $code_base;

    /**
     * @param CodeBase $code_base
     * The entire code base used to choose a file
     * analysis ordering.
     */
    public function __construct(CodeBase $code_base)
    {
        $this->code_base = $code_base;
    }

    /**
     * @param int $process_count
     * The number of processes we'd like to divide work up
     * amongst.
     *
     * @param array<int,string> $analysis_file_list
     * A list of files that should be analyzed which will be
     * used to ignore any files outside of the list and to
     * draw from for any missing files.
     *
     * @return array<int,array<int,string>>
     * A map from process_id to a list of files to be analyzed
     * on that process in stable ordering.
     *
     * @suppress PhanPartialTypeMismatchReturn TODO: investigate why array<int,null> is inferred as possible?
     */
    public function orderForProcessCount(
        int $process_count,
        array $analysis_file_list
    ) : array {

        \assert(
            $process_count > 0,
            "The process count must be greater than zero."
        );

        if (Config::getValue('randomize_file_order')) {
            $random_proc_file_map = [];
            $i = 0;
            shuffle($analysis_file_list);
            foreach ($analysis_file_list as $i => $file) {
                $random_proc_file_map[$i++ % $process_count][] = $file;
            }
            return $random_proc_file_map;
        }

        // Construct a Hasher implementation based on config.
        if (Config::getValue('consistent_hashing_file_order')) {
            sort($analysis_file_list, SORT_STRING);
            $hasher = new Consistent($process_count);
        } else {
            $hasher = new Sequential($process_count);
        }

        // Create a Set from the file list
        $analysis_file_map = [];
        foreach ($analysis_file_list as $file) {
            $analysis_file_map[$file] = true;
        }

        // A map from the root of an object hierarchy to all
        // elements within that hierarchy
        $root_fqsen_list = [];

        $file_names_for_classes = [];

        // Iterate over each class extracting files
        foreach ($this->code_base->getUserDefinedClassMap() as $class) {
            // Get the name of the file associated with the class
            $file_name = $class->getContext()->getFile();

            // Ignore any files that are not to be analyzed
            if (!isset($analysis_file_map[$file_name])) {
                continue;
            }
            unset($analysis_file_map[$file_name]);
            $file_names_for_classes[$file_name] = $class;
        }

        if (Config::getValue('consistent_hashing_file_order')) {
            ksort($file_names_for_classes, SORT_STRING);
        }

        foreach ($file_names_for_classes as $file_name => $class) {
            // Get the class's depth in its object hierarchy and
            // the FQSEN of the object at the root of its hierarchy
            $hierarchy_depth = $class->getHierarchyDepth($this->code_base);
            $hierarchy_root = $class->getHierarchyRootFQSEN($this->code_base);

            // Create a bucket for this root if it doesn't exist
            if (empty($root_fqsen_list[(string)$hierarchy_root])) {
                $root_fqsen_list[(string)$hierarchy_root] = [];
            }

            // Append this {file,depth} pair to the hierarchy
            // root
            $root_fqsen_list[(string)$hierarchy_root][] = [
                'file'  => $file_name,
                'depth' => $hierarchy_depth,
            ];
        }

        // Create a map from processor_id to the list of files
        // to be analyzed on that processor
        $processor_file_list_map = [];

        // Sort the set of files with a given root by their
        // depth in the hierarchy
        foreach ($root_fqsen_list as $root_fqsen => $list) {
            // Sort first by depth, and break ties by file name lexicographically
            // (usort is not a stable sort).
            usort($list, function (array $a, array $b) {
                return ($a['depth'] <=> $b['depth']) ?:
                       strcmp($a['file'], $b['file']);
            });

            // Choose which process this file list will be
            // run on
            $process_id = $hasher->getGroup((string)$root_fqsen);

            // Append each file to this process list
            foreach ($list as $item) {
                $processor_file_list_map[$process_id][] = $item['file'];
            }
        }

        // Distribute any remaining files without classes evenly
        // between the processes
        $hasher->reset();
        foreach (array_keys($analysis_file_map) as $file) {
            // Choose which process this file list will be
            // run on
            $process_id = $hasher->getGroup((string)$file);

            $processor_file_list_map[$process_id][] = $file;
        }

        return $processor_file_list_map;
    }
}
<?php declare(strict_types=1);
namespace Phan;

/**
 * A utility trait to memoize (cache) the result of instance methods and static methods.
 */
trait Memoize
{
    use Profile;

    /**
     * @var array
     * A map from key to memoized values
     */
    private $memoized_data = [];

    /**
     * Memoize the result of $fn(), saving the result
     * with key $key.
     *
     * @param string $key
     * The key to use for storing the result of the
     * computation.
     *
     * @param \Closure $fn
     * A function to compute only once for the given
     * $key.
     *
     * @return mixed
     * The result of the given computation is returned
     */
    protected function memoize(string $key, \Closure $fn)
    {
        if (!\array_key_exists($key, $this->memoized_data)) {
            $this->memoized_data[$key] = $fn();
        }

        return $this->memoized_data[$key];
    }

    /**
     * @param string $key
     * A unique key to test to see if its been seen before
     *
     * @return bool
     * True if this is the first time this function has been
     * called on this class with this key.
     */
    protected function isFirstExecution(string $key) : bool
    {
        if (!\array_key_exists($key, $this->memoized_data)) {
            $this->memoized_data[$key] = true;
            return true;
        }

        return false;
    }

    /**
     * Memoize the result of $fn(), saving the result
     * with key $key.
     *
     * @param string $key
     * The key to use for storing the result of the
     * computation.
     *
     * @param \Closure $fn
     * A function to compute only once for the given
     * $key.
     *
     * @return mixed
     * The result of the given computation is returned
     */
    protected static function memoizeStatic(string $key, \Closure $fn)
    {
        static $memoized_data = [];

        if (!\array_key_exists($key, $memoized_data)) {
            $memoized_data[$key] = $fn();
        }

        return $memoized_data[$key];
    }

    /**
     * Delete all memoized data
     *
     * @return void
     */
    protected function memoizeFlushAll()
    {
        $this->memoized_data = [];
    }
}
<?php declare(strict_types=1);
namespace Phan;

use Phan\Daemon\Request;
use Phan\Daemon\Transport\StreamResponder;
use Phan\Daemon\ExitException;
use Closure;

/**
 * A simple analyzing daemon that can be used by IDEs. (see `phan_client`)
 * Accepts requests (Currently only JSON blobs) over a unix socket or TCP sockets.
 *
 * @see \Phan\LanguageServer\LanguageServer for an implementation of the Language Server Protocol
 */
class Daemon
{
    /**
     * This creates an analyzing daemon, to be used by IDEs.
     * Format:
     *
     * - Read over TCP socket, e.g. with JSON
     * - Respond over TCP socket, e.g. with JSON
     *
     * @param CodeBase $code_base (Must have undo tracker enabled)
     *
     * @param Closure $file_path_lister
     * Returns string[] - A list of files to scan. This may be different from the previous contents.
     *
     * @return Request|null - A writeable request, which has been fully read from.
     * Callers should close after they are finished writing.
     *
     * @suppress PhanPluginUnusedVariable https://github.com/mattriverm/PhanUnusedVariable/issues/30
     */
    public static function run(CodeBase $code_base, Closure $file_path_lister)
    {
        if (Config::getValue('language_server_use_pcntl_fallback')) {
            self::runWithoutPcntl($code_base, $file_path_lister);
            // Not reachable
            exit(0);
        }
        \assert($code_base->isUndoTrackingEnabled());

        // example requests over TCP
        // Assumes that clients send and close the their requests quickly, then wait for a response.

        // {"method":"analyze","files":["/path/to/file1.php","/path/to/file2.php"]}

        $socket_server = self::createDaemonStreamSocketServer();
        // TODO: Limit the maximum number of active processes to a small number(4?)
        // TODO: accept SIGCHLD when child terminates, somehow?
        try {
            $gotSignal = false;
            pcntl_signal(SIGCHLD, function (...$args) use (&$gotSignal) {
                $gotSignal = true;
                Request::childSignalHandler(...$args);
            });
            while (true) {
                $gotSignal = false;  // reset this.
                // We get an error from stream_socket_accept. After the RuntimeException is thrown, pcntl_signal is called.
                /**
                 * @param int $severity
                 * @param string $message
                 * @param string $file
                 * @param int $line
                 * @return bool
                 */
                $previousErrorHandler = set_error_handler(function ($severity, $message, $file, $line) use (&$previousErrorHandler) {
                    self::debugf("In new error handler '$message'");
                    if (!preg_match('/stream_socket_accept/i', $message)) {
                        return $previousErrorHandler($severity, $message, $file, $line);
                    }
                    throw new \RuntimeException("Got signal");
                });

                $conn = false;
                try {
                    $conn = stream_socket_accept($socket_server, -1);
                } catch (\RuntimeException $e) {
                    self::debugf("Got signal");
                    pcntl_signal_dispatch();
                    self::debugf("done processing signals");
                    if ($gotSignal) {
                        continue;  // Ignore notices from stream_socket_accept if it's due to being interrupted by a child process terminating.
                    }
                } finally {
                    restore_error_handler();
                }

                if (!\is_resource($conn)) {
                    // If we didn't get a connection, and it wasn't due to a signal from a child process, then stop the daemon.
                    break;
                }
                $request = Request::accept(
                    $code_base,
                    $file_path_lister,
                    new StreamResponder($conn, true),
                    true
                );
                if ($request instanceof Request) {
                    return $request;  // We forked off a worker process successfully, and this is the worker process
                }
            }
            error_log("Stopped accepting connections");
        } finally {
            restore_error_handler();
        }
        return null;
    }

    /**
     * @return void - A writeable request, which has been fully read from.
     * Callers should close after they are finished writing.
     *
     * @suppress PhanPluginUnusedVariable https://github.com/mattriverm/PhanUnusedVariable/issues/30
     */
    private static function runWithoutPcntl(CodeBase $code_base, Closure $file_path_lister)
    {
        // This is a single threaded server, it only analyzes one TCP request at a time
        $socket_server = self::createDaemonStreamSocketServer();
        try {
            while (true) {
                $gotSignal = false;  // reset this.
                // We get an error from stream_socket_accept. After the RuntimeException is thrown, pcntl_signal is called.
                $previousErrorHandler = set_error_handler(function ($severity, $message, $file, $line) use (&$previousErrorHandler) {
                    self::debugf("In new error handler '$message'");
                    if (!preg_match('/stream_socket_accept/i', $message)) {
                        return $previousErrorHandler($severity, $message, $file, $line);
                    }
                    throw new \RuntimeException("Got signal");
                });

                $conn = false;
                try {
                    $conn = stream_socket_accept($socket_server, -1);
                } catch (\RuntimeException $e) {
                    self::debugf("Got signal");
                    pcntl_signal_dispatch();
                    self::debugf("done processing signals");
                    if ($gotSignal) {
                        continue;  // Ignore notices from stream_socket_accept if it's due to being interrupted by a child process terminating.
                    }
                } finally {
                    restore_error_handler();
                }

                if (!\is_resource($conn)) {
                    // If we didn't get a connection, and it wasn't due to a signal from a child process, then stop the daemon.
                    break;
                }
                // We **are** the only process. Imitate the worker proces
                $request = Request::accept(
                    $code_base,
                    $file_path_lister,
                    new StreamResponder($conn, true),
                    false  // This is not a fork, do not call exit($status)
                );
                if ($request instanceof Request) {
                    self::debugf("Calling analyzeDaemonRequestOnMainThread\n");
                    // This did not fork, and will not fork (Unless --processes N was used)
                    self::analyzeDaemonRequestOnMainThread($code_base, $request);
                    // Force garbage collection in case it didn't respond
                    $request = null;
                    self::debugf("Finished call to analyzeDaemonRequestOnMainThread\n");
                    // We did not terminate, we keep accepting
                }
            }
            error_log("Stopped accepting connections");
        } finally {
            restore_error_handler();
        }
    }

    /**
     * @return void
     */
    private static function analyzeDaemonRequestOnMainThread(CodeBase $code_base, Request $request)
    {
        $restore_point = $code_base->createRestorePoint();

        // Stop tracking undo operations, now that the parse phase is done.
        // TODO: Save and reset $code_base in place
        $analyze_file_path_list = $request->filterFilesToAnalyze($code_base->getParsedFilePathList());
        $code_base->disableUndoTracking();
        Phan::setPrinter($request->getPrinter());
        if (count($analyze_file_path_list) === 0) {
            // Nothing to do, don't start analysis
            $request->respondWithNoFilesToAnalyze();  // respond and exit.
            return;
        }
        $temporary_file_mapping = $request->getTemporaryFileMapping();

        try {
            Phan::finishAnalyzingRemainingStatements($code_base, $request, $analyze_file_path_list, $temporary_file_mapping);
        } catch (ExitException $e) {
            // This is normal and expected, do nothing
        } finally {
            $code_base->restoreFromRestorePoint($restore_point);
        }
    }

    /**
     * @return resource (resource is not a reserved keyword)
     */
    private static function createDaemonStreamSocketServer()
    {
        $listen_url = null;
        if (Config::getValue('daemonize_socket')) {
            $listen_url = 'unix://' . Config::getValue('daemonize_socket');
        } elseif (Config::getValue('daemonize_tcp_port')) {
            $listen_url = sprintf('tcp://127.0.0.1:%d', Config::getValue('daemonize_tcp_port'));
        } else {
            throw new \InvalidArgumentException("Should not happen, no port/socket for daemon to listen on.");
        }
        printf(
            "Listening for Phan analysis requests at %s\nAwaiting analysis requests for directory %s\n",
            $listen_url,
            var_export(Config::getProjectRootDirectory(), true)
        );
        $socket_server = stream_socket_server($listen_url, $errno, $errstr);
        if (!$socket_server) {
            error_log("Failed to create unix socket server $listen_url: $errstr ($errno)\n");
            exit(1);
        }
        return $socket_server;
    }

    /**
     * Debug (non-error) statement related to the daemon.
     * Uncomment this when debugging issues (E.g. changes not being picked up)
     *
     * @param string $format - printf style format string
     * @param mixed ...$args - printf args
     *
     * @suppress PhanPluginUnusedPublicMethodArgument (Currently commented out)
     */
    public static function debugf(string $format, ...$args)
    {
        /*
        if (count($args) > 0) {
            $message = sprintf($format, ...$args);
        } else {
            $message = $format;
        }
        error_log($message);
        */
    }
}
<?php declare(strict_types=1);
namespace Phan\Library\Hasher;

use Phan\Library\Hasher;

/**
 * Hasher implementation mapping keys to sequential groups (first key to 0, second key to 1, looping back to 0)
 * getGroup() is called exactly once on each string to be hashed.
 */
class Sequential implements Hasher
{
    /** @var int */
    protected $counter;
    /** @var int */
    protected $groupCount;

    public function __construct(int $groupCount)
    {
        $this->counter = 1;
        $this->groupCount = $groupCount;
    }

    /**
     * @param string $key (Used by sibling class Consistent) (@phan-unused-param)
     * @return int - an integer between 0 and $this->groupCount - 1, inclusive
     */
    public function getGroup(string $key) : int
    {
        return ($this->counter++) % $this->groupCount;
    }

    /**
     * Resets counter
     * @return void
     */
    public function reset()
    {
        $this->counter = 1;
    }
}
<?php declare(strict_types=1);
namespace Phan\Library\Hasher;

use Phan\Library\Hasher;

/**
 * Hasher implementation mapping keys to sequential groups (first key to 0, second key to 1, looping back to 0)
 * getGroup() is called exactly once on each string to be hashed.
 * See https://en.wikipedia.org/wiki/Consistent_hashing
 */
class Consistent implements Hasher
{
    const VIRTUAL_COPY_COUNT = 16;  // Larger number means a more balanced distribution.
    const MAX = 0x40000000;  // i.e. (1 << 30)
    /** @var array<int,int> - Sorted list of hash values, for binary search. */
    protected $hashRingIds;
    /** @var array<int,int> - Groups corresponding to hash values in hashRingIds */
    protected $hashRingGroups;

    public function __construct(int $groupCount)
    {
        $map = self::generateMap($groupCount);
        $hashRingIds = [];
        $hashRingGroups = [];
        foreach ($map as $key => $group) {
            $hashRingIds[] = $key;
            $hashRingGroups[] = $group;
        }
        // ... and make the map wrap around.
        $hashRingIds[] = self::MAX - 1;
        $hashRingGroups[] = \reset($map) ?: 0;

        $this->hashRingIds = $hashRingIds;
        $this->hashRingGroups = $hashRingGroups;
    }

    /**
     * @return array<int,int> maps points in the field to the corresponding group (for consistent hashing)
     */
    private static function generateMap(int $groupCount)
    {
        $map = [];
        for ($group = 0; $group < $groupCount; $group++) {
            foreach (self::getHashesForGroup($group) as $hash) {
                $map[$hash] = $group;
            }
        }
        \ksort($map);
        return $map;
    }
    /**
     * Do a binary search in the consistent hashing ring to find the group.
     * @return int - an integer between 0 and $this->groupCount - 1, inclusive
     * @suppress PhanPartialTypeMismatchReturn
     */
    public function getGroup(string $key) : int
    {
        $searchHash = self::generateKeyHash($key);
        $begin = 0;
        $end = count($this->hashRingIds) - 1;
        while ($begin <= $end) {
            $pos = $begin + (($end - $begin) >> 1);
            $curVal = $this->hashRingIds[$pos];
            if ($searchHash > $curVal) {
                $begin = $pos + 1;
            } else {
                $end = $pos - 1;
            }
        }
        // Postcondition: $this->hashRingIds[$begin] >= $searchHash, and $this->hashRingIds[$begin - 1] does not exist or is less than $searchHash.

        // Fetch the group corresponding to that hash in the hash ring.
        return $this->hashRingGroups[$begin];
    }

    /**
     * No-op reset
     * @return void
     */
    public function reset()
    {
    }

    /**
     * @return array<int,int>
     */
    public static function getHashesForGroup(int $group) : array
    {
        $hashes = [];
        for ($i = 0; $i < self::VIRTUAL_COPY_COUNT; $i++) {
            $hashes[$i] = self::generateKeyHash("${i}@$group");
        }
        return $hashes;
    }

    /**
     * Returns a 30-bit signed integer (i.e. in the range [0, self::MAX-1])
     * Designed to work on 32-bit php installations as well.
     */
    public static function generateKeyHash(string $material) : int
    {
        $bits = \md5($material);
        $result = ((\intval($bits[0], 16) & 3) << 28) ^ \intval(\substr($bits, 1, 7), 16);
        return $result;
    }
}
<?php declare(strict_types=1);

namespace Phan\Library;

use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;

/**
 * A PSR-3 logger for the composer xdebug handler
 */
class StderrLogger implements LoggerInterface
{
    use LoggerTrait;

    /**
     * @param string $level
     * @param string $message
     * @param array $unused_context
     * @return void
     *
     * @override
     */
    public function log($level, $message, array $unused_context = [])
    {
        fprintf(STDERR, "[%s] %s\n", $level, $message);
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * @inherits Option<null>
 */
class None extends Option
{
    /**
     * Get a new instance of nothing
     */
    public function __construct()
    {
    }

    /**
     * @return bool
     */
    public function isDefined() : bool
    {
        return false;
    }

    /**
     * @param mixed $else
     * @return mixed
     */
    public function getOrElse($else)
    {
        return $else;
    }

    /**
     * @return null
     */
    public function get()
    {
        throw new \Exception("Cannot call get on None");
    }

    /**
     * @return string
     * A string representation of this object
     */
    public function __tostring() : string
    {
        return 'None()';
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * A tuple of 3 elements.
 *
 * @template T0
 * The type of element zero
 *
 * @template T1
 * The type of element one
 *
 * @template T2
 * The type of element one
 *
 * @inherits Tuple2<T0, T1>
 */
class Tuple3 extends Tuple2
{
    /** @var int */
    const ARITY = 3;

    /** @var T2 */
    public $_2;

    /**
     * @param T0 $_0
     * The 0th element
     *
     * @param T1 $_1
     * The 1st element
     *
     * @param T2 $_2
     * The 2nd element
     */
    public function __construct($_0, $_1, $_2)
    {
        parent::__construct($_0, $_1);
        $this->_2 = $_2;
    }

    /**
     * @return array
     * An array of all elements in this tuple.
     */
    public function toArray() : array
    {
        return [
            $this->_0,
            $this->_1,
            $this->_2,
        ];
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * A tuple of 4 elements.
 *
 * @template T0
 * The type of element zero
 *
 * @template T1
 * The type of element one
 *
 * @template T2
 * The type of element two
 *
 * @template T3
 * The type of element three
 *
 * @inherits Tuple3<T0, T1, T2>
 */
class Tuple4 extends Tuple3
{
    /** @var int */
    const ARITY = 4;

    /** @var T3 */
    public $_3;

    /**
     * @param T0 $_0
     * The 0th element
     *
     * @param T1 $_1
     * The 1st element
     *
     * @param T2 $_2
     * The 2nd element
     *
     * @param T3 $_3
     * The 3rd element
     */
    public function __construct($_0, $_1, $_2, $_3)
    {
        parent::__construct($_0, $_1, $_2);
        $this->_3 = $_3;
    }

    /**
     * @return array
     * An array of all elements in this tuple.
     */
    public function toArray() : array
    {
        return [
            $this->_0,
            $this->_1,
            $this->_2,
            $this->_3,
        ];
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * A map from object to object with key comparisons
 * based on spl_object_hash.
 */
class Map extends \SplObjectStorage
{

    /**
     * We redefine the key to be the actual key rather than
     * the index of the key
     *
     * @return object
     * @suppress PhanParamSignatureMismatchInternal - This is deliberately changing the phpdoc return type.
     */
    public function key()
    {
        return parent::current();
    }

    /**
     * We redefine the current value to the current value rather
     * than the current key
     */
    public function current()
    {
        return $this->offsetGet(parent::current());
    }

    /**
     * @param \Closure $key_closure
     * A closure that maps each key of this map
     * to a new key
     *
     * @param \Closure $value_closure
     * A closure that maps each value of this map
     * to a new value.
     *
     * @return Map
     * A new map containing the mapped keys and
     * values
     */
    public function keyValueMap(\Closure $key_closure, \Closure $value_closure)
    {
        $map = new Map;
        foreach ($this as $key => $value) {
            // TODO: Don't infer $map[$key] = ...; as making $map possibly an array
            $map->offsetSet($key_closure($key), $value_closure($value));
        }
        return $map;
    }

    /**
     * @return Map
     * A new map with each key and value cloned
     * @suppress PhanUnreferencedPublicMethod possibly useful but currently unused
     */
    public function deepCopy() : Map
    {
        $clone = function ($element) {
            return clone($element);
        };
        return $this->keyValueMap($clone, $clone);
    }

    /**
     * @return Map
     * A new map with each value cloned (keys remain uncloned)
     */
    public function deepCopyValues() : Map
    {
        $map = new Map;
        foreach ($this as $key => $value) {
            $map->offsetSet($key, clone($value));
        }
        return $map;
    }

    /**
     * @return Set
     * A new set with the unique values from this map.
     * Precondition: values of this map are objects.
     * @suppress PhanUnreferencedPublicMethod possibly useful but currently unused
     */
    public function valueSet() : Set
    {
        $set = new Set();
        foreach ($this as $value) {
            $set->attach($value);
        }
        return $set;
    }

    /**
     * @return Set
     * A new set with the unique keys from this map.
     * Precondition: values of this set are objects.
     * @suppress PhanUnreferencedPublicMethod possibly useful but currently unused
     */
    public function keySet() : Set
    {
        $set = new Set();
        foreach ($this as $key => $_) {
            $set->attach($key);
        }
        return $set;
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * @template T
 * The type of the element
 */
abstract class Option
{
    /**
     * @param T $else
     * @return T
     */
    abstract public function getOrElse($else);

    /**
     * @return bool
     */
    abstract public function isDefined() : bool;

    /**
     * @return T
     */
    abstract public function get();
}
<?php declare(strict_Types=1);
namespace Phan\Library;

/**
 * An LRU cache for the contents of files (FileCacheEntry), and data structures derived from contents of files.
 */
final class FileCache
{
    const MINIMUM_CACHE_SIZE = 5;
    /**
     * @var int - Maximum cache size
     */
    private static $max_size;

    /**
     * @var array<string,FileCacheEntry> - An ordered php associative array, with most recently used at the end of the array.
     */
    private static $cache_entries = [];

    public static function setMaxCacheSize(int $max_size)
    {
        self::$max_size = max($max_size, self::MINIMUM_CACHE_SIZE);
        while (\count(self::$cache_entries) > self::$max_size) {
            \array_shift(self::$cache_entries);
        }
    }

    /**
     * @return FileCacheEntry
     */
    public static function addEntry(string $file_name, string $contents) : FileCacheEntry
    {
        $old_entry = self::$cache_entries[$file_name] ?? null;
        if ($old_entry) {
            unset(self::$cache_entries[$file_name]);
            if ($old_entry->getContents() === $contents) {
                // If the contents didn't change, keep the cache entry and move it to the end (Most recently used).
                self::$cache_entries[$file_name] = $old_entry;
                return $old_entry;
            }
        }
        $entry = new FileCacheEntry($contents);
        self::$cache_entries[$file_name] = $entry;
        if (\count(self::$cache_entries) > self::$max_size) {
            // Ensure that the size <= self::$max_size. Remove the least recently used entry (front of list).
            \array_shift(self::$cache_entries);
        }
        return $entry;
    }

    /**
     * @return ?FileCacheEntry
     */
    public static function getEntry(string $file_name)
    {
        $entry = self::$cache_entries[$file_name] ?? null;
        if ($entry) {
            // Move the entry to the end (most recently used) and return it.
            unset(self::$cache_entries[$file_name]);
            self::$cache_entries[$file_name] = $entry;
            return $entry;
        }
        return null;
    }

    /**
     * @throws \RuntimeException if the file could not be loaded
     * @return FileCacheEntry
     */
    public static function getOrReadEntry(string $file_name) : FileCacheEntry
    {
        $entry = self::getEntry($file_name);
        if ($entry !== null) {
            return $entry;
        }
        if (!\file_exists($file_name)) {
            throw new \RuntimeException("FileCache::getOrReadEntry: unable to find '$file_name'\n");
        }
        if (!\is_readable($file_name)) {
            throw new \RuntimeException("FileCache::getOrReadEntry: unable to read '$file_name'\n");
        }
        $contents = file_get_contents($file_name);
        if (!\is_string($contents)) {
            throw new \RuntimeException("FileCache::getOrReadEntry: file_get_contents failed for '$file_name'\n");
        }
        $entry = self::addEntry($file_name, $contents);
        return $entry;
    }

    /**
     * Clear the cache (E.g. after pausing, accepting a daemon mode request, then resuming)
     * @return void
     */
    public static function clear()
    {
        self::$cache_entries = [];
    }

    /**
     * @return array<int,string> list of file paths with most recently used entries at the end.
     */
    public static function getCachedFileList() : array
    {
        return array_keys(self::$cache_entries);
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * An abstract tuple.
 */
abstract class Tuple
{
    const ARITY = 0;

    /**
     * @return int
     * The arity of this tuple
     * @suppress PhanUnreferencedPublicMethod potentially used in the future
     */
    public function arity() : int
    {
        return static::ARITY;
    }

    /**
     * @return array
     * An array of all elements in this tuple.
     */
    abstract public function toArray() : array;
}
<?php declare(strict_types=1);

namespace Phan\Library;

/**
 * An interface to map strings to integers representing groups.
 * getGroup() is called exactly once on each string to be hashed.
 */
interface Hasher
{
    /**
     * Returns an integer between 0 and the number of groups - 1
     */
    public function getGroup(string $key) : int;

    /**
     * @return void
     * If there is any state, clear it.
     */
    public function reset();
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * A tuple of 2 elements.
 *
 * @template T0
 * The type of element zero
 *
 * @template T1
 * The type of element one
 *
 * @inherits Tuple1<T0>
 */
class Tuple2 extends Tuple1
{
    /** @var int */
    const ARITY = 2;

    /** @var T1 */
    public $_1;

    /**
     * @param T0 $_0
     * The 0th element
     *
     * @param T1 $_1
     * The 1st element
     */
    public function __construct($_0, $_1)
    {
        parent::__construct($_0);
        $this->_1 = $_1;
    }

    /**
     * @return array
     * An array of all elements in this tuple.
     */
    public function toArray() : array
    {
        return [
            $this->_0,
            $this->_1,
        ];
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * @template T
 * The type of the element
 *
 * @inherits Option<T>
 */
class Some extends Option
{
    /** @var T */
    private $_;

    /**
     * @param T $_
     */
    public function __construct($_)
    {
        $this->_ = $_;
    }

    /**
     * @return bool
     */
    public function isDefined() : bool
    {
        return true;
    }

    /**
     * @return T
     */
    public function get()
    {
        return $this->_;
    }

    /**
     * @param T $else used in the None sibling class (@phan-unused-param)
     * @return T
     */
    public function getOrElse($else)
    {
        return $this->get();
    }

    /**
     * @return string
     * A string representation of this object
     */
    public function __tostring() : string
    {
        return 'Some(' . $this->_ . ')';
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * A tuple of 5 elements.
 *
 * @template T0
 * The type of element zero
 *
 * @template T1
 * The type of element one
 *
 * @template T2
 * The type of element two
 *
 * @template T3
 * The type of element three
 *
 * @template T4
 * The type of element four
 *
 * @inherits Tuple4<T0, T1, T2, T3>
 */
class Tuple5 extends Tuple4
{
    /** @var int */
    const ARITY = 4;

    /** @var T4 */
    public $_4;

    /**
     * @param T0 $_0
     * The 0th element
     *
     * @param T1 $_1
     * The 1st element
     *
     * @param T2 $_2
     * The 2nd element
     *
     * @param T3 $_3
     * The 3rd element
     *
     * @param T4 $_4
     * The 4th element
     */
    public function __construct($_0, $_1, $_2, $_3, $_4)
    {
        $this->_0 = $_0;
        $this->_1 = $_1;
        $this->_2 = $_2;
        $this->_3 = $_3;
        $this->_4 = $_4;
    }

    /**
     * @return array
     * An array of all elements in this tuple.
     */
    public function toArray() : array
    {
        return [
            $this->_0,
            $this->_1,
            $this->_2,
            $this->_3,
            $this->_4,
        ];
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

class FileCacheEntry
{
    /** @var string contents of the file */
    private $contents;
    /**
     * @var ?array lines of the contents of the file. Lazily populated.
     */
    private $lines = null;

    public function __construct(string $contents)
    {
        $this->contents = $contents;
    }

    public function getContents() : string
    {
        return $this->contents;
    }

    /**
     * @return array<int,string> a 1-based array of lines
     */
    public function getLines() : array
    {
        $lines = $this->lines;
        if (\is_array($lines)) {
            return $lines;
        }
        $lines = \preg_split("/^/m", $this->contents);
        unset($lines[0]);
        $this->lines = $lines;
        return $lines;
    }


    /**
     * Helper method to get individual lines from a file.
     * This is more efficient than using \SplFileObject if multiple lines may need to be fetched.
     *
     * @param int $lineno - A line number, starting with line 1
     * @return ?string
     */
    public function getLine(int $lineno)
    {
        $lines = $this->getLines();
        return $lines[$lineno] ?? null;
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * A set of objects supporting union and
 * intersection
 */
class Set extends \SplObjectStorage
{

    /**
     * @param \Iterator|array $element_iterator
     * An optional set of items to add to the set
     */
    public function __construct($element_iterator = null)
    {
        foreach ($element_iterator ?? [] as $element) {
            $this->attach($element);
        }
    }

    /**
     * @return array
     * An array of all elements in the set is returned
     */
    public function toArray() : array
    {
        return \iterator_to_array($this);
    }

    /**
     * @param Set $other
     * A set of items to intersect with this set
     *
     * @return Set
     * A new set which contains only items in this
     * Set and the given Set
     */
    public function intersect(Set $other) : Set
    {
        $set = new Set();
        foreach ($this as $element) {
            if ($other->contains($element)) {
                $set->attach($element);
            }
        }
        return $set;
    }

    /**
     * @param Set[] $set_list
     * A list of sets to intersect
     *
     * @return Set
     * A new Set containing only the elements that appear in
     * all parameters
     * @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
     */
    public static function intersectAll(array $set_list) : Set
    {
        if (empty($set_list)) {
            return new Set();
        }

        $intersected_set = \array_shift($set_list);
        foreach ($set_list as $set) {
            $intersected_set = $intersected_set->intersect($set);
        }

        return $intersected_set;
    }

    /**
     * @param Set $other
     * A set of items to union with this set
     *
     * @return Set
     * A new set which contains only items in this
     * Set and the given Set.
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function union(Set $other) : Set
    {
        $set = new Set();
        $set->addAll($this);
        $set->addAll($other);
        return $set;
    }

    /**
     * @param Set[] $set_list
     * A list of sets to intersect
     *
     * @return Set
     * A new Set containing any element that appear in
     * any parameters
     * @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
     */
    public static function unionAll(array $set_list) : Set
    {
        if (empty($set_list)) {
            return new Set();
        }

        $union_set = \array_shift($set_list);
        foreach ($set_list as $set) {
            $union_set = $union_set->union($set);
        }

        return $union_set;
    }


    /**
     * @return bool
     * True if this set contains any elements in the given list
     * @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
     */
    public function containsAny(array $element_list) : bool
    {
        foreach ($element_list as $element) {
            if ($this->contains($element)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param \Closure $closure
     * A closure taking a set element that returns a boolean
     * for which true will cause the element to be retained
     * and false will cause the element to be removed
     *
     * @return Set
     * A new set for which all elements when passed to the given
     * closure return true
     * @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
     */
    public function filter(\Closure $closure)
    {
        $set = new Set();
        foreach ($this as $element) {
            if ($closure($element)) {
                $set->attach($element);
            }
        }
        return $set;
    }

    /**
     * @param \Closure $closure
     * A closure that maps each element of this set
     * to a new element
     *
     * @return Set
     * A new set containing the mapped values
     */
    public function map(\Closure $closure) : Set
    {
        $set = new Set;
        foreach ($this as $element) {
            $set->attach($closure($element));
        }
        return $set;
    }

    /**
     * @return Set
     * A new set with each element cloned
     */
    public function deepCopy() : Set
    {
        return $this->map(function ($element) {
            return clone($element);
        });
    }

    /**
     * @param \Closure $closure
     * A closure that takes an element and returns a boolean
     *
     * @return mixed|bool
     * The first element for which the given closure returns
     * true is returned or false if no elements pass the
     * given closure
     * @suppress PhanUnreferencedPublicMethod potentially useful but currently unused
     */
    public function find(\Closure $closure)
    {
        foreach ($this as $element) {
            if ($closure($element)) {
                return $element;
            }
        }
        return false;
    }

    /**
     * @return string
     * A string representation of this set for use in
     * debugging
     */
    public function __toString() : string
    {
        $string = '['
            . \implode(',', \array_map(function ($element) {
                return (string)$element;
            }, \iterator_to_array($this)))
            . ']';

        return $string;
    }
}
<?php declare(strict_types=1);
namespace Phan\Library;

/**
 * A tuple of 1 element.
 *
 * @template T0
 * The type of element zero
 */
class Tuple1 extends Tuple
{
    /** @var int */
    const ARITY = 1;

    /** @var T0 */
    public $_0;

    /**
     * @param T0 $_0
     * The 0th element
     */
    public function __construct($_0)
    {
        $this->_0 = $_0;
    }

    /**
     * @return int
     * The arity of this tuple
     */
    public function arity() : int
    {
        return static::ARITY;
    }

    /**
     * @return array
     * An array of all elements in this tuple.
     */
    public function toArray() : array
    {
        return [
            $this->_0,
        ];
    }
}
<?php declare(strict_types=1);
namespace Phan;

use Phan\Config;
use Phan\Language\Context;
use Phan\Language\Element\TypedElement;
use Phan\Language\Element\UnaddressableTypedElement;
use Phan\Language\FQSEN;
use Phan\Language\Type;
use Phan\Language\UnionType;

/**
 * An issue emitted during the course of analysis
 */
class Issue
{
    // @codingStandardsIgnoreStart
    // Issue::CATEGORY_SYNTAX
    const SyntaxError               = 'PhanSyntaxError';

    // Issue::CATEGORY_UNDEFINED
    const AmbiguousTraitAliasSource = 'PhanAmbiguousTraitAliasSource';
    const ClassContainsAbstractMethodInternal = 'PhanClassContainsAbstractMethodInternal';
    const ClassContainsAbstractMethod = 'PhanClassContainsAbstractMethod';
    const EmptyFile                 = 'PhanEmptyFile';
    const ParentlessClass           = 'PhanParentlessClass';
    const RequiredTraitNotAdded     = 'PhanRequiredTraitNotAdded';
    const TraitParentReference      = 'PhanTraitParentReference';
    const UndeclaredAliasedMethodOfTrait = 'PhanUndeclaredAliasedMethodOfTrait';
    const UndeclaredClass           = 'PhanUndeclaredClass';
    const UndeclaredClassAliasOriginal = 'PhanUndeclaredClassAliasOriginal';
    const UndeclaredClassCatch      = 'PhanUndeclaredClassCatch';
    const UndeclaredClassConstant   = 'PhanUndeclaredClassConstant';
    const UndeclaredClassInstanceof = 'PhanUndeclaredClassInstanceof';
    const UndeclaredClassMethod     = 'PhanUndeclaredClassMethod';
    const UndeclaredClassReference  = 'PhanUndeclaredClassReference';
    const UndeclaredClosureScope    = 'PhanUndeclaredClosureScope';
    const UndeclaredConstant        = 'PhanUndeclaredConstant';
    const UndeclaredExtendedClass   = 'PhanUndeclaredExtendedClass';
    const UndeclaredFunction        = 'PhanUndeclaredFunction';
    const UndeclaredInterface       = 'PhanUndeclaredInterface';
    const UndeclaredMethod          = 'PhanUndeclaredMethod';
    const UndeclaredProperty        = 'PhanUndeclaredProperty';
    const UndeclaredStaticMethod    = 'PhanUndeclaredStaticMethod';
    const UndeclaredStaticProperty  = 'PhanUndeclaredStaticProperty';
    const UndeclaredTrait           = 'PhanUndeclaredTrait';
    const UndeclaredTypeParameter   = 'PhanUndeclaredTypeParameter';
    const UndeclaredTypeReturnType  = 'PhanUndeclaredTypeReturnType';
    const UndeclaredTypeProperty    = 'PhanUndeclaredTypeProperty';
    const UndeclaredTypeThrowsType  = 'PhanUndeclaredTypeThrowsType';
    const UndeclaredVariable        = 'PhanUndeclaredVariable';
    const UndeclaredVariableDim     = 'PhanUndeclaredVariableDim';
    const UndeclaredClassInCallable = 'PhanUndeclaredClassInCallable';
    const UndeclaredStaticMethodInCallable = 'PhanUndeclaredStaticMethodInCallable';
    const UndeclaredFunctionInCallable = 'PhanUndeclaredFunctionInCallable';
    const UndeclaredMethodInCallable = 'PhanUndeclaredMethodInCallable';
    const EmptyFQSENInCallable      = 'PhanEmptyFQSENInCallable';
    const EmptyFQSENInClasslike     = 'PhanEmptyFQSENInClasslike';

    // Issue::CATEGORY_TYPE
    const NonClassMethodCall        = 'PhanNonClassMethodCall';
    const TypeArrayOperator         = 'PhanTypeArrayOperator';
    const TypeArraySuspicious       = 'PhanTypeArraySuspicious';
    const TypeArrayUnsetSuspicious  = 'PhanTypeArrayUnsetSuspicious';
    const TypeArraySuspiciousNullable = 'PhanTypeArraySuspiciousNullable';
    const TypeSuspiciousIndirectVariable = 'PhanTypeSuspiciousIndirectVariable';
    const TypeComparisonFromArray   = 'PhanTypeComparisonFromArray';
    const TypeComparisonToArray     = 'PhanTypeComparisonToArray';
    const TypeConversionFromArray   = 'PhanTypeConversionFromArray';
    const TypeInstantiateAbstract   = 'PhanTypeInstantiateAbstract';
    const TypeInstantiateInterface  = 'PhanTypeInstantiateInterface';
    const TypeInvalidClosureScope   = 'PhanTypeInvalidClosureScope';
    const TypeInvalidLeftOperand    = 'PhanTypeInvalidLeftOperand';
    const TypeInvalidRightOperand   = 'PhanTypeInvalidRightOperand';
    const TypeInvalidInstanceof     = 'PhanTypeInvalidInstanceof';
    const TypeInvalidDimOffset      = 'PhanTypeInvalidDimOffset';
    const TypeInvalidDimOffsetArrayDestructuring = 'PhanTypeInvalidDimOffsetArrayDestructuring';
    const TypeInvalidThrowsNonObject = 'PhanTypeInvalidThrowsNonObject';
    const TypeInvalidThrowsNonThrowable = 'PhanTypeInvalidThrowsNonThrowable';
    const TypeInvalidThrowsIsTrait = 'PhanTypeInvalidThrowsIsTrait';
    const TypeInvalidThrowsIsInterface = 'PhanTypeInvalidThrowsIsInterface';
    const TypeMagicVoidWithReturn   = 'PhanTypeMagicVoidWithReturn';
    const TypeMismatchArgument      = 'PhanTypeMismatchArgument';
    const TypeMismatchArgumentInternal = 'PhanTypeMismatchArgumentInternal';
    const PartialTypeMismatchArgument = 'PhanPartialTypeMismatchArgument';
    const PartialTypeMismatchArgumentInternal= 'PhanPartialTypeMismatchArgumentInternal';
    const PossiblyNullTypeArgument  = 'PhanPossiblyNullTypeArgument';
    const PossiblyNullTypeArgumentInternal = 'PhanPossiblyNullTypeArgumentInternal';
    const PossiblyFalseTypeArgument  = 'PhanPossiblyFalseTypeArgument';
    const PossiblyFalseTypeArgumentInternal = 'PhanPossiblyFalseTypeArgumentInternal';

    const TypeMismatchDefault       = 'PhanTypeMismatchDefault';
    const TypeMismatchDimAssignment = 'PhanTypeMismatchDimAssignment';
    const TypeMismatchDimEmpty      = 'PhanTypeMismatchDimEmpty';
    const TypeMismatchDimFetch      = 'PhanTypeMismatchDimFetch';
    const TypeMismatchDimFetchNullable = 'PhanTypeMismatchDimFetchNullable';
    const TypeMismatchUnpackKey     = 'PhanTypeMismatchUnpackKey';
    const TypeMismatchUnpackValue   = 'PhanTypeMismatchUnpackValue';
    const TypeMismatchArrayDestructuringKey = 'PhanTypeMismatchArrayDestructuringKey';
    const TypeMismatchVariadicComment = 'PhanMismatchVariadicComment';
    const TypeMismatchVariadicParam = 'PhanMismatchVariadicParam';
    const TypeMismatchForeach       = 'PhanTypeMismatchForeach';
    const TypeMismatchProperty      = 'PhanTypeMismatchProperty';
    const PossiblyNullTypeMismatchProperty = 'PhanPossiblyNullTypeMismatchProperty';
    const PossiblyFalseTypeMismatchProperty = 'PhanPossiblyFalseTypeMismatchProperty';
    const PartialTypeMismatchProperty = 'PhanPartialTypeMismatchProperty';
    const TypeMismatchReturn        = 'PhanTypeMismatchReturn';
    const PartialTypeMismatchReturn = 'PhanPartialTypeMismatchReturn';
    const PossiblyNullTypeReturn  = 'PhanPossiblyNullTypeReturn';
    const PossiblyFalseTypeReturn  = 'PhanPossiblyFalseTypeReturn';
    const TypeMismatchDeclaredReturn = 'PhanTypeMismatchDeclaredReturn';
    const TypeMismatchDeclaredReturnNullable = 'PhanTypeMismatchDeclaredReturnNullable';
    const TypeMismatchDeclaredParam = 'PhanTypeMismatchDeclaredParam';
    const TypeMismatchDeclaredParamNullable = 'PhanTypeMismatchDeclaredParamNullable';
    const TypeMissingReturn         = 'PhanTypeMissingReturn';
    const TypeNonVarPassByRef       = 'PhanTypeNonVarPassByRef';
    const TypeParentConstructorCalled = 'PhanTypeParentConstructorCalled';
    const TypeSuspiciousEcho        = 'PhanTypeSuspiciousEcho';
    const TypeVoidAssignment        = 'PhanTypeVoidAssignment';
    const TypeInvalidCallableArraySize = 'PhanTypeInvalidCallableArraySize';
    const TypeInvalidCallableArrayKey = 'PhanTypeInvalidCallableArrayKey';
    const TypeInvalidCallableObjectOfMethod = 'PhanTypeInvalidCallableObjectOfMethod';
    const TypeExpectedObject        = 'PhanTypeExpectedObject';
    const TypeExpectedObjectOrClassName = 'PhanTypeExpectedObjectOrClassName';
    const TypeExpectedObjectPropAccess = 'PhanTypeExpectedObjectPropAccess';
    const TypeExpectedObjectPropAccessButGotNull = 'PhanTypeExpectedObjectPropAccessButGotNull';
    const TypeExpectedObjectStaticPropAccess = 'PhanTypeExpectedObjectStaticPropAccess';

    // Issue::CATEGORY_ANALYSIS
    const Unanalyzable              = 'PhanUnanalyzable';
    const UnanalyzableInheritance   = 'PhanUnanalyzableInheritance';

    // Issue::CATEGORY_VARIABLE
    const VariableUseClause         = 'PhanVariableUseClause';

    // Issue::CATEGORY_STATIC
    const StaticCallToNonStatic     = 'PhanStaticCallToNonStatic';

    // Issue::CATEGORY_CONTEXT
    const ContextNotObject          = 'PhanContextNotObject';
    const ContextNotObjectInCallable = 'PhanContextNotObjectInCallable';

    // Issue::CATEGORY_DEPRECATED
    const DeprecatedClass           = 'PhanDeprecatedClass';
    const DeprecatedInterface       = 'PhanDeprecatedInterface';
    const DeprecatedTrait           = 'PhanDeprecatedTrait';
    const DeprecatedFunction        = 'PhanDeprecatedFunction';
    const DeprecatedFunctionInternal = 'PhanDeprecatedFunctionInternal';
    const DeprecatedProperty        = 'PhanDeprecatedProperty';

    // Issue::CATEGORY_PARAMETER
    const ParamReqAfterOpt          = 'PhanParamReqAfterOpt';
    const ParamSpecial1             = 'PhanParamSpecial1';
    const ParamSpecial2             = 'PhanParamSpecial2';
    const ParamSpecial3             = 'PhanParamSpecial3';
    const ParamSpecial4             = 'PhanParamSpecial4';
    const ParamSuspiciousOrder      = 'PhanParamSuspiciousOrder';
    const ParamTooFew               = 'PhanParamTooFew';
    const ParamTooFewInternal       = 'PhanParamTooFewInternal';
    const ParamTooFewCallable       = 'PhanParamTooFewCallable';
    const ParamTooMany              = 'PhanParamTooMany';
    const ParamTooManyInternal      = 'PhanParamTooManyInternal';
    const ParamTooManyCallable      = 'PhanParamTooManyCallable';
    const ParamTypeMismatch         = 'PhanParamTypeMismatch';
    const ParamSignatureMismatch    = 'PhanParamSignatureMismatch';
    const ParamSignatureMismatchInternal = 'PhanParamSignatureMismatchInternal';
    const ParamRedefined            = 'PhanParamRedefined';

    const ParamSignatureRealMismatchReturnType                        = 'PhanParamSignatureRealMismatchReturnType';
    const ParamSignatureRealMismatchReturnTypeInternal                = 'PhanParamSignatureRealMismatchReturnTypeInternal';
    const ParamSignaturePHPDocMismatchReturnType                      = 'PhanParamSignaturePHPDocMismatchReturnType';
    const ParamSignatureRealMismatchTooManyRequiredParameters         = 'PhanParamSignatureRealMismatchTooManyRequiredParameters';
    const ParamSignatureRealMismatchTooManyRequiredParametersInternal = 'PhanParamSignatureRealMismatchTooManyRequiredParametersInternal';
    const ParamSignaturePHPDocMismatchTooManyRequiredParameters       = 'PhanParamSignaturePHPDocMismatchTooManyRequiredParameters';
    const ParamSignatureRealMismatchTooFewParameters                  = 'PhanParamSignatureRealMismatchTooFewParameters';
    const ParamSignatureRealMismatchTooFewParametersInternal          = 'PhanParamSignatureRealMismatchTooFewParametersInternal';
    const ParamSignaturePHPDocMismatchTooFewParameters                = 'PhanParamSignaturePHPDocMismatchTooFewParameters';
    const ParamSignatureRealMismatchHasParamType                      = 'PhanParamSignatureRealMismatchHasParamType';
    const ParamSignatureRealMismatchHasParamTypeInternal              = 'PhanParamSignatureRealMismatchHasParamTypeInternal';
    const ParamSignaturePHPDocMismatchHasParamType                    = 'PhanParamSignaturePHPDocMismatchHasParamType';
    const ParamSignatureRealMismatchHasNoParamType                    = 'PhanParamSignatureRealMismatchHasNoParamType';
    const ParamSignatureRealMismatchHasNoParamTypeInternal            = 'PhanParamSignatureRealMismatchHasNoParamTypeInternal';
    const ParamSignaturePHPDocMismatchHasNoParamType                  = 'PhanParamSignaturePHPDocMismatchHasNoParamType';
    const ParamSignatureRealMismatchParamIsReference                  = 'PhanParamSignatureRealMismatchParamIsReference';
    const ParamSignatureRealMismatchParamIsReferenceInternal          = 'PhanParamSignatureRealMismatchParamIsReferenceInternal';
    const ParamSignaturePHPDocMismatchParamIsReference                = 'PhanParamSignaturePHPDocMismatchParamIsReference';
    const ParamSignatureRealMismatchParamIsNotReference               = 'PhanParamSignatureRealMismatchParamIsNotReference';
    const ParamSignatureRealMismatchParamIsNotReferenceInternal       = 'PhanParamSignatureRealMismatchParamIsNotReferenceInternal';
    const ParamSignaturePHPDocMismatchParamIsNotReference             = 'PhanParamSignaturePHPDocMismatchParamIsNotReference';
    const ParamSignatureRealMismatchParamVariadic                     = 'PhanParamSignatureRealMismatchParamVariadic';
    const ParamSignatureRealMismatchParamVariadicInternal             = 'PhanParamSignatureRealMismatchParamVariadicInternal';
    const ParamSignaturePHPDocMismatchParamVariadic                   = 'PhanParamSignaturePHPDocMismatchParamVariadic';
    const ParamSignatureRealMismatchParamNotVariadic                  = 'PhanParamSignatureRealMismatchParamNotVariadic';
    const ParamSignatureRealMismatchParamNotVariadicInternal          = 'PhanParamSignatureRealMismatchParamNotVariadicInternal';
    const ParamSignaturePHPDocMismatchParamNotVariadic                = 'PhanParamSignaturePHPDocMismatchParamNotVariadic';
    const ParamSignatureRealMismatchParamType                         = 'PhanParamSignatureRealMismatchParamType';
    const ParamSignatureRealMismatchParamTypeInternal                 = 'PhanParamSignatureRealMismatchParamTypeInternal';
    const ParamSignaturePHPDocMismatchParamType                       = 'PhanParamSignaturePHPDocMismatchParamType';

    // Issue::CATEGORY_NOOP
    const NoopArray                     = 'PhanNoopArray';
    const NoopClosure                   = 'PhanNoopClosure';
    const NoopConstant                  = 'PhanNoopConstant';
    const NoopProperty                  = 'PhanNoopProperty';
    const NoopVariable                  = 'PhanNoopVariable';
    const NoopUnaryOperator             = 'PhanNoopUnaryOperator';
    const NoopBinaryOperator            = 'PhanNoopBinaryOperator';
    const UnreachableCatch              = 'PhanUnreachableCatch';
    const UnreferencedClass             = 'PhanUnreferencedClass';
    const UnreferencedFunction          = 'PhanUnreferencedFunction';
    const UnreferencedPublicMethod      = 'PhanUnreferencedPublicMethod';
    const UnreferencedProtectedMethod   = 'PhanUnreferencedProtectedMethod';
    const UnreferencedPrivateMethod     = 'PhanUnreferencedPrivateMethod';
    const UnreferencedPublicProperty    = 'PhanUnreferencedPublicProperty';
    const UnreferencedProtectedProperty = 'PhanUnreferencedProtectedProperty';
    const UnreferencedPrivateProperty   = 'PhanUnreferencedPrivateProperty';
    const WriteOnlyPublicProperty       = 'PhanWriteOnlyPublicProperty';
    const WriteOnlyProtectedProperty    = 'PhanWriteOnlyProtectedProperty';
    const WriteOnlyPrivateProperty      = 'PhanWriteOnlyPrivateProperty';
    const UnreferencedConstant          = 'PhanUnreferencedConstant';
    const UnreferencedPublicClassConstant = 'PhanUnreferencedPublicClassConstant';
    const UnreferencedProtectedClassConstant = 'PhanUnreferencedProtectedClassConstant';
    const UnreferencedPrivateClassConstant = 'PhanUnreferencedPrivateClassConstant';
    const UnreferencedClosure           = 'PhanUnreferencedClosure';
    const UnreferencedUseNormal         = 'PhanUnreferencedUseNormal';
    const UnreferencedUseFunction       = 'PhanUnreferencedUseFunction';
    const UnreferencedUseConstant       = 'PhanUnreferencedUseConstant';

    // Issue::CATEGORY_REDEFINE
    const RedefineClass             = 'PhanRedefineClass';
    const RedefineClassAlias        = 'PhanRedefineClassAlias';
    const RedefineClassInternal     = 'PhanRedefineClassInternal';
    const RedefineFunction          = 'PhanRedefineFunction';
    const RedefineFunctionInternal  = 'PhanRedefineFunctionInternal';
    const IncompatibleCompositionProp = 'PhanIncompatibleCompositionProp';
    const IncompatibleCompositionMethod = 'PhanIncompatibleCompositionMethod';

    // Issue::CATEGORY_ACCESS
    const AccessPropertyPrivate     = 'PhanAccessPropertyPrivate';
    const AccessPropertyProtected   = 'PhanAccessPropertyProtected';
    const AccessMethodPrivate       = 'PhanAccessMethodPrivate';
    const AccessMethodPrivateWithCallMagicMethod = 'PhanAccessMethodPrivateWithCallMagicMethod';
    const AccessMethodProtected     = 'PhanAccessMethodProtected';
    const AccessMethodProtectedWithCallMagicMethod = 'PhanAccessMethodProtectedWithCallMagicMethod';
    const AccessSignatureMismatch   = 'PhanAccessSignatureMismatch';
    const AccessSignatureMismatchInternal = 'PhanAccessSignatureMismatchInternal';
    const AccessStaticToNonStatic   = 'PhanAccessStaticToNonStatic';
    const AccessNonStaticToStatic   = 'PhanAccessNonStaticToStatic';
    const AccessClassConstantPrivate     = 'PhanAccessClassConstantPrivate';
    const AccessClassConstantProtected   = 'PhanAccessClassConstantProtected';
    const AccessPropertyStaticAsNonStatic = 'PhanAccessPropertyStaticAsNonStatic';
    const AccessPropertyNonStaticAsStatic = 'PhanAccessPropertyNonStaticAsStatic';
    const AccessOwnConstructor = 'PhanAccessOwnConstructor';

    const AccessConstantInternal    = 'PhanAccessConstantInternal';
    const AccessClassInternal       = 'PhanAccessClassInternal';
    const AccessClassConstantInternal = 'PhanAccessClassConstantInternal';
    const AccessPropertyInternal    = 'PhanAccessPropertyInternal';
    const AccessMethodInternal      = 'PhanAccessMethodInternal';
    const AccessWrongInheritanceCategory = 'PhanAccessWrongInheritanceCategory';
    const AccessWrongInheritanceCategoryInternal = 'PhanAccessWrongInheritanceCategoryInternal';
    const AccessExtendsFinalClass                = 'PhanAccessExtendsFinalClass';
    const AccessExtendsFinalClassInternal        = 'PhanAccessExtendsFinalClassInternal';
    const AccessOverridesFinalMethod             = 'PhanAccessOverridesFinalMethod';
    const AccessOverridesFinalMethodInternal     = 'PhanAccessOverridesFinalMethodInternal';
    const AccessOverridesFinalMethodPHPDoc       = 'PhanAccessOverridesFinalMethodPHPDoc';

    // Issue::CATEGORY_COMPATIBLE
    const CompatibleExpressionPHP7          = 'PhanCompatibleExpressionPHP7';
    const CompatiblePHP7                    = 'PhanCompatiblePHP7';
    const CompatibleNullableTypePHP70       = 'PhanCompatibleNullableTypePHP70';
    const CompatibleShortArrayAssignPHP70   = 'PhanCompatibleShortArrayAssignPHP70';
    const CompatibleKeyedArrayAssignPHP70   = 'PhanCompatibleKeyedArrayAssignPHP70';
    const CompatibleVoidTypePHP70           = 'PhanCompatibleVoidTypePHP70';
    const CompatibleIterableTypePHP70       = 'PhanCompatibleIterableTypePHP70';
    const CompatibleObjectTypePHP71         = 'PhanCompatibleNullableTypePHP71';

    // Issue::CATEGORY_GENERIC
    const TemplateTypeConstant       = 'PhanTemplateTypeConstant';
    const TemplateTypeStaticMethod   = 'PhanTemplateTypeStaticMethod';
    const TemplateTypeStaticProperty = 'PhanTemplateTypeStaticProperty';
    const GenericGlobalVariable      = 'PhanGenericGlobalVariable';
    const GenericConstructorTypes    = 'PhanGenericConstructorTypes';

    // Issue::CATEGORY_COMMENT
    const InvalidCommentForDeclarationType = 'PhanInvalidCommentForDeclarationType';
    const MisspelledAnnotation             = 'PhanMisspelledAnnotation';
    const UnextractableAnnotation          = 'PhanUnextractableAnnotation';
    const UnextractableAnnotationPart      = 'PhanUnextractableAnnotationPart';
    const UnextractableAnnotationSuffix    = 'PhanUnextractableAnnotationSuffix';
    const UnextractableAnnotationElementName = 'PhanUnextractableAnnotationElementName';
    const CommentParamWithoutRealParam     = 'PhanCommentParamWithoutRealParam';
    const CommentParamOnEmptyParamList     = 'PhanCommentParamOnEmptyParamList';
    const CommentOverrideOnNonOverrideMethod = 'PhanCommentOverrideOnNonOverrideMethod';
    const CommentOverrideOnNonOverrideConstant = 'PhanCommentOverrideOnNonOverrideConstant';
    const CommentParamOutOfOrder           = 'PhanCommentParamOutOfOrder';


    const CATEGORY_ACCESS            = 1 << 1;
    const CATEGORY_ANALYSIS          = 1 << 2;
    const CATEGORY_COMPATIBLE        = 1 << 3;
    const CATEGORY_CONTEXT           = 1 << 4;
    const CATEGORY_DEPRECATED        = 1 << 5;
    const CATEGORY_NOOP              = 1 << 6;
    const CATEGORY_PARAMETER         = 1 << 7;
    const CATEGORY_REDEFINE          = 1 << 8;
    const CATEGORY_STATIC            = 1 << 9;
    const CATEGORY_TYPE              = 1 << 10;
    const CATEGORY_UNDEFINED         = 1 << 11;
    const CATEGORY_VARIABLE          = 1 << 12;
    const CATEGORY_PLUGIN            = 1 << 13;
    const CATEGORY_GENERIC           = 1 << 14;
    const CATEGORY_INTERNAL          = 1 << 15;
    const CATEGORY_COMMENT           = 1 << 16;
    const CATEGORY_SYNTAX            = 1 << 17;

    const CATEGORY_NAME = [
        self::CATEGORY_ACCESS            => 'AccessError',
        self::CATEGORY_ANALYSIS          => 'Analysis',
        self::CATEGORY_COMMENT           => 'CommentError',
        self::CATEGORY_COMPATIBLE        => 'CompatError',
        self::CATEGORY_CONTEXT           => 'Context',
        self::CATEGORY_DEPRECATED        => 'DeprecatedError',
        self::CATEGORY_GENERIC           => 'Generic',
        self::CATEGORY_INTERNAL          => 'Internal',
        self::CATEGORY_NOOP              => 'NOOPError',
        self::CATEGORY_PARAMETER         => 'ParamError',
        self::CATEGORY_PLUGIN            => 'Plugin',
        self::CATEGORY_REDEFINE          => 'RedefineError',
        self::CATEGORY_STATIC            => 'StaticCallError',
        self::CATEGORY_SYNTAX            => 'Syntax',
        self::CATEGORY_TYPE              => 'TypeError',
        self::CATEGORY_UNDEFINED         => 'UndefError',
        self::CATEGORY_VARIABLE          => 'VarError',
    ];

    const SEVERITY_LOW      = 0;
    const SEVERITY_NORMAL   = 5;
    const SEVERITY_CRITICAL = 10;

    // See https://docs.codeclimate.com/v1.0/docs/remediation
    const REMEDIATION_A = 1000000;
    const REMEDIATION_B = 3000000;
    /** @suppress PhanUnreferencedPublicClassConstant */
    const REMEDIATION_C = 6000000;
    /** @suppress PhanUnreferencedPublicClassConstant */
    const REMEDIATION_D = 12000000;
    /** @suppress PhanUnreferencedPublicClassConstant */
    const REMEDIATION_E = 16000000;
    /** @suppress PhanUnreferencedPublicClassConstant */
    const REMEDIATION_F = 18000000;

    // type id constants.
    const TYPE_ID_UNKNOWN = 999;

    // Keep sorted and in sync with Colorizing::default_color_for_template
    const uncolored_format_string_for_template = [
        'CLASS'         => '%s',
        'CLASSLIKE'     => '%s',
        'COMMENT'       => '%s',  // contents of a phpdoc comment
        'CONST'         => '%s',
        'COUNT'         => '%d',
        'DETAILS'       => '%s',  // additional details about an error
        'FILE'          => '%s',
        'FUNCTIONLIKE'  => '%s',
        'FUNCTION'      => '%s',
        'INDEX'         => '%d',
        'INTERFACE'     => '%s',
        'ISSUETYPE'     => '%s',  // used by Phan\Output\Printer, for minor issues.
        'ISSUETYPE_CRITICAL' => '%s',  // for critical issues
        'ISSUETYPE_NORMAL' => '%s',  // for normal issues
        'LINE'          => '%d',
        'METHOD'        => '%s',
        'NAMESPACE'     => '%s',
        'OPERATOR'      => '%s',
        'PARAMETER'     => '%s',
        'PROPERTY'      => '%s',
        'SCALAR'        => '%s',  // A scalar from the code
        'STRING_LITERAL' => '%s',  // A string literal from the code
        'TYPE'          => '%s',
        'TRAIT'         => '%s',
        'VARIABLE'      => '%s',
    ];
    // @codingStandardsIgnoreEnd

    /** @var string */
    private $type;

    /** @var int */
    private $type_id;

    /** @var int */
    private $category;

    /** @var int */
    private $severity;

    /** @var string - Used for colorizing option. */
    private $template_raw;

    /** @var string */
    private $template;

    /** @var int */
    private $remediation_difficulty;

    /**
     * @param string $type
     * @param int $category
     * @param int $severity
     * @param string $template_raw - Contains a mix of {CLASS} and %s/%d annotations.
     * @param int $remediation_difficulty
     * @param int $type_id (unique integer id for $type)
     */
    public function __construct(
        string $type,
        int $category,
        int $severity,
        string $template_raw,
        int $remediation_difficulty,
        int $type_id
    ) {
        $this->type = $type;
        $this->category = $category;
        $this->severity = $severity;
        $this->template_raw = $template_raw;
        $this->template = self::templateToFormatString($template_raw);
        $this->remediation_difficulty = $remediation_difficulty;
        $this->type_id = $type_id;
    }

    private static function templateToFormatString(
        string $template
    ) : string {
        /** @param array<int,string> $matches */
        return preg_replace_callback('/{([A-Z_]+)}/', function (array $matches) use ($template): string {
            $key = $matches[1];
            $replacement_exists = \array_key_exists($key, self::uncolored_format_string_for_template);
            if (!$replacement_exists) {
                error_log(sprintf(
                    "No coloring info for issue message (%s), key {%s}. Valid template types: %s",
                    $template,
                    $key,
                    implode(', ', array_keys(self::uncolored_format_string_for_template))
                ));
                return '%s';
            }
            return self::uncolored_format_string_for_template[$key];
        }, $template);
    }

    /**
     * @return array<string,Issue>
     */
    public static function issueMap()
    {
        static $error_map;

        if (!empty($error_map)) {
            return $error_map;
        }

        /**
         * @var array<int,Issue>
         * Note: All type ids should be unique, and be grouped by the category.
         * (E.g. If the category is (1 << x), then the type_id should be x*1000 + y
         * If new type ids are added, existing ones should not be changed.
         */
        $error_list = [
            // Issue::CATEGORY_SYNTAX
            new Issue(
                self::SyntaxError,
                self::CATEGORY_SYNTAX,
                self::SEVERITY_CRITICAL,
                "%s",
                self::REMEDIATION_A,
                17000
            ),

            // Issue::CATEGORY_UNDEFINED
            new Issue(
                self::EmptyFile,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_LOW,
                "Empty file {FILE}",
                self::REMEDIATION_B,
                11000
            ),
            new Issue(
                self::ParentlessClass,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Reference to parent of class {CLASS} that does not extend anything",
                self::REMEDIATION_B,
                11001
            ),
            new Issue(
                self::UndeclaredClass,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Reference to undeclared class {CLASS}",
                self::REMEDIATION_B,
                11002
            ),
            new Issue(
                self::UndeclaredExtendedClass,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Class extends undeclared class {CLASS}",
                self::REMEDIATION_B,
                11003
            ),
            new Issue(
                self::UndeclaredInterface,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Class implements undeclared interface {INTERFACE}",
                self::REMEDIATION_B,
                11004
            ),
            new Issue(
                self::UndeclaredTrait,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Class uses undeclared trait {TRAIT}",
                self::REMEDIATION_B,
                11005
            ),
            new Issue(
                self::UndeclaredClassCatch,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Catching undeclared class {CLASS}",
                self::REMEDIATION_B,
                11006
            ),
            new Issue(
                self::UndeclaredClassConstant,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Reference to constant {CONST} from undeclared class {CLASS}",
                self::REMEDIATION_B,
                11007
            ),
            new Issue(
                self::UndeclaredClassInstanceof,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Checking instanceof against undeclared class {CLASS}",
                self::REMEDIATION_B,
                11008
            ),
            new Issue(
                self::UndeclaredClassMethod,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Call to method {METHOD} from undeclared class {CLASS}",
                self::REMEDIATION_B,
                11009
            ),
            new Issue(
                self::UndeclaredClassReference,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Reference to undeclared class {CLASS}",
                self::REMEDIATION_B,
                11010
            ),
            new Issue(
                self::UndeclaredConstant,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Reference to undeclared constant {CONST}",
                self::REMEDIATION_B,
                11011
            ),
            new Issue(
                self::UndeclaredFunction,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Call to undeclared function {FUNCTION}",
                self::REMEDIATION_B,
                11012
            ),
            new Issue(
                self::UndeclaredMethod,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Call to undeclared method {METHOD}",
                self::REMEDIATION_B,
                11013
            ),
            new Issue(
                self::UndeclaredStaticMethod,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Static call to undeclared method {METHOD}",
                self::REMEDIATION_B,
                11014
            ),
            new Issue(
                self::UndeclaredProperty,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Reference to undeclared property {PROPERTY}",
                self::REMEDIATION_B,
                11015
            ),
            new Issue(
                self::UndeclaredStaticProperty,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Static property '{PROPERTY}' on {CLASS} is undeclared",
                self::REMEDIATION_B,
                11016
            ),
            new Issue(
                self::TraitParentReference,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_LOW,
                "Reference to parent from trait {TRAIT}",
                self::REMEDIATION_B,
                11017
            ),
            new Issue(
                self::UndeclaredVariable,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Variable \${VARIABLE} is undeclared",
                self::REMEDIATION_B,
                11018
            ),
            new Issue(
                self::UndeclaredTypeParameter,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Parameter of undeclared type {TYPE}",
                self::REMEDIATION_B,
                11019
            ),
            new Issue(
                self::UndeclaredTypeProperty,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Property {PROPERTY} has undeclared type {TYPE}",
                self::REMEDIATION_B,
                11020
            ),
            new Issue(
                self::UndeclaredClosureScope,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Reference to undeclared class {CLASS} in @phan-closure-scope",
                self::REMEDIATION_B,
                11021
            ),
            new Issue(
                self::ClassContainsAbstractMethod,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "non-abstract class {CLASS} contains abstract method {METHOD} declared at {FILE}:{LINE}",
                self::REMEDIATION_B,
                11022
            ),
            new Issue(
                self::ClassContainsAbstractMethodInternal,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "non-abstract class {CLASS} contains abstract internal method {METHOD}",
                self::REMEDIATION_B,
                11023
            ),
            new Issue(
                self::UndeclaredAliasedMethodOfTrait,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Alias {METHOD} was defined for a method {METHOD} which does not exist in trait {TRAIT}",
                self::REMEDIATION_B,
                11024
            ),
            new Issue(
                self::RequiredTraitNotAdded,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Required trait {TRAIT} for trait adaptation was not added to class",
                self::REMEDIATION_B,
                11025
            ),
            new Issue(
                self::AmbiguousTraitAliasSource,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Trait alias {METHOD} has an ambiguous source method {METHOD} with more than one possible source trait. Possibilities: {TRAIT}",
                self::REMEDIATION_B,
                11026
            ),
            new Issue(
                self::UndeclaredVariableDim,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_LOW,
                "Variable \${VARIABLE} was undeclared, but array fields are being added to it.",
                self::REMEDIATION_B,
                11027
            ),
            new Issue(
                self::UndeclaredTypeReturnType,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Return type of {METHOD} is undeclared type {TYPE}",
                self::REMEDIATION_B,
                11028
            ),
            new Issue(
                self::UndeclaredTypeThrowsType,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "@throws type of {METHOD} has undeclared type {TYPE}",
                self::REMEDIATION_B,
                11034
            ),
            new Issue(
                self::UndeclaredClassAliasOriginal,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_CRITICAL,
                "Reference to undeclared class {CLASS} for the original class of a class_alias for {CLASS}",
                self::REMEDIATION_B,
                11029
            ),
            new Issue(
                self::UndeclaredClassInCallable,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Reference to undeclared class {CLASS} in callable {METHOD}",
                self::REMEDIATION_B,
                11030
            ),
            new Issue(
                self::UndeclaredStaticMethodInCallable,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Reference to undeclared static method {METHOD} in callable",
                self::REMEDIATION_B,
                11031
            ),
            new Issue(
                self::UndeclaredFunctionInCallable,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Call to undeclared function {FUNCTION} in callable",
                self::REMEDIATION_B,
                11032
            ),
            new Issue(
                self::UndeclaredMethodInCallable,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Call to undeclared method {METHOD} in callable. Possible object type(s) for that method are {TYPE}",
                self::REMEDIATION_B,
                11033
            ),
            new Issue(
                self::EmptyFQSENInCallable,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Possible call to a function '{FUNCTIONLIKE}' with an empty FQSEN.",
                self::REMEDIATION_B,
                11035
            ),
            new Issue(
                self::EmptyFQSENInClasslike,
                self::CATEGORY_UNDEFINED,
                self::SEVERITY_NORMAL,
                "Possible use of a classlike '{CLASSLIKE}' with an empty FQSEN.",
                self::REMEDIATION_B,
                11036
            ),

            // Issue::CATEGORY_ANALYSIS
            new Issue(
                self::Unanalyzable,
                self::CATEGORY_ANALYSIS,
                self::SEVERITY_LOW,
                "Expression is unanalyzable or feature is unimplemented. Please create an issue at https://github.com/phan/phan/issues/new.",
                self::REMEDIATION_B,
                2000
            ),
            new Issue(
                self::UnanalyzableInheritance,
                self::CATEGORY_ANALYSIS,
                self::SEVERITY_LOW,
                "Unable to determine the method(s) which {METHOD} overrides, but Phan inferred that it did override something earlier. Please create an issue at https://github.com/phan/phan/issues/new with a test case.",
                self::REMEDIATION_B,
                2001
            ),

            // Issue::CATEGORY_TYPE
            new Issue(
                self::TypeMismatchProperty,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Assigning {TYPE} to property but {PROPERTY} is {TYPE}",
                self::REMEDIATION_B,
                10001
            ),
            new Issue(
                self::PartialTypeMismatchProperty,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Assigning {TYPE} to property but {PROPERTY} is {TYPE} ({TYPE} is incompatible)",
                self::REMEDIATION_B,
                10063
            ),
            new Issue(
                self::PossiblyNullTypeMismatchProperty,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Assigning {TYPE} to property but {PROPERTY} is {TYPE} ({TYPE} is incompatible)",
                self::REMEDIATION_B,
                10064
            ),
            new Issue(
                self::PossiblyFalseTypeMismatchProperty,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Assigning {TYPE} to property but {PROPERTY} is {TYPE} ({TYPE} is incompatible)",
                self::REMEDIATION_B,
                10065
            ),
            new Issue(
                self::TypeMismatchDefault,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Default value for {TYPE} \${VARIABLE} can't be {TYPE}",
                self::REMEDIATION_B,
                10002
            ),
            new Issue(
                self::TypeMismatchVariadicComment,
                self::CATEGORY_TYPE,
                self::SEVERITY_LOW,
                "{PARAMETER} is variadic in comment, but not variadic in param ({PARAMETER})",
                self::REMEDIATION_B,
                10021
            ),
            new Issue(
                self::TypeMismatchVariadicParam,
                self::CATEGORY_TYPE,
                self::SEVERITY_LOW,
                "{PARAMETER} is not variadic in comment, but variadic in param ({PARAMETER})",
                self::REMEDIATION_B,
                10023
            ),
            new Issue(
                self::TypeMismatchArgument,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} ({VARIABLE}) is {TYPE} but {FUNCTIONLIKE}() takes {TYPE} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                10003
            ),
            new Issue(
                self::TypeMismatchArgumentInternal,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} ({VARIABLE}) is {TYPE} but {FUNCTIONLIKE}() takes {TYPE}",
                self::REMEDIATION_B,
                10004
            ),
            new Issue(
                self::PartialTypeMismatchArgument,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} ({VARIABLE}) is {TYPE} but {FUNCTIONLIKE}() takes {TYPE} ({TYPE} is incompatible) defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                10054
            ),
            new Issue(
                self::PartialTypeMismatchArgumentInternal,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} ({VARIABLE}) is {TYPE} but {FUNCTIONLIKE}() takes {TYPE} ({TYPE} is incompatible)",
                self::REMEDIATION_B,
                10055
            ),
            new Issue(
                self::PossiblyNullTypeArgument,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} ({VARIABLE}) is {TYPE} but {FUNCTIONLIKE}() takes {TYPE} ({TYPE} is incompatible) defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                10056
            ),
            new Issue(
                self::PossiblyNullTypeArgumentInternal,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} ({VARIABLE}) is {TYPE} but {FUNCTIONLIKE}() takes {TYPE} ({TYPE} is incompatible)",
                self::REMEDIATION_B,
                10057
            ),
            new Issue(
                self::PossiblyFalseTypeArgument,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} ({VARIABLE}) is {TYPE} but {FUNCTIONLIKE}() takes {TYPE} ({TYPE} is incompatible) defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                10058
            ),
            new Issue(
                self::PossiblyFalseTypeArgumentInternal,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} ({VARIABLE}) is {TYPE} but {FUNCTIONLIKE}() takes {TYPE} ({TYPE} is incompatible)",
                self::REMEDIATION_B,
                10059
            ),
            new Issue(
                self::TypeMismatchReturn,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Returning type {TYPE} but {FUNCTIONLIKE}() is declared to return {TYPE}",
                self::REMEDIATION_B,
                10005
            ),
            new Issue(
                self::PartialTypeMismatchReturn,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Returning type {TYPE} but {FUNCTIONLIKE}() is declared to return {TYPE} ({TYPE} is incompatible)",
                self::REMEDIATION_B,
                10060
            ),
            new Issue(
                self::PossiblyNullTypeReturn,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Returning type {TYPE} but {FUNCTIONLIKE}() is declared to return {TYPE} ({TYPE} is incompatible)",
                self::REMEDIATION_B,
                10061
            ),
            new Issue(
                self::PossiblyFalseTypeReturn,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Returning type {TYPE} but {FUNCTIONLIKE}() is declared to return {TYPE} ({TYPE} is incompatible)",
                self::REMEDIATION_B,
                10062
            ),
            new Issue(
                self::TypeMismatchDeclaredReturn,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Doc-block of {METHOD} contains declared return type {TYPE} which is incompatible with the return type {TYPE} declared in the signature",
                self::REMEDIATION_B,
                10020
            ),
            new Issue(
                self::TypeMismatchDeclaredReturnNullable,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Doc-block of {METHOD} has declared return type {TYPE} which is not a permitted replacement of the nullable return type {TYPE} declared in the signature ('?T' should be documented as 'T|null' or '?T')",
                self::REMEDIATION_B,
                10028
            ),
            new Issue(
                self::TypeMismatchDeclaredParam,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Doc-block of \${VARIABLE} in {METHOD} contains phpdoc param type {TYPE} which is incompatible with the param type {TYPE} declared in the signature",
                self::REMEDIATION_B,
                10022
            ),
            new Issue(
                self::TypeMismatchDeclaredParamNullable,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Doc-block of \${VARIABLE} in {METHOD} is phpdoc param type {TYPE} which is not a permitted replacement of the nullable param type {TYPE} declared in the signature ('?T' should be documented as 'T|null' or '?T')",
                self::REMEDIATION_B,
                10027
            ),
            new Issue(
                self::TypeMissingReturn,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Method {METHOD} is declared to return {TYPE} but has no return value",
                self::REMEDIATION_B,
                10006
            ),
            new Issue(
                self::TypeMismatchForeach,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "{TYPE} passed to foreach instead of array",
                self::REMEDIATION_B,
                10007
            ),
            new Issue(
                self::TypeArrayOperator,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Invalid array operator between types {TYPE} and {TYPE}",
                self::REMEDIATION_B,
                10008
            ),
            new Issue(
                self::TypeArraySuspicious,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Suspicious array access to {TYPE}",
                self::REMEDIATION_B,
                10009
            ),
            new Issue(
                self::TypeArrayUnsetSuspicious,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Suspicious attempt to unset an offset of a value of type {TYPE}",
                self::REMEDIATION_B,
                10048
            ),
            new Issue(
                self::TypeComparisonToArray,
                self::CATEGORY_TYPE,
                self::SEVERITY_LOW,
                "{TYPE} to array comparison",
                self::REMEDIATION_B,
                10010
            ),
            new Issue(
                self::TypeComparisonFromArray,
                self::CATEGORY_TYPE,
                self::SEVERITY_LOW,
                "array to {TYPE} comparison",
                self::REMEDIATION_B,
                10011
            ),
            new Issue(
                self::TypeConversionFromArray,
                self::CATEGORY_TYPE,
                self::SEVERITY_LOW,
                "array to {TYPE} conversion",
                self::REMEDIATION_B,
                10012
            ),
            new Issue(
                self::TypeInstantiateAbstract,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Instantiation of abstract class {CLASS}",
                self::REMEDIATION_B,
                10013
            ),
            new Issue(
                self::TypeInstantiateInterface,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Instantiation of interface {INTERFACE}",
                self::REMEDIATION_B,
                10014
            ),
            new Issue(
                self::TypeInvalidClosureScope,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Invalid @phan-closure-scope: expected a class name, got {TYPE}",
                self::REMEDIATION_B,
                10024
            ),
            new Issue(
                self::TypeInvalidRightOperand,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Invalid operator: left operand is array and right is not",
                self::REMEDIATION_B,
                10015
            ),
            new Issue(
                self::TypeInvalidLeftOperand,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Invalid operator: right operand is array and left is not",
                self::REMEDIATION_B,
                10016
            ),
            new Issue(
                self::TypeParentConstructorCalled,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Must call parent::__construct() from {CLASS} which extends {CLASS}",
                self::REMEDIATION_B,
                10017
            ),
            new Issue(
                self::TypeNonVarPassByRef,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Only variables can be passed by reference at argument {INDEX} of {FUNCTIONLIKE}()",
                self::REMEDIATION_B,
                10018
            ),
            new Issue(
                self::NonClassMethodCall,
                self::CATEGORY_TYPE,
                self::SEVERITY_CRITICAL,
                "Call to method {METHOD} on non-class type {TYPE}",
                self::REMEDIATION_B,
                10019
            ),
            new Issue(
                self::TypeVoidAssignment,
                self::CATEGORY_TYPE,
                self::SEVERITY_LOW,
                "Cannot assign void return value",
                self::REMEDIATION_B,
                10000
            ),
            new Issue(
                self::TypeSuspiciousIndirectVariable,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'Indirect variable ${(expr)} has invalid inner expression type {TYPE}, expected string/integer',
                self::REMEDIATION_B,
                10025
            ),
            new Issue(
                self::TypeMagicVoidWithReturn,
                self::CATEGORY_TYPE,
                self::SEVERITY_LOW,
                'Found a return statement with a value in the implementation of the magic method {METHOD}, expected void return type',
                self::REMEDIATION_B,
                10026
            ),
            new Issue(
                self::TypeInvalidInstanceof,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'Found an instanceof class name of type {TYPE}, but class name must be a valid object or a string',
                self::REMEDIATION_B,
                10029
            ),
            new Issue(
                self::TypeMismatchDimAssignment,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'When appending to a value of type {TYPE}, found an array access index of type {TYPE}, but expected the index to be of type {TYPE}',
                self::REMEDIATION_B,
                10030
            ),
            new Issue(
                self::TypeMismatchDimEmpty,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'Assigning to an empty array index of a value of type {TYPE}, but expected the index to exist and be of type {TYPE}',
                self::REMEDIATION_B,
                10031
            ),
            new Issue(
                self::TypeMismatchDimFetch,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'When fetching an array index from a value of type {TYPE}, found an array index of type {TYPE}, but expected the index to be of type {TYPE}',
                self::REMEDIATION_B,
                10032
            ),
            new Issue(
                self::TypeMismatchDimFetchNullable,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'When fetching an array index from a value of type {TYPE}, found an array index of type {TYPE}, but expected the index to be of the non-nullable type {TYPE}',
                self::REMEDIATION_B,
                10044
            ),
            new Issue(
                self::TypeInvalidCallableArraySize,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'In a place where phan was expecting a callable, saw an array of size {COUNT}, but callable arrays must be of size 2',
                self::REMEDIATION_B,
                10033
            ),
            new Issue(
                self::TypeInvalidCallableArrayKey,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'In a place where phan was expecting a callable, saw an array with an unexpected key for element #{INDEX} (expected [$class_or_expr, $method_name])',
                self::REMEDIATION_B,
                10034
            ),
            new Issue(
                self::TypeInvalidCallableObjectOfMethod,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'In a place where phan was expecting a callable, saw a two-element array with a class or expression with an unexpected type {TYPE} (expected a class type or string). Method name was {METHOD}',
                self::REMEDIATION_B,
                10035
            ),
            new Issue(
                self::TypeExpectedObject,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'Expected an object instance but saw expression with type {TYPE}',
                self::REMEDIATION_B,
                10036
            ),
            new Issue(
                self::TypeExpectedObjectOrClassName,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'Expected an object instance or the name of a class but saw expression with type {TYPE}',
                self::REMEDIATION_B,
                10037
            ),
            new Issue(
                self::TypeExpectedObjectPropAccess,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'Expected an object instance when accessing an instance property, but saw an expression with type {TYPE}',
                self::REMEDIATION_B,
                10038
            ),
            new Issue(
                self::TypeExpectedObjectStaticPropAccess,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'Expected an object instance or a class name when accessing a static property, but saw an expression with type {TYPE}',
                self::REMEDIATION_B,
                10039
            ),
            new Issue(
                self::TypeExpectedObjectPropAccessButGotNull,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'Expected an object instance when accessing an instance property, but saw an expression with type {TYPE}',
                self::REMEDIATION_B,
                10040
            ),
            new Issue(
                self::TypeMismatchUnpackKey,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'When unpacking a value of type {TYPE}, the value\'s keys were of type {TYPE}, but the keys should be consecutive integers starting from 0',
                self::REMEDIATION_B,
                10041
            ),
            new Issue(
                self::TypeMismatchUnpackValue,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'Attempting to unpack a value of type {TYPE} which does not contain any subtypes of iterable (such as array or Traversable)',
                self::REMEDIATION_B,
                10042
            ),
            new Issue(
                self::TypeMismatchArrayDestructuringKey,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                'Attempting an array destructing assignment with a key of type {TYPE} but the only key types of the right hand side are of type {TYPE}',
                self::REMEDIATION_B,
                10043
            ),
            new Issue(
                self::TypeArraySuspiciousNullable,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Suspicious array access to nullable {TYPE}",
                self::REMEDIATION_B,
                10045
            ),
            new Issue(
                self::TypeInvalidDimOffset,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Invalid offset {SCALAR} of array type {TYPE}",
                self::REMEDIATION_B,
                10046
            ),
            new Issue(
                self::TypeInvalidDimOffsetArrayDestructuring,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Invalid offset {SCALAR} of array type {TYPE} in an array destructuring assignment",
                self::REMEDIATION_B,
                10047
            ),
            new Issue(
                self::TypeSuspiciousEcho,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "Suspicious argument {TYPE} for an echo/print statement",
                self::REMEDIATION_B,
                10049
            ),
            new Issue(
                self::TypeInvalidThrowsNonObject,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "@throws annotation of {FUNCTIONLIKE} has invalid non-object type {TYPE}, expected a class",
                self::REMEDIATION_B,
                10050
            ),
            new Issue(
                self::TypeInvalidThrowsIsTrait,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "@throws annotation of {FUNCTIONLIKE} has invalid trait type {TYPE}, expected a class",
                self::REMEDIATION_B,
                10051
            ),
            new Issue(
                self::TypeInvalidThrowsIsInterface,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "@throws annotation of {FUNCTIONLIKE} has suspicious interface type {TYPE} for an @throws annotation, expected class (PHP allows interfaces to be caught, so this might be intentional)",
                self::REMEDIATION_B,
                10052
            ),
            new Issue(
                self::TypeInvalidThrowsNonThrowable,
                self::CATEGORY_TYPE,
                self::SEVERITY_NORMAL,
                "@throws annotation of {FUNCTIONLIKE} has suspicious class type {TYPE}, which does not extend Error/Exception",
                self::REMEDIATION_B,
                10053
            ),
            // Issue::CATEGORY_VARIABLE
            new Issue(
                self::VariableUseClause,
                self::CATEGORY_VARIABLE,
                self::SEVERITY_NORMAL,
                "Non-variables not allowed within use clause",
                self::REMEDIATION_B,
                12000
            ),

            // Issue::CATEGORY_STATIC
            new Issue(
                self::StaticCallToNonStatic,
                self::CATEGORY_STATIC,
                self::SEVERITY_NORMAL,
                "Static call to non-static method {METHOD} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                9000
            ),

            // Issue::CATEGORY_CONTEXT
            new Issue(
                self::ContextNotObject,
                self::CATEGORY_CONTEXT,
                self::SEVERITY_CRITICAL,
                "Cannot access {CLASS} when not in object context",
                self::REMEDIATION_B,
                4000
            ),
            new Issue(
                self::ContextNotObjectInCallable,
                self::CATEGORY_CONTEXT,
                self::SEVERITY_NORMAL,
                "Cannot access {CLASS} when not in object context, but code is using callable {METHOD}",
                self::REMEDIATION_B,
                4001
            ),

            // Issue::CATEGORY_DEPRECATED
            new Issue(
                self::DeprecatedFunction,
                self::CATEGORY_DEPRECATED,
                self::SEVERITY_NORMAL,
                "Call to deprecated function {FUNCTIONLIKE}() defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                5000
            ),
            new Issue(
                self::DeprecatedFunctionInternal,
                self::CATEGORY_DEPRECATED,
                self::SEVERITY_NORMAL,
                "Call to deprecated function {FUNCTIONLIKE}()",
                self::REMEDIATION_B,
                5005
            ),
            new Issue(
                self::DeprecatedClass,
                self::CATEGORY_DEPRECATED,
                self::SEVERITY_NORMAL,
                "Call to deprecated class {CLASS} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                5001
            ),
            new Issue(
                self::DeprecatedProperty,
                self::CATEGORY_DEPRECATED,
                self::SEVERITY_NORMAL,
                "Reference to deprecated property {PROPERTY} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                5002
            ),
            new Issue(
                self::DeprecatedInterface,
                self::CATEGORY_DEPRECATED,
                self::SEVERITY_NORMAL,
                "Using a deprecated interface {INTERFACE} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                5003
            ),
            new Issue(
                self::DeprecatedTrait,
                self::CATEGORY_DEPRECATED,
                self::SEVERITY_NORMAL,
                "Using a deprecated trait {TRAIT} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                5004
            ),

            // Issue::CATEGORY_PARAMETER
            new Issue(
                self::ParamReqAfterOpt,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Required argument follows optional",
                self::REMEDIATION_B,
                7000
            ),
            new Issue(
                self::ParamTooMany,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Call with {COUNT} arg(s) to {FUNCTIONLIKE}() which only takes {COUNT} arg(s) defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                7001
            ),
            new Issue(
                self::ParamTooManyInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Call with {COUNT} arg(s) to {FUNCTIONLIKE}() which only takes {COUNT} arg(s)",
                self::REMEDIATION_B,
                7002
            ),
            new Issue(
                self::ParamTooManyCallable,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Call with {COUNT} arg(s) to {FUNCTIONLIKE}() (As a provided callable) which only takes {COUNT} arg(s) defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                7043
            ),
            new Issue(
                self::ParamTooFew,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Call with {COUNT} arg(s) to {FUNCTIONLIKE}() which requires {COUNT} arg(s) defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                7003
            ),
            new Issue(
                self::ParamTooFewInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Call with {COUNT} arg(s) to {FUNCTIONLIKE}() which requires {COUNT} arg(s)",
                self::REMEDIATION_B,
                7004
            ),
            new Issue(
                self::ParamTooFewCallable,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Call with {COUNT} arg(s) to {FUNCTIONLIKE}() (as a provided callable) which requires {COUNT} arg(s) defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                7044
            ),
            new Issue(
                self::ParamSpecial1,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} ({PARAMETER}) is {TYPE} but {FUNCTIONLIKE}() takes {TYPE} when argument {INDEX} is {TYPE}",
                self::REMEDIATION_B,
                7005
            ),
            new Issue(
                self::ParamSpecial2,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} ({PARAMETER}) is {TYPE} but {FUNCTIONLIKE}() takes {TYPE} when passed only one argument",
                self::REMEDIATION_B,
                7006
            ),
            new Issue(
                self::ParamSpecial3,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "The last argument to {FUNCTIONLIKE} must be of type {TYPE}",
                self::REMEDIATION_B,
                7007
            ),
            new Issue(
                self::ParamSpecial4,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "The second to last argument to {FUNCTIONLIKE} must be of type {TYPE}",
                self::REMEDIATION_B,
                7008
            ),
            new Issue(
                self::ParamTypeMismatch,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Argument {INDEX} is {TYPE} but {FUNCTIONLIKE}() takes {TYPE}",
                self::REMEDIATION_B,
                7009
            ),
            new Issue(
                self::ParamSignatureMismatch,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with {METHOD} defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7010
            ),
            new Issue(
                self::ParamSignatureMismatchInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD}",
                self::REMEDIATION_B,
                7011
            ),
            new Issue(
                self::ParamRedefined,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Redefinition of parameter {PARAMETER}",
                self::REMEDIATION_B,
                7012
            ),
            // TODO: Optionally, change the other message to say that it's based off of phpdoc and LSP in a future PR.
            // NOTE: Incompatibilities in the param list are SEVERITY_NORMAL, because the php interpreter emits a notice.
            // Incompatibilities in the return types are SEVERITY_CRITICAL, because the php interpreter will throw an Error.
            new Issue(
                self::ParamSignatureRealMismatchReturnType,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_CRITICAL,
                "Declaration of {METHOD} should be compatible with {METHOD} (method returning '{TYPE}' cannot override method returning '{TYPE}') defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7013
            ),
            new Issue(
                self::ParamSignatureRealMismatchReturnTypeInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_CRITICAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD} (method returning '{TYPE}' cannot override method returning '{TYPE}')",
                self::REMEDIATION_B,
                7014
            ),
            new Issue(
                self::ParamSignaturePHPDocMismatchReturnType,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Declaration of real/@method {METHOD} should be compatible with real/@method {METHOD} (method returning '{TYPE}' cannot override method returning '{TYPE}') defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7033
            ),
            new Issue(
                self::ParamSignatureRealMismatchParamType,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with {METHOD} (parameter #{INDEX} of type '{TYPE}' cannot replace original parameter of type '{TYPE}') defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7015
            ),
            new Issue(
                self::ParamSignatureRealMismatchParamTypeInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD} (parameter #{INDEX} of type '{TYPE}' cannot replace original parameter of type '{TYPE}')",
                self::REMEDIATION_B,
                7016
            ),
            new Issue(
                self::ParamSignaturePHPDocMismatchParamType,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Declaration of real/@method {METHOD} should be compatible with real/@method {METHOD} (parameter #{INDEX} of type '{TYPE}' cannot replace original parameter of type '{TYPE}') defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7034
            ),
            new Issue(
                self::ParamSignatureRealMismatchHasParamType,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with {METHOD} (parameter #{INDEX} has type '{TYPE}' cannot replace original parameter with no type) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7017
            ),
            new Issue(
                self::ParamSignatureRealMismatchHasParamTypeInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD} (parameter #{INDEX} has type '{TYPE}' cannot replace original parameter with no type)",
                self::REMEDIATION_B,
                7018
            ),
            new Issue(
                self::ParamSignaturePHPDocMismatchHasParamType,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Declaration of real/@method {METHOD} should be compatible with real/@method {METHOD} (parameter #{INDEX} has type '{TYPE}' cannot replace original parameter with no type) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7035
            ),
            new Issue(
                self::ParamSignatureRealMismatchHasNoParamType,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with {METHOD} (parameter #{INDEX} with no type cannot replace original parameter with type '{TYPE}') defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7019
            ),
            new Issue(
                self::ParamSignatureRealMismatchHasNoParamTypeInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD} (parameter #{INDEX} with no type cannot replace original parameter with type '{TYPE}')",
                self::REMEDIATION_B,
                7020
            ),
            new Issue(
                self::ParamSignaturePHPDocMismatchHasNoParamType,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Declaration of real/@method {METHOD} should be compatible with real/@method {METHOD} (parameter #{INDEX} with no type cannot replace original parameter with type '{TYPE}') defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7036
            ),
            new Issue(
                self::ParamSignatureRealMismatchParamVariadic,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with {METHOD} (parameter #{INDEX} is a variadic parameter replacing a non-variadic parameter) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7021
            ),
            new Issue(
                self::ParamSignatureRealMismatchParamVariadicInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD} (parameter #{INDEX} is a variadic parameter replacing a non-variadic parameter)",
                self::REMEDIATION_B,
                7022
            ),
            new Issue(
                self::ParamSignaturePHPDocMismatchParamVariadic,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Declaration of real/@method {METHOD} should be compatible with real/@method {METHOD} (parameter #{INDEX} is a variadic parameter replacing a non-variadic parameter) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7037
            ),
            new Issue(
                self::ParamSignatureRealMismatchParamNotVariadic,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with {METHOD} (parameter #{INDEX} is a non-variadic parameter replacing a variadic parameter) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7023
            ),
            new Issue(
                self::ParamSignatureRealMismatchParamNotVariadicInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD} (parameter #{INDEX} is a non-variadic parameter replacing a variadic parameter)",
                self::REMEDIATION_B,
                7024
            ),
            new Issue(
                self::ParamSignaturePHPDocMismatchParamNotVariadic,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Declaration of real/@method {METHOD} should be compatible with real/@method {METHOD} (parameter #{INDEX} is a non-variadic parameter replacing a variadic parameter) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7038
            ),
            new Issue(
                self::ParamSignatureRealMismatchParamIsReference,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with {METHOD} (parameter #{INDEX} is a reference parameter overriding a non-reference parameter) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7025
            ),
            new Issue(
                self::ParamSignatureRealMismatchParamIsReferenceInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD} (parameter #{INDEX} is a reference parameter overriding a non-reference parameter)",
                self::REMEDIATION_B,
                7026
            ),
            new Issue(
                self::ParamSignaturePHPDocMismatchParamIsReference,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Declaration of real/@method {METHOD} should be compatible with real/@method {METHOD} (parameter #{INDEX} is a reference parameter overriding a non-reference parameter) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7039
            ),
            new Issue(
                self::ParamSignatureRealMismatchParamIsNotReference,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with {METHOD} (parameter #{INDEX} is a non-reference parameter overriding a reference parameter) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7027
            ),
            new Issue(
                self::ParamSignatureRealMismatchParamIsNotReferenceInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD} (parameter #{INDEX} is a non-reference parameter overriding a reference parameter)",
                self::REMEDIATION_B,
                7028
            ),
            new Issue(
                self::ParamSignaturePHPDocMismatchParamIsNotReference,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Declaration of real/@method {METHOD} should be compatible with real/@method {METHOD} (parameter #{INDEX} is a non-reference parameter overriding a reference parameter) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7040
            ),
            new Issue(
                self::ParamSignatureRealMismatchTooFewParameters,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with {METHOD} (the method override accepts {COUNT} parameter(s), but the overridden method can accept {COUNT}) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7029
            ),
            new Issue(
                self::ParamSignatureRealMismatchTooFewParametersInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD} (the method override accepts {COUNT} parameter(s), but the overridden method can accept {COUNT})",
                self::REMEDIATION_B,
                7030
            ),
            new Issue(
                self::ParamSignaturePHPDocMismatchTooFewParameters,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Declaration of real/@method {METHOD} should be compatible with real/@method {METHOD} (the method override accepts {COUNT} parameter(s), but the overridden method can accept {COUNT}) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7041
            ),
            new Issue(
                self::ParamSignatureRealMismatchTooManyRequiredParameters,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with {METHOD} (the method override requires {COUNT} parameter(s), but the overridden method requires only {COUNT}) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7031
            ),
            new Issue(
                self::ParamSignatureRealMismatchTooManyRequiredParametersInternal,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} should be compatible with internal {METHOD} (the method override requires {COUNT} parameter(s), but the overridden method requires only {COUNT})",
                self::REMEDIATION_B,
                7032
            ),
            new Issue(
                self::ParamSignaturePHPDocMismatchTooManyRequiredParameters,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_LOW,
                "Declaration of real/@method {METHOD} should be compatible with real/@method {METHOD} (the method override requires {COUNT} parameter(s), but the overridden method requires only {COUNT}) defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                7042
            ),
            new Issue(
                self::ParamSuspiciousOrder,
                self::CATEGORY_PARAMETER,
                self::SEVERITY_NORMAL,
                "Argument #{INDEX} of this call to {FUNCTIONLIKE} is typically a literal or constant but isn't, but argument #{INDEX} (which is typically a variable) is a literal or constant. The arguments may be in the wrong order.",
                self::REMEDIATION_B,
                7045
            ),

            // Issue::CATEGORY_NOOP
            new Issue(
                self::NoopProperty,
                self::CATEGORY_NOOP,
                self::SEVERITY_LOW,
                "Unused property",
                self::REMEDIATION_B,
                6000
            ),
            new Issue(
                self::NoopArray,
                self::CATEGORY_NOOP,
                self::SEVERITY_LOW,
                "Unused array",
                self::REMEDIATION_B,
                6001
            ),
            new Issue(
                self::NoopConstant,
                self::CATEGORY_NOOP,
                self::SEVERITY_LOW,
                "Unused constant",
                self::REMEDIATION_B,
                6002
            ),
            new Issue(
                self::NoopClosure,
                self::CATEGORY_NOOP,
                self::SEVERITY_LOW,
                "Unused closure",
                self::REMEDIATION_B,
                6003
            ),
            new Issue(
                self::NoopVariable,
                self::CATEGORY_NOOP,
                self::SEVERITY_LOW,
                "Unused variable",
                self::REMEDIATION_B,
                6004
            ),
            new Issue(
                self::NoopUnaryOperator,
                self::CATEGORY_NOOP,
                self::SEVERITY_LOW,
                "Unused result of a unary '{OPERATOR}' operator",
                self::REMEDIATION_B,
                6020
            ),
            new Issue(
                self::NoopBinaryOperator,
                self::CATEGORY_NOOP,
                self::SEVERITY_LOW,
                "Unused result of a binary '{OPERATOR}' operator",
                self::REMEDIATION_B,
                6021
            ),
            new Issue(
                self::UnreferencedClass,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to class {CLASS}",
                self::REMEDIATION_B,
                6005
            ),
            new Issue(
                self::UnreferencedConstant,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to global constant {CONST}",
                self::REMEDIATION_B,
                6008
            ),
            new Issue(
                self::UnreferencedFunction,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to function {FUNCTION}",
                self::REMEDIATION_B,
                6009
            ),
            new Issue(
                self::UnreferencedClosure,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to closure {FUNCTION}",
                self::REMEDIATION_B,
                6010
            ),
            new Issue(
                self::UnreferencedPublicMethod,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to public method {METHOD}",
                self::REMEDIATION_B,
                6011
            ),
            new Issue(
                self::UnreferencedProtectedMethod,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to protected method {METHOD}",
                self::REMEDIATION_B,
                6012
            ),
            new Issue(
                self::UnreferencedPrivateMethod,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to private method {METHOD}",
                self::REMEDIATION_B,
                6013
            ),
            new Issue(
                self::UnreferencedPublicProperty,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to public property {PROPERTY}",
                self::REMEDIATION_B,
                6014
            ),
            new Issue(
                self::UnreferencedProtectedProperty,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to protected property {PROPERTY}",
                self::REMEDIATION_B,
                6015
            ),
            new Issue(
                self::UnreferencedPrivateProperty,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to private property {PROPERTY}",
                self::REMEDIATION_B,
                6016
            ),
            new Issue(
                self::WriteOnlyPublicProperty,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero read references to public property {PROPERTY}",
                self::REMEDIATION_B,
                6025
            ),
            new Issue(
                self::WriteOnlyProtectedProperty,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero read references to protected property {PROPERTY}",
                self::REMEDIATION_B,
                6026
            ),
            new Issue(
                self::WriteOnlyPrivateProperty,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero read references to private property {PROPERTY}",
                self::REMEDIATION_B,
                6027
            ),
            new Issue(
                self::UnreferencedPublicClassConstant,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to public class constant {CONST}",
                self::REMEDIATION_B,
                6017
            ),
            new Issue(
                self::UnreferencedProtectedClassConstant,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to protected class constant {CONST}",
                self::REMEDIATION_B,
                6018
            ),
            new Issue(
                self::UnreferencedPrivateClassConstant,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to public class constant {CONST}",
                self::REMEDIATION_B,
                6019
            ),
            new Issue(
                self::UnreferencedUseNormal,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to use statement for classlike/namespace {CLASSLIKE} ({CLASSLIKE})",
                self::REMEDIATION_B,
                6022
            ),
            new Issue(
                self::UnreferencedUseFunction,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to use statement for function {FUNCTION} ({FUNCTION})",
                self::REMEDIATION_B,
                6023
            ),
            new Issue(
                self::UnreferencedUseConstant,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Possibly zero references to use statement for constant {CONST} ({CONST})",
                self::REMEDIATION_B,
                6024
            ),
            new Issue(
                self::UnreachableCatch,
                self::CATEGORY_NOOP,
                self::SEVERITY_NORMAL,
                "Catch statement for {CLASSLIKE} is unreachable. An earlier catch statement at line {LINE} caught the ancestor class/interface {CLASSLIKE}",
                self::REMEDIATION_B,
                6028
            ),

            // Issue::CATEGORY_REDEFINE
            new Issue(
                self::RedefineClass,
                self::CATEGORY_REDEFINE,
                self::SEVERITY_NORMAL,
                "{CLASS} defined at {FILE}:{LINE} was previously defined as {CLASS} at {FILE}:{LINE}",
                self::REMEDIATION_B,
                8000
            ),
            new Issue(
                self::RedefineClassInternal,
                self::CATEGORY_REDEFINE,
                self::SEVERITY_NORMAL,
                "{CLASS} defined at {FILE}:{LINE} was previously defined as {CLASS} internally",
                self::REMEDIATION_B,
                8001
            ),
            new Issue(
                self::RedefineFunction,
                self::CATEGORY_REDEFINE,
                self::SEVERITY_NORMAL,
                "Function {FUNCTION} defined at {FILE}:{LINE} was previously defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                8002
            ),
            new Issue(
                self::RedefineFunctionInternal,
                self::CATEGORY_REDEFINE,
                self::SEVERITY_NORMAL,
                "Function {FUNCTION} defined at {FILE}:{LINE} was previously defined internally",
                self::REMEDIATION_B,
                8003
            ),
            new Issue(
                self::IncompatibleCompositionProp,
                self::CATEGORY_REDEFINE,
                self::SEVERITY_NORMAL,
                "{TRAIT} and {TRAIT} define the same property ({PROPERTY}) in the composition of {CLASS}. However, the definition differs and is considered incompatible. Class was composed in {FILE} on line {LINE}",
                self::REMEDIATION_B,
                8004
            ),
            new Issue(
                self::IncompatibleCompositionMethod,
                self::CATEGORY_REDEFINE,
                self::SEVERITY_NORMAL,
                "Declaration of {METHOD} must be compatible with {METHOD} in {FILE} on line {LINE}",
                self::REMEDIATION_B,
                8005
            ),
            new Issue(
                self::RedefineClassAlias,
                self::CATEGORY_REDEFINE,
                self::SEVERITY_NORMAL,
                "{CLASS} aliased at {FILE}:{LINE} was previously defined as {CLASS} at {FILE}:{LINE}",
                self::REMEDIATION_B,
                8006
            ),

            // Issue::CATEGORY_ACCESS
            new Issue(
                self::AccessPropertyProtected,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Cannot access protected property {PROPERTY}",
                self::REMEDIATION_B,
                1000
            ),
            new Issue(
                self::AccessPropertyPrivate,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Cannot access private property {PROPERTY}",
                self::REMEDIATION_B,
                1001
            ),
            new Issue(
                self::AccessMethodProtected,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Cannot access protected method {METHOD} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                1002
            ),
            new Issue(
                self::AccessMethodPrivate,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Cannot access private method {METHOD} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                1003
            ),
            new Issue(
                self::AccessSignatureMismatch,
                self::CATEGORY_ACCESS,
                self::SEVERITY_NORMAL,
                "Access level to {METHOD} must be compatible with {METHOD} defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                1004
            ),
            new Issue(
                self::AccessSignatureMismatchInternal,
                self::CATEGORY_ACCESS,
                self::SEVERITY_NORMAL,
                "Access level to {METHOD} must be compatible with internal {METHOD}",
                self::REMEDIATION_B,
                1005
            ),
            new Issue(
                self::AccessStaticToNonStatic,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Cannot make static method {METHOD}() non static",
                self::REMEDIATION_B,
                1006
            ),
            new Issue(
                self::AccessNonStaticToStatic,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Cannot make non static method {METHOD}() static",
                self::REMEDIATION_B,
                1007
            ),
            new Issue(
                self::AccessClassConstantPrivate,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Cannot access private class constant {CONST} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                1008
            ),
            new Issue(
                self::AccessClassConstantProtected,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Cannot access protected class constant {CONST} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                1009
            ),
            new Issue(
                self::AccessPropertyStaticAsNonStatic,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Accessing static property {PROPERTY} as non static",
                self::REMEDIATION_B,
                1010
            ),
            new Issue(
                self::AccessOwnConstructor,
                self::CATEGORY_ACCESS,
                self::SEVERITY_NORMAL,
                "Accessing own constructor directly via {CLASS}::__construct",
                self::REMEDIATION_B,
                1020
            ),
            new Issue(
                self::AccessMethodProtectedWithCallMagicMethod,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Cannot access protected method {METHOD} defined at {FILE}:{LINE} (if this call should be handled by __call, consider adding a @method tag to the class)",
                self::REMEDIATION_B,
                1011
            ),
            new Issue(
                self::AccessMethodPrivateWithCallMagicMethod,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Cannot access private method {METHOD} defined at {FILE}:{LINE} (if this call should be handled by __call, consider adding a @method tag to the class)",
                self::REMEDIATION_B,
                1012
            ),
            new Issue(
                self::AccessWrongInheritanceCategory,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Attempting to inherit {CLASSLIKE} defined at {FILE}:{LINE} as if it were a {CLASSLIKE}",
                self::REMEDIATION_B,
                1013
            ),
            new Issue(
                self::AccessWrongInheritanceCategoryInternal,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Attempting to inherit internal {CLASSLIKE} as if it were a {CLASSLIKE}",
                self::REMEDIATION_B,
                1014
            ),
            new Issue(
                self::AccessExtendsFinalClass,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Attempting to extend from final class {CLASS} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                1015
            ),
            new Issue(
                self::AccessExtendsFinalClassInternal,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Attempting to extend from final internal class {CLASS}",
                self::REMEDIATION_B,
                1016
            ),
            new Issue(
                self::AccessOverridesFinalMethod,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Declaration of method {METHOD} overrides final method {METHOD} defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                1017
            ),
            new Issue(
                self::AccessOverridesFinalMethodInternal,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Declaration of method {METHOD} overrides final internal method {METHOD}",
                self::REMEDIATION_B,
                1018
            ),
            new Issue(
                self::AccessOverridesFinalMethodPHPDoc,
                self::CATEGORY_ACCESS,
                self::SEVERITY_LOW,
                "Declaration of phpdoc method {METHOD} is an unnecessary override of final method {METHOD} defined in {FILE}:{LINE}",
                self::REMEDIATION_B,
                1019
            ),
            new Issue(
                self::AccessPropertyNonStaticAsStatic,
                self::CATEGORY_ACCESS,
                self::SEVERITY_CRITICAL,
                "Accessing non static property {PROPERTY} as static",
                self::REMEDIATION_B,
                1021
            ),

            // Issue::CATEGORY_COMPATIBLE
            new Issue(
                self::CompatiblePHP7,
                self::CATEGORY_COMPATIBLE,
                self::SEVERITY_NORMAL,
                "Expression may not be PHP 7 compatible",
                self::REMEDIATION_B,
                3000
            ),
            new Issue(
                self::CompatibleExpressionPHP7,
                self::CATEGORY_COMPATIBLE,
                self::SEVERITY_NORMAL,
                "{CLASS} expression may not be PHP 7 compatible",
                self::REMEDIATION_B,
                3001
            ),
            new Issue(
                self::CompatibleNullableTypePHP70,
                self::CATEGORY_COMPATIBLE,
                self::SEVERITY_CRITICAL,
                "Nullable type '{TYPE}' is not compatible with PHP 7.0",
                self::REMEDIATION_B,
                3002
            ),
            new Issue(
                self::CompatibleShortArrayAssignPHP70,
                self::CATEGORY_COMPATIBLE,
                self::SEVERITY_CRITICAL,
                "Square bracket syntax for an array destructuring assignment is not compatible with PHP 7.0",
                self::REMEDIATION_A,
                3003
            ),
            new Issue(
                self::CompatibleKeyedArrayAssignPHP70,
                self::CATEGORY_COMPATIBLE,
                self::SEVERITY_CRITICAL,
                "Using array keys in an array destructuring assignment is not compatible with PHP 7.0",
                self::REMEDIATION_B,
                3004
            ),
            new Issue(
                self::CompatibleVoidTypePHP70,
                self::CATEGORY_COMPATIBLE,
                self::SEVERITY_CRITICAL,
                "Return type '{TYPE}' means the absense of a return value starting in PHP 7.1. In PHP 7.0, void refers to a class/interface with the name 'void'",
                self::REMEDIATION_B,
                3005
            ),
            new Issue(
                self::CompatibleIterableTypePHP70,
                self::CATEGORY_COMPATIBLE,
                self::SEVERITY_CRITICAL,
                "Return type '{TYPE}' means a Traversable/array value starting in PHP 7.1. In PHP 7.0, iterable refers to a class/interface with the name 'iterable'",
                self::REMEDIATION_B,
                3006
            ),
            new Issue(
                self::CompatibleObjectTypePHP71,
                self::CATEGORY_COMPATIBLE,
                self::SEVERITY_CRITICAL,
                "Type '{TYPE}' refers to any object starting in PHP 7.2. In PHP 7.1 and earlier, it refers to a class/interface with the name 'object'",
                self::REMEDIATION_B,
                3007
            ),

            // Issue::CATEGORY_GENERIC
            new Issue(
                self::TemplateTypeConstant,
                self::CATEGORY_GENERIC,
                self::SEVERITY_NORMAL,
                "constant {CONST} may not have a template type",
                self::REMEDIATION_B,
                14000
            ),
            new Issue(
                self::TemplateTypeStaticMethod,
                self::CATEGORY_GENERIC,
                self::SEVERITY_NORMAL,
                "static method {METHOD} may not use template types",
                self::REMEDIATION_B,
                14001
            ),
            new Issue(
                self::TemplateTypeStaticProperty,
                self::CATEGORY_GENERIC,
                self::SEVERITY_NORMAL,
                "static property {PROPERTY} may not have a template type",
                self::REMEDIATION_B,
                14002
            ),
            new Issue(
                self::GenericGlobalVariable,
                self::CATEGORY_GENERIC,
                self::SEVERITY_NORMAL,
                "Global variable {VARIABLE} may not be assigned an instance of a generic class",
                self::REMEDIATION_B,
                14003
            ),
            new Issue(
                self::GenericConstructorTypes,
                self::CATEGORY_GENERIC,
                self::SEVERITY_NORMAL,
                "Missing template parameters {PARAMETER} on constructor for generic class {CLASS}",
                self::REMEDIATION_B,
                14004
            ),

            // Issue::CATEGORY_INTERNAL
            new Issue(
                self::AccessConstantInternal,
                self::CATEGORY_INTERNAL,
                self::SEVERITY_NORMAL,
                "Cannot access internal constant {CONST} of namepace {NAMESPACE} defined at {FILE}:{LINE} from namespace {NAMESPACE}",
                self::REMEDIATION_B,
                15000
            ),
            new Issue(
                self::AccessClassInternal,
                self::CATEGORY_INTERNAL,
                self::SEVERITY_NORMAL,
                "Cannot access internal {CLASS} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                15001
            ),
            new Issue(
                self::AccessClassConstantInternal,
                self::CATEGORY_INTERNAL,
                self::SEVERITY_NORMAL,
                "Cannot access internal class constant {CONST} defined at {FILE}:{LINE}",
                self::REMEDIATION_B,
                15002
            ),
            new Issue(
                self::AccessPropertyInternal,
                self::CATEGORY_INTERNAL,
                self::SEVERITY_NORMAL,
                "Cannot access internal property {PROPERTY} of namespace {NAMESPACE} defined at {FILE}:{LINE} from namespace {NAMESPACE}",
                self::REMEDIATION_B,
                15003
            ),
            new Issue(
                self::AccessMethodInternal,
                self::CATEGORY_INTERNAL,
                self::SEVERITY_NORMAL,
                "Cannot access internal method {METHOD} of namespace {NAMESPACE} defined at {FILE}:{LINE} from namespace {NAMESPACE}",
                self::REMEDIATION_B,
                15004
            ),

            // Issue::CATEGORY_COMMENT
            new Issue(
                self::InvalidCommentForDeclarationType,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "The phpdoc comment for {COMMENT} cannot occur on a {TYPE}",
                self::REMEDIATION_B,
                16000
            ),
            new Issue(
                self::MisspelledAnnotation,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "Saw misspelled annotation {COMMENT}, should be one of {COMMENT}",
                self::REMEDIATION_B,
                16001
            ),
            new Issue(
                self::UnextractableAnnotation,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "Saw unextractable annotation for comment '{COMMENT}'",
                self::REMEDIATION_B,
                16002
            ),
            new Issue(
                self::UnextractableAnnotationPart,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "Saw unextractable annotation for a fragment of comment '{COMMENT}': '{COMMENT}'",
                self::REMEDIATION_B,
                16003
            ),
            new Issue(
                self::UnextractableAnnotationSuffix,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "Saw a token Phan may have failed to parse after '{COMMENT}': after {TYPE}, saw '{COMMENT}'",
                self::REMEDIATION_B,
                16009
            ),
            new Issue(
                self::UnextractableAnnotationElementName,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "Saw possibly unextractable annotation for a fragment of comment '{COMMENT}': after {TYPE}, did not see an element name (will guess based on comment order)",
                self::REMEDIATION_B,
                16010
            ),
            new Issue(
                self::CommentParamWithoutRealParam,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "Saw an @param annotation for {VARIABLE}, but it was not found in the param list of {FUNCTIONLIKE}",
                self::REMEDIATION_B,
                16004
            ),
            new Issue(
                self::CommentParamOnEmptyParamList,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "Saw an @param annotation for {VARIABLE}, but the param list of {FUNCTIONLIKE} is empty",
                self::REMEDIATION_B,
                16005
            ),
            new Issue(
                self::CommentOverrideOnNonOverrideMethod,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "Saw an @override annotation for method {METHOD}, but could not find an overridden method and it is not a magic method",
                self::REMEDIATION_B,
                16006
            ),
            new Issue(
                self::CommentOverrideOnNonOverrideConstant,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "Saw an @override annotation for class constant {CONST}, but could not find an overridden constant",
                self::REMEDIATION_B,
                16007
            ),
            new Issue(
                self::CommentParamOutOfOrder,
                self::CATEGORY_COMMENT,
                self::SEVERITY_LOW,
                "Expected @param annotation for {VARIABLE} to be before the @param annotation for {VARIABLE}",
                self::REMEDIATION_A,
                16008
            ),
        ];

        self::sanityCheckErrorList($error_list);
        // Verified the error meets preconditions, now add it.
        foreach ($error_list as $error) {
            $error_type = $error->getType();
            $error_map[$error_type] = $error;
        }

        return $error_map;
    }

    /**
     * @param array<int,Issue> $error_list
     * @return void
     * @suppress PhanPluginUnusedVariable (error_map and unique_type_id_set)
     */
    private static function sanityCheckErrorList(array $error_list)
    {
        $error_map = [];
        $unique_type_id_set = [];
        foreach ($error_list as $error) {
            $error_type = $error->getType();
            \assert(!\array_key_exists($error_type, $error_map), "Issue of type $error_type has multiple definitions");
            \assert(\strncmp($error_type, 'Phan', 4) === 0, "Issue of type $error_type should begin with 'Phan'");

            $error_type_id = $error->getTypeId();
            \assert(!\array_key_exists($error_type_id, $unique_type_id_set), "Multiple issues exist with pylint error id $error_type_id");
            $unique_type_id_set[$error_type_id] = $error;
            $category = $error->getCategory();
            $expected_category_for_type_id_bitpos = (int)floor($error_type_id / 1000);
            $expected_category_for_type_id = 1 << $expected_category_for_type_id_bitpos;
            if ($category !== $expected_category_for_type_id) {
                assert(false, sprintf(
                    "Expected error %s of type %d to be category %d(1<<%d), got 1<<%d\n",
                    $error_type,
                    $error_type_id,
                    $category,
                    (int)round(log($category, 2)),
                    $expected_category_for_type_id_bitpos
                ));
            }
            $error_map[$error_type] = $error;
        }
    }


    /**
     * @return string
     */
    public function getType() : string
    {
        return $this->type;
    }

    /**
     * @return int (Unique integer code corresponding to getType())
     */
    public function getTypeId() : int
    {
        return $this->type_id;
    }

    /**
     * @return int
     */
    public function getCategory() : int
    {
        return $this->category;
    }

    /**
     * @return string
     * The name of the category
     */
    public static function getNameForCategory(int $category) : string
    {
        return self::CATEGORY_NAME[$category] ?? '';
    }

    /**
     * @return int
     */
    public function getSeverity() : int
    {
        return $this->severity;
    }

    /**
     * @return string
     * A descriptive name of the severity of the issue
     */
    public function getSeverityName() : string
    {
        switch ($this->getSeverity()) {
            case self::SEVERITY_LOW:
                return 'low';
            case self::SEVERITY_NORMAL:
                return 'normal';
            case self::SEVERITY_CRITICAL:
                return 'critical';
            default:
                throw new \AssertionError('Unknown severity ' . $this->getSeverity());
        }
    }

    /**
     * @return int
     * @suppress PhanUnreferencedPublicMethod (no reporters use this right now)
     */
    public function getRemediationDifficulty() : int
    {
        return $this->remediation_difficulty;
    }

    /**
     * @return string
     */
    public function getTemplate() : string
    {
        return $this->template;
    }

    /**
     * @return string - template with the information needed to colorize this.
     */
    public function getTemplateRaw() : string
    {
        return $this->template_raw;
    }

    /**
     * @return IssueInstance
     */
    public function __invoke(
        string $file,
        int $line,
        array $template_parameters = []
    ) : IssueInstance {
        // TODO: Add callable to expanded union types instead
        return new IssueInstance(
            $this,
            $file,
            $line,
            $template_parameters
        );
    }

    /**
     * return Issue
     */
    public static function fromType(string $type) : Issue
    {
        $error_map = self::issueMap();

        \assert(
            !empty($error_map[$type]),
            "Undefined error type $type"
        );

        return $error_map[$type];
    }

    /**
     * @param string $type
     * The type of the issue
     *
     * @param string $file
     * The name of the file where the issue was found
     *
     * @param int $line
     * The line number (start) where the issue was found
     *
     * @param string|int|float|bool|Type|UnionType|FQSEN|TypedElement|UnaddressableTypedElement ...$template_parameters
     * Any template parameters required for the issue
     * message
     *
     * @return void
     * @suppress PhanUnreferencedPublicMethod
     */
    public static function emit(
        string $type,
        string $file,
        int $line,
        ...$template_parameters
    ) {
        self::emitWithParameters(
            $type,
            $file,
            $line,
            $template_parameters
        );
    }

    /**
     * @param string $type
     * The type of the issue
     *
     * @param string $file
     * The name of the file where the issue was found
     *
     * @param int $line
     * The line number (start) where the issue was found
     *
     * @param array<int,string|int|float|bool|Type|UnionType|FQSEN|TypedElement|UnaddressableTypedElement> $template_parameters
     * Any template parameters required for the issue
     * message
     *
     * @return void
     */
    public static function emitWithParameters(
        string $type,
        string $file,
        int $line,
        array $template_parameters
    ) {
        $issue = self::fromType($type);

        self::emitInstance(
            $issue($file, $line, $template_parameters)
        );
    }

    /**
     * @param IssueInstance $issue_instance
     * An issue instance to emit
     *
     * @return void
     */
    public static function emitInstance(
        IssueInstance $issue_instance
    ) {
        Phan::getIssueCollector()->collectIssue($issue_instance);
    }

    /**
     * @param CodeBase $code_base
     * The code base within which we're operating
     *
     * @param Context $context
     * The context in which the instance was found
     *
     * @param IssueInstance $issue_instance
     * An issue instance to emit
     *
     * @return void
     */
    public static function maybeEmitInstance(
        CodeBase $code_base,
        Context $context,
        IssueInstance $issue_instance
    ) {
        // If this issue type has been suppressed in
        // the config, ignore it

        if (!Config::getValue('disable_suppression')) {
            if (in_array(
                $issue_instance->getIssue()->getType(),
                Config::getValue('suppress_issue_types') ?? []
            )
            ) {
                return;
            }

            // If a white-list of allowed issue types is defined,
            // only emit issues on the white-list
            $whitelist_issue_types = Config::getValue('whitelist_issue_types') ?? [];
            if (count($whitelist_issue_types) > 0
                && !in_array(
                    $issue_instance->getIssue()->getType(),
                    $whitelist_issue_types
                )
            ) {
                return;
            }

            // If this issue type has been suppressed in
            // this scope from a doc block, ignore it.
            if ($context->hasSuppressIssue(
                $code_base,
                $issue_instance->getIssue()->getType()
            )) {
                return;
            }
        }

        self::emitInstance($issue_instance);
    }

    /**
     * @param CodeBase $code_base
     * The code base within which we're operating
     *
     * @param Context $context
     * The context in which the node we're going to be looking
     * at exits.
     *
     * @param string $issue_type
     * The type of issue to emit such as Issue::ParentlessClass
     *
     * @param int $lineno
     * The line number where the issue was found
     *
     * @param string|int|float|bool|Type|UnionType|FQSEN|TypedElement|UnaddressableTypedElement ...$parameters
     * Template parameters for the issue's error message.
     * If these are objects, they should define __toString()
     *
     * @return void
     */
    public static function maybeEmit(
        CodeBase $code_base,
        Context $context,
        string $issue_type,
        int $lineno,
        ...$parameters
    ) {
        self::maybeEmitWithParameters(
            $code_base,
            $context,
            $issue_type,
            $lineno,
            $parameters
        );
    }

    /**
     * @param CodeBase $code_base
     * The code base within which we're operating
     *
     * @param Context $context
     * The context in which the node we're going to be looking
     * at exits.
     *
     * @param string $issue_type
     * The type of issue to emit such as Issue::ParentlessClass
     *
     * @param int $lineno
     * The line number where the issue was found
     *
     * @param array<int,string|int|float|bool|Type|UnionType|FQSEN|TypedElement|UnaddressableTypedElement> $parameters
     * Template parameters for the issue's error message
     *
     * @return void
     */
    public static function maybeEmitWithParameters(
        CodeBase $code_base,
        Context $context,
        string $issue_type,
        int $lineno,
        array $parameters
    ) {
        // If this issue type has been suppressed in
        // the config, ignore it
        if (!Config::getValue('disable_suppression')
            && in_array(
                $issue_type,
                Config::getValue('suppress_issue_types') ?? []
            )
        ) {
            return;
        }
        // If a white-list of allowed issue types is defined,
        // only emit issues on the white-list
        if (!Config::getValue('disable_suppression')
            && count(Config::getValue('whitelist_issue_types')) > 0
            && !in_array(
                $issue_type,
                Config::getValue('whitelist_issue_types') ?? []
            )
        ) {
            return;
        }

        if (!Config::getValue('disable_suppression')
            && $context->hasSuppressIssue($code_base, $issue_type)
        ) {
            return;
        }

        Issue::emitWithParameters(
            $issue_type,
            $context->getFile(),
            $lineno,
            $parameters
        );
    }
}
<?php declare(strict_types=1);
namespace Phan;

use Phan\AST\ASTSimplifier;
use Phan\AST\Parser;
use Phan\AST\PhanAnnotationAdder;
use Phan\AST\TolerantASTConverter\ParseException;
use Phan\AST\Visitor\Element;
use Phan\Analysis\DuplicateFunctionAnalyzer;
use Phan\Analysis\ParameterTypesAnalyzer;
use Phan\Analysis\ReturnTypesAnalyzer;
use Phan\Analysis\ReferenceCountsAnalyzer;
use Phan\Analysis\ThrowsTypesAnalyzer;
use Phan\Language\Context;
use Phan\Language\Element\Func;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Method;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Library\FileCache;
use Phan\Parse\ParseVisitor;
use Phan\Plugin\ConfigPluginSet;

use ast\Node;
use ParseError;

/**
 * This class is the entry point into the static analyzer.
 */
class Analysis
{
    /**
     * This first pass parses code and looks for the subset
     * of issues that can be found without having to have
     * an understanding of the entire code base.
     *
     * @param CodeBase $code_base
     * The CodeBase represents state across the entire
     * code base. This is a mutable object which is
     * populated as we parse files
     *
     * @param string $file_path
     * The full path to a file we'd like to parse
     *
     * @param bool $suppress_parse_errors
     *
     * @param ?string $override_contents
     * If this is not null, this function will act as if $file_path's contents
     * were $override_contents
     *
     * @param bool $is_php_internal_stub
     * If this is true, this function will act as though the parsed constants, functions, and classes are actually part of PHP or it's extension's internals.
     * See autoload_internal_extension_signatures.
     *
     * @return Context
     *
     * @suppress PhanAccessMethodInternal
     */
    public static function parseFile(CodeBase $code_base, string $file_path, bool $suppress_parse_errors = false, string $override_contents = null, bool $is_php_internal_stub = false) : Context
    {
        $original_file_path = $file_path;
        $code_base->setCurrentParsedFile($file_path);
        if ($is_php_internal_stub) {
            /** @see \Phan\Language\FileRef->isPHPInternal() */
            $file_path = 'internal';
        }
        $context = (new Context)->withFile($file_path);

        // Convert the file to an Abstract Syntax Tree
        // before passing it on to the recursive version
        // of this method

        $real_file_path = Config::projectPath($original_file_path);
        if (\is_string($override_contents)) {
            $cache_entry = FileCache::addEntry($real_file_path, $override_contents);
        } else {
            $cache_entry = FileCache::getOrReadEntry($real_file_path);
        }
        $file_contents = $cache_entry->getContents();
        if ($file_contents === '') {
            if ($is_php_internal_stub) {
                throw new \InvalidArgumentException("Unexpected empty php file for autoload_internal_extension_signatures: path=" . json_encode($original_file_path, JSON_UNESCAPED_SLASHES));
            }
            // php-ast would return null for 0 byte files as an implementation detail.
            // Make Phan consistently emit this warning.
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::EmptyFile,
                0,
                $original_file_path
            );

            return $context;
        }
        try {
            $node = Parser::parseCode($code_base, $context, $file_path, $file_contents, $suppress_parse_errors);
        } catch (ParseError $e) {
            return $context;
        } catch (ParseException $e) {
            return $context;
        }

        if (Config::getValue('dump_ast')) {
            echo $file_path . "\n"
                . str_repeat("\u{00AF}", strlen($file_path))
                . "\n";
            Debug::printNode($node);
            return $context;
        }

        if (empty($node)) {
            // php-ast would return an empty node for 0 byte files in older releases.
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::EmptyFile,
                0,
                $original_file_path
            );

            return $context;
        }

        if (Config::getValue('simplify_ast')) {
            try {
                $newNode = ASTSimplifier::applyStatic($node);  // Transform the original AST, leaving the original unmodified.
                $node = $newNode;  // Analyze the new AST instead.
            } catch (\Exception $e) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::SyntaxError,  // Not the right kind of error. I don't think it would throw, anyway.
                    $e->getLine(),
                    $e->getMessage()
                );
            }
        }

        $context = self::parseNodeInContext(
            $code_base,
            $context,
            $node
        );
        $code_base->addParsedNamespaceMap($context->getFile(), $context->getNamespace(), $context->getNamespaceId(), $context->getNamespaceMap());
        return $context;
    }

    /**
     * Parse the given node in the given context populating
     * the code base within the context as a side effect. The
     * returned context is the new context from within the
     * given node.
     *
     * @param CodeBase $code_base
     * The global code base in which we store all
     * state
     *
     * @param Context $context
     * The context in which this node exists
     *
     * @param Node $node
     * A node to parse and scan for errors
     *
     * @return Context
     * The context from within the node is returned
     */
    public static function parseNodeInContext(CodeBase $code_base, Context $context, Node $node) : Context
    {
        // Save a reference to the outer context
        $outer_context = $context;

        // Visit the given node populating the code base
        // with anything we learn and get a new context
        // indicating the state of the world within the
        // given node.
        // NOTE: This is called extremely frequently
        // (E.g. on a large number of the analyzed project's vendor dependencies,
        // proportionally to the node count in the files), so code style was sacrificed for performance.
        // Equivalent to (new ParseVisitor(...))($node), which uses ParseVisitor->__invoke
        $context = (new ParseVisitor(
            $code_base,
            $context->withLineNumberStart($node->lineno ?? 0)
        ))->{Element::VISIT_LOOKUP_TABLE[$node->kind] ?? 'handleMissingNodeKind'}($node);

        \assert(!empty($context), 'Context cannot be null');
        $kind = $node->kind;

        // \ast\AST_GROUP_USE has \ast\AST_USE as a child.
        // We don't want to use block twice in the parse phase.
        // (E.g. `use MyNS\{const A, const B}` would lack the MyNs part if this were to recurse.
        // And \ast\AST_DECLARE has AST_CONST_DECL as a child, so don't parse a constant declaration either.
        if ($kind === \ast\AST_GROUP_USE || $kind === \ast\AST_DECLARE) {
            return $context;
        }

        // Recurse into each child node
        $child_context = $context;
        foreach ($node->children as $child_node) {
            // Skip any non Node children.
            if (!($child_node instanceof Node)) {
                continue;
            }

            // Step into each child node and get an
            // updated context for the node
            $child_context = self::parseNodeInContext($code_base, $child_context, $child_node);

            \assert(!empty($child_context), 'Context cannot be null');
        }

        // For closed context elements (that have an inner scope)
        // return the outer context instead of their inner context
        // after we finish parsing their children.
        if (in_array($kind, [
            \ast\AST_CLASS,
            \ast\AST_METHOD,
            \ast\AST_FUNC_DECL,
            \ast\AST_CLOSURE,
        ], true)) {
            return $outer_context;
        }
        if ($kind === \ast\AST_STMT_LIST) {
            // Workaround that ensures that the context from namespace blocks gets passed to the caller.
            return $child_context;
        }

        // Pass the context back up to our parent
        return $context;
    }

    /**
     * Take a pass over all functions verifying various
     * states.
     * @suppress PhanTypeArraySuspicious https://github.com/phan/phan/issues/642
     *
     * @return void
     */
    public static function analyzeFunctions(CodeBase $code_base, array $file_filter = null)
    {
        $plugin_set = ConfigPluginSet::instance();
        $has_function_or_method_plugins = $plugin_set->hasAnalyzeFunctionPlugins() || $plugin_set->hasAnalyzeMethodPlugins();
        $show_progress = CLI::shouldShowProgress();
        $analyze_function_or_method = function (FunctionInterface $function_or_method) use (
            $code_base,
            $plugin_set,
            $has_function_or_method_plugins,
            $file_filter
        ) {
            if ($function_or_method->isPHPInternal()) {
                return;
            }
            // Phan always has to call this, to add default values to types of parameters.
            $function_or_method->ensureScopeInitialized($code_base);

            // If there is an array limiting the set of files, skip this file if it's not in the list,
            if (\is_array($file_filter) && !isset($file_filter[$function_or_method->getContext()->getFile()])) {
                return;
            }

            DuplicateFunctionAnalyzer::analyzeDuplicateFunction(
                $code_base,
                $function_or_method
            );

            // This is the most time consuming step.
            // Can probably apply this to other functions, but this was the slowest.
            ParameterTypesAnalyzer::analyzeParameterTypes(
                $code_base,
                $function_or_method
            );

            ReturnTypesAnalyzer::analyzeReturnTypes(
                $code_base,
                $function_or_method
            );

            ThrowsTypesAnalyzer::analyzeThrowsTypes(
                $code_base,
                $function_or_method
            );
            // Let any plugins analyze the methods or functions
            // XXX: Add a way to run plugins on all functions/methods, this was limited for speed.
            // Assumes that the given plugins will emit an issue in the same file as the function/method,
            // which isn't necessarily the case.
            // 0.06
            if ($has_function_or_method_plugins) {
                if ($function_or_method instanceof Func) {
                    $plugin_set->analyzeFunction(
                        $code_base,
                        $function_or_method
                    );
                } elseif ($function_or_method instanceof Method) {
                    $plugin_set->analyzeMethod(
                        $code_base,
                        $function_or_method
                    );
                }
            }
        };

        // Analyze user-defined method declarations.
        // Plugins may also analyze user-defined methods here.
        $i = 0;
        if ($show_progress) {
            CLI::progress('function', 0.0);
        }
        $function_map = $code_base->getFunctionMap();
        foreach ($function_map as $function) {  // iterate, ignoring $fqsen
            if ($show_progress) {
                CLI::progress('function', (++$i)/(\count($function_map)));
            }
            $analyze_function_or_method($function);
        }

        // Analyze user-defined method declarations.
        // Plugins may also analyze user-defined methods here.
        $i = 0;
        $method_set = $code_base->getMethodSet();
        if ($show_progress) {
            CLI::progress('method', 0.0);
        }
        foreach ($method_set as $method) {
            if ($show_progress) {
                // I suspect that method analysis is hydrating some of the classes,
                // adding even more inherited methods to the end of the set.
                // This recalculation is needed so that the progress bar is accurate.
                CLI::progress('method', (++$i)/(\count($method_set)));
            }
            $analyze_function_or_method($method);
        }
    }

    /**
     * Loads extra logic for analyzing function and method calls.
     *
     * @return void
     */
    public static function loadMethodPlugins(CodeBase $code_base)
    {
        $plugin_set = ConfigPluginSet::instance();
        foreach ($plugin_set->getReturnTypeOverrides($code_base) as $fqsen_string => $closure) {
            if (\stripos($fqsen_string, '::') !== false) {
                $fqsen = FullyQualifiedMethodName::fromFullyQualifiedString($fqsen_string);
                $class_fqsen = $fqsen->getFullyQualifiedClassName();
                // We have to call hasClassWithFQSEN before calling hasMethodWithFQSEN in order to autoload the internal function signatures.
                // TODO: Move class autoloading into hasMethodWithFQSEN()?
                if ($code_base->hasClassWithFQSEN($class_fqsen)) {
                    // This is an override of a method.
                    if ($code_base->hasMethodWithFQSEN($fqsen)) {
                        $method = $code_base->getMethodByFQSEN($fqsen);
                        $method->setDependentReturnTypeClosure($closure);
                    }
                }
            } else {
                // This is an override of a function.
                $fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
                if ($code_base->hasFunctionWithFQSEN($fqsen)) {
                    $function = $code_base->getFunctionByFQSEN($fqsen);
                    $function->setDependentReturnTypeClosure($closure);
                }
            }
        }

        foreach ($plugin_set->getAnalyzeFunctionCallClosures($code_base) as $fqsen_string => $closure) {
            if (stripos($fqsen_string, '::') !== false) {
                // This is an override of a method.
                $fqsen = FullyQualifiedMethodName::fromFullyQualifiedString($fqsen_string);
                if ($code_base->hasMethodWithFQSEN($fqsen)) {
                    $method = $code_base->getMethodByFQSEN($fqsen);
                    $method->setFunctionCallAnalyzer($closure);
                }
            } else {
                // This is an override of a function.
                $fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
                if ($code_base->hasFunctionWithFQSEN($fqsen)) {
                    $function = $code_base->getFunctionByFQSEN($fqsen);
                    $function->setFunctionCallAnalyzer($closure);
                }
            }
        }
    }

    /**
     * Take a pass over all classes/traits/interfaces
     * verifying various states.
     *
     * @return void
     */
    public static function analyzeClasses(CodeBase $code_base, array $path_filter = null)
    {
        $classes = $code_base->getUserDefinedClassMap();
        if (\is_array($path_filter)) {
            // If a list of files is provided, then limit analysis to classes defined in those files.
            $old_classes = $classes;
            $classes = [];
            foreach ($old_classes as $class) {
                if (isset($path_filter[$class->getContext()->getFile()])) {
                    $classes[] = $class;
                }
            }
        }
        foreach ($classes as $class) {
            $class->analyze($code_base);
        }
    }

    /**
     * Take a look at all globally accessible elements and see if
     * we can find any dead code that is never referenced
     *
     * @return void
     */
    public static function analyzeDeadCode(CodeBase $code_base)
    {
        // Check to see if dead code detection is enabled. Keep
        // in mind that the results here are just a guess and
        // we can't tell with certainty that anything is
        // definitely unreferenced.
        if (!Config::getValue('dead_code_detection')) {
            return;
        }

        ReferenceCountsAnalyzer::analyzeReferenceCounts($code_base);
    }

    /**
     * Once we know what the universe looks like we
     * can scan for more complicated issues.
     *
     * @param CodeBase $code_base
     * The global code base holding all state
     *
     * @param ?string $override_contents
     * If this is not null, this function will act as if $file_path's contents
     * were $override_contents
     *
     * @return Context
     * @suppress PhanAccessMethodInternal
     */
    public static function analyzeFile(
        CodeBase $code_base,
        string $file_path,
        string $override_contents = null
    ) : Context {
        // Set the file on the context
        $context = (new Context)->withFile($file_path);
        $context->importNamespaceMapFromParsePhase($code_base);

        $code_base->setCurrentAnalyzedFile($file_path);

        // Convert the file to an Abstract Syntax Tree
        // before passing it on to the recursive version
        // of this method
        try {
            $real_file_path = Config::projectPath($file_path);
            if (\is_string($override_contents)) {
                $cache_entry = FileCache::addEntry($real_file_path, $override_contents);
            } else {
                $cache_entry = FileCache::getOrReadEntry($real_file_path);
            }
            $file_contents = $cache_entry->getContents();
            if ($file_contents === '') {
                // php-ast would return null for 0 byte files as an implementation detail.
                // Make Phan consistently emit this warning.
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::EmptyFile,
                    0,
                    $file_path
                );

                return $context;
            }
            $node = Parser::parseCode($code_base, $context, $file_path, $file_contents, false);
        } catch (ParseException $parse_error) {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::SyntaxError,
                $parse_error->getLineNumberStart(),
                $parse_error->getMessage()
            );
            return $context;
        } catch (ParseError $parse_error) {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::SyntaxError,
                $parse_error->getLine(),
                $parse_error->getMessage()
            );
            return $context;
        }

        // Ensure we have some content
        if (empty($node)) {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::EmptyFile,
                0,
                $file_path
            );
            return $context;
        }

        if (Config::getValue('simplify_ast')) {
            try {
                $newNode = ASTSimplifier::applyStatic($node);  // Transform the original AST, leaving the original unmodified.
                $node = $newNode;  // Analyze the new AST instead.
            } catch (\Exception $e) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::SyntaxError,  // Not the right kind of error. I don't think it would throw, anyway.
                    $e->getLine(),
                    $e->getMessage()
                );
            }
        }
        PhanAnnotationAdder::applyFull($node);

        ConfigPluginSet::instance()->beforeAnalyzeFile($code_base, $context, $file_contents, $node);

        $context = (new BlockAnalysisVisitor($code_base, $context))($node);
        $context->warnAboutUnusedUseElements($code_base);

        ConfigPluginSet::instance()->afterAnalyzeFile($code_base, $context, $file_contents, $node);
        return $context;
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\UnionTypeVisitor;
use Phan\AST\Visitor\Element;
use Phan\AST\Visitor\FlagVisitorImplementation;
use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\UnionType;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\BoolType;
use Phan\Language\Type\FloatType;
use Phan\Language\Type\IntType;
use Phan\Language\Type\StringType;
use Phan\Issue;
use ast\Node;

// TODO: Improve analysis of bitwise operations, warn if non-int is provided and consistently return int if it's guaranteed
class BinaryOperatorFlagVisitor extends FlagVisitorImplementation
{

    /**
     * @var CodeBase
     */
    private $code_base;

    /**
     * @var Context
     */
    private $context;

    /**
     * Create a new BinaryOperatorFlagVisitor
     */
    public function __construct(
        CodeBase $code_base,
        Context $context
    ) {
        $this->code_base = $code_base;
        $this->context = $context;
    }

    /**
     * @param Node $node
     * A node to visit
     */
    public function __invoke(Node $node)
    {
        return Element::acceptBinaryFlagVisitor($node, $this);
    }

    /**
     * Default visitor for node kinds that do not have
     * an overriding method
     *
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visit(Node $node) : UnionType
    {
        $left = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['left']
        );

        $right = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['right']
        );

        if ($left->isType(ArrayType::instance(false))
            || $right->isType(ArrayType::instance(false))
        ) {
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::TypeArrayOperator,
                $node->lineno ?? 0,
                $left,
                $right
            );

            return UnionType::empty();
        } elseif ($left->hasType(IntType::instance(false))
            && $right->hasType(IntType::instance(false))
        ) {
            return IntType::instance(false)->asUnionType();
        } elseif ($left->hasType(FloatType::instance(false))
            && $right->hasType(FloatType::instance(false))
        ) {
            return FloatType::instance(false)->asUnionType();
        }

        static $int_or_float = null;
        return $int_or_float ?? ($int_or_float = new UnionType([
            IntType::instance(false),
            FloatType::instance(false)
        ]));
    }

    /**
     * Analyzes the `<=>` operator.
     *
     * @param Node $node @phan-unused-param
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinarySpaceship(Node $node) : UnionType
    {
        // TODO: Any sanity checks should go here.

        // <=> returns -1, 0, or 1
        return IntType::instance(false)->asUnionType();
    }

    /**
     * Code can bitwise xor strings byte by byte in PHP
     * @override
     */
    public function visitBinaryBitwiseXor(Node $node) : UnionType
    {
        return $this->analyzeBinaryBitwiseCommon($node);
    }

    /**
     * @override
     */
    public function visitBinaryBitwiseOr(Node $node) : UnionType
    {
        return $this->analyzeBinaryBitwiseCommon($node);
    }

    /**
     * @override
     */
    public function visitBinaryBitwiseAnd(Node $node) : UnionType
    {
        return $this->analyzeBinaryBitwiseCommon($node);
    }

    private function analyzeBinaryBitwiseCommon(Node $node) : UnionType
    {
        $left = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['left']
        );

        $right = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['right']
        );

        if ($left->isType(ArrayType::instance(false))
            || $right->isType(ArrayType::instance(false))
        ) {
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::TypeArrayOperator,
                $node->lineno ?? 0,
                $left,
                $right
            );

            return UnionType::empty();
        } elseif ($left->hasType(IntType::instance(false))
            && $right->hasType(IntType::instance(false))
        ) {
            return IntType::instance(false)->asUnionType();
        } elseif ($left->hasType(StringType::instance(false))
            && $right->hasType(StringType::instance(false))
        ) {
            return StringType::instance(false)->asUnionType();
        }

        return IntType::instance(false)->asUnionType();
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryBoolAnd(Node $node) : UnionType
    {
        return $this->visitBinaryBool($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryBoolXor(Node $node) : UnionType
    {
        return $this->visitBinaryBool($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryBoolOr(Node $node) : UnionType
    {
        return $this->visitBinaryBool($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     * TODO: Check that both types can cast to string or scalars?
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     *
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function visitBinaryConcat(Node $node) : UnionType
    {
        return StringType::instance(false)->asUnionType();
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    private function visitBinaryOpCommon(Node $node)
    {
        $left = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['left']
        );

        $right = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['right']
        );

        $left_is_array_like = $left->isExclusivelyArrayLike();
        $right_is_array_like = $right->isExclusivelyArrayLike();

        $left_can_cast_to_array = $left->canCastToUnionType(
            ArrayType::instance(false)->asUnionType()
        );

        $right_can_cast_to_array = $right->canCastToUnionType(
            ArrayType::instance(false)->asUnionType()
        );

        if ($left_is_array_like
            && !$right->hasArrayLike()
            && !$right_can_cast_to_array
            && !$right->isEmpty()
            && !$right->containsNullable()
            && !$left->hasAnyType($right->getTypeSet())  // TODO: Strict canCastToUnionType() variant?
        ) {
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::TypeComparisonFromArray,
                $node->lineno ?? 0,
                (string)$right
            );
        } elseif ($right_is_array_like
            && !$left->hasArrayLike()
            && !$left_can_cast_to_array
            && !$left->isEmpty()
            && !$left->containsNullable()
            && !$right->hasAnyType($left->getTypeSet())  // TODO: Strict canCastToUnionType() variant?

        ) {
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::TypeComparisonToArray,
                $node->lineno ?? 0,
                (string)$left
            );
        }

        return BoolType::instance(false)->asUnionType();
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryIsIdentical(Node $node) : UnionType
    {
        return $this->visitBinaryOpCommon($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryIsNotIdentical(Node $node) : UnionType
    {
        return $this->visitBinaryOpCommon($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryIsEqual(Node $node) : UnionType
    {
        return $this->visitBinaryOpCommon($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryIsNotEqual(Node $node) : UnionType
    {
        return $this->visitBinaryOpCommon($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryIsSmaller(Node $node) : UnionType
    {
        return $this->visitBinaryOpCommon($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryIsSmallerOrEqual(Node $node) : UnionType
    {
        return $this->visitBinaryOpCommon($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryIsGreater(Node $node) : UnionType
    {
        return $this->visitBinaryOpCommon($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryIsGreaterOrEqual(Node $node) : UnionType
    {
        return $this->visitBinaryOpCommon($node);
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryAdd(Node $node) : UnionType
    {
        $left = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['left']
        );

        $right = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['right']
        );

        static $int_type = null;
        static $float_type = null;
        static $array_type = null;
        static $int_or_float_union_type = null;
        if ($int_type === null) {
            $int_type = IntType::instance(false);
            $float_type = FloatType::instance(false);
            $array_type = ArrayType::instance(false);
            $int_or_float_union_type = new UnionType([
                $int_type,
                $float_type
            ]);
        }

        // fast-track common cases
        if ($left->isType($int_type) && $right->isType($int_type)) {
            return IntType::instance(false)->asUnionType();
        }

        // If both left and right are arrays, then this is array
        // concatenation.
        if ($left->isGenericArray() && $right->isGenericArray()) {
            if ($left->isEqualTo($right)) {
                return $left;
            }
            return ArrayType::combineArrayTypesOverriding($left, $right);
        }

        if (($left->isType($int_type)
            || $left->isType($float_type))
            && ($right->isType($int_type)
            || $right->isType($float_type))
        ) {
            return $float_type->asUnionType();
        }

        $left_is_array = (
            !$left->genericArrayElementTypes()->isEmpty()
            && $left->nonArrayTypes()->isEmpty()
        ) || $left->isType($array_type);

        $right_is_array = (
            !$right->genericArrayElementTypes()->isEmpty()
            && $right->nonArrayTypes()->isEmpty()
        ) || $right->isType($array_type);

        if ($left_is_array || $right_is_array) {
            if ($left_is_array && $right_is_array) {
                // TODO: Make the right types for array offsets completely override the left types?
                return ArrayType::combineArrayTypesOverriding($left, $right);
            }

            if ($left_is_array
                && !$right->canCastToUnionType(
                    ArrayType::instance(false)->asUnionType()
                )
            ) {
                Issue::maybeEmit(
                    $this->code_base,
                    $this->context,
                    Issue::TypeInvalidRightOperand,
                    $node->lineno ?? 0
                );
                return UnionType::empty();
            } elseif ($right_is_array
                && !$left->canCastToUnionType($array_type->asUnionType())
            ) {
                Issue::maybeEmit(
                    $this->code_base,
                    $this->context,
                    Issue::TypeInvalidLeftOperand,
                    $node->lineno ?? 0
                );
                return UnionType::empty();
            } elseif ($left_is_array || $right_is_array) {
                // If it is a '+' and we know one side is an array
                // and the other is unknown, assume array
                return $array_type->asUnionType();
            }
        }

        return $int_or_float_union_type;
    }

    /**
     * Common visitor for binary boolean operations
     *
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    private function visitBinaryBool(Node $node) : UnionType
    {
        // TODO: Check for suspicious operations (E.g. always false, always true, always object)
        $unused_left = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['left']
        );

        $unused_right = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['right']
        );

        return BoolType::instance(false)->asUnionType();
    }

    /**
     * Common visitor for binary boolean operations
     *
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryCoalesce(Node $node) : UnionType
    {
        $left_type = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['left']
        );

        $right_type = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['right']
        );

        // On the left side, remove null and replace '?T' with 'T'
        // Don't bother if the right side contains null.
        if (!$right_type->isEmpty() && $left_type->containsNullable() && !$right_type->containsNullable()) {
            $left_type = $left_type->nonNullableClone();
        }

        return $left_type->withUnionType($right_type);
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CodeBase;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\Element\Clazz;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\TemplateType;

class PropertyTypesAnalyzer
{

    /**
     * Check to see if the given properties have issues
     *
     * @return void
     */
    public static function analyzePropertyTypes(CodeBase $code_base, Clazz $clazz)
    {
        foreach ($clazz->getPropertyMap($code_base) as $property) {
            try {
                $union_type = $property->getUnionType();
            } catch (IssueException $exception) {
                Issue::maybeEmitInstance(
                    $code_base,
                    $property->getContext(),
                    $exception->getIssueInstance()
                );
                continue;
            }

            // Look at each type in the parameter's Union Type
            foreach ($union_type->getTypeSet() as $outer_type) {
                $type = $outer_type;
                // TODO: Expand this to ArrayShapeType
                while ($type instanceof GenericArrayType) {
                    $type = $type->genericArrayElementType();
                }

                // If its a native type or a reference to
                // self, its OK
                if ($type->isNativeType() || $type->isSelfType()) {
                    continue;
                }

                if ($type instanceof TemplateType) {
                    if ($property->isStatic()) {
                        Issue::maybeEmit(
                            $code_base,
                            $property->getContext(),
                            Issue::TemplateTypeStaticProperty,
                            $property->getFileRef()->getLineNumberStart(),
                            (string)$property->getFQSEN()
                        );
                    }
                } else {
                    // Make sure the class exists
                    $type_fqsen = $type->asFQSEN();
                    \assert($type_fqsen instanceof FullyQualifiedClassName, 'fqsens of non-native types must be class names');

                    if (!$code_base->hasClassWithFQSEN($type_fqsen)
                        && !($type instanceof TemplateType)
                        && (
                            !$property->hasDefiningFQSEN()
                            || $property->getDefiningFQSEN() == $property->getFQSEN()
                        )
                    ) {
                        Issue::maybeEmit(
                            $code_base,
                            $property->getContext(),
                            Issue::UndeclaredTypeProperty,
                            $property->getFileRef()->getLineNumberStart(),
                            (string)$property->getFQSEN(),
                            (string)$outer_type
                        );
                    }
                }
            }
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CodeBase;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\Element\Clazz;
use Phan\Language\UnionType;

class CompositionAnalyzer
{

    /**
     * Check to see if signatures match
     *
     * @return void
     */
    public static function analyzeComposition(
        CodeBase $code_base,
        Clazz $class
    ) {
        // Get the list of all inherited classes.
        $inherited_class_list =
            $class->getAncestorClassList($code_base);

        // No chance of failed composition if we don't inherit from
        // lots of stuff.
        if (count($inherited_class_list) < 2) {
            return;
        }

        // Since we're not necessarily getting this list of classes
        // via getClass, we need to ensure that hydration has occurred.
        $class->hydrate($code_base);

        // For each property, find out every inherited class that defines it
        // and check to see if the types line up.
        foreach ($class->getPropertyMap($code_base) as $property) {
            try {
                $property_union_type = $property->getUnionType();
            } catch (IssueException $exception) {
                $property_union_type = UnionType::empty();
            }

            // Check for that property on each inherited
            // class/trait/interface
            foreach ($inherited_class_list as $inherited_class) {
                // Skip any classes/traits/interfaces not defining that
                // property
                if (!$inherited_class->hasPropertyWithName($code_base, $property->getName())) {
                    continue;
                }

                // We don't call `getProperty` because that will create
                // them in some circumstances.
                $inherited_property_map =
                    $inherited_class->getPropertyMap($code_base);

                if (!isset($inherited_property_map[$property->getName()])) {
                    continue;
                }

                // Get the inherited property
                $inherited_property =
                    $inherited_property_map[$property->getName()];

                // Figure out if this property type can cast to the
                // inherited definition's type.
                $can_cast =
                    $property_union_type->canCastToExpandedUnionType(
                        $inherited_property->getUnionType(),
                        $code_base
                    );

                if ($can_cast) {
                    continue;
                }

                // Don't emit an issue if the property suppresses the issue
                // NOTE: The current context is the class, not either of the properties.
                if ($property->hasSuppressIssue(Issue::IncompatibleCompositionProp)) {
                    $property->incrementSuppressIssueCount(Issue::IncompatibleCompositionProp);
                    continue;
                }

                Issue::maybeEmit(
                    $code_base,
                    $property->getContext(),
                    Issue::IncompatibleCompositionProp,
                    $property->getFileRef()->getLineNumberStart(),
                    (string)$class->getFQSEN(),
                    (string)$inherited_class->getFQSEN(),
                    $property->getName(),
                    (string)$class->getFQSEN(),
                    $class->getFileRef()->getFile(),
                    $class->getFileRef()->getLineNumberStart()
                );
            }
        }

        // TODO: This has too much overlap with PhanParamSignatureMismatch
        //       and we should figure out how to merge it.

        /*
        // Get the Class's FQSEN
        $fqsen = $class->getFQSEN();

        $method_map =
            $code_base->getMethodMapByFullyQualifiedClassName($fqsen);

        // For each method, find out every inherited class that defines it
        // and check to see if the types line up.
        foreach ($method_map as $i => $method) {

            $method_union_type = $method->getUnionType();

            // We don't need to analyze constructors for signature
            // compatibility
            if ($method->getName() == '__construct') {
                continue;
            }

            // Get the method parameter list

            // Check for that method on each inherited
            // class/trait/interface
            foreach ($inherited_class_list as $inherited_class) {

                // Skip anything that doesn't define this method
                if (!$inherited_class->hasMethodWithName($code_base, $method->getName())) {
                    continue;
                }

                $inherited_method =
                    $inherited_class->getMethodByName($code_base, $method->getName());

                if ($method == $inherited_method) {
                    continue;
                }

                // Figure out if this method return type can cast to the
                // inherited definition's return type.
                $is_compatible =
                    $method_union_type->canCastToExpandedUnionType(
                        $inherited_method->getUnionType(),
                        $code_base
                    );

                $inherited_method_parameter_map =
                    $inherited_method->getParameterList();

                // Figure out if all of the parameter types line up
                foreach ($method->getParameterList() as $i => $parameter) {
                    $is_compatible = (
                        $is_compatible
                        && isset($inherited_method_parameter_map[$i])
                        && $parameter->getUnionType()->canCastToExpandedUnionType(
                            ($inherited_method_parameter_map[$i])->getUnionType(),
                            $code_base
                        )
                    );
                }

                if ($is_compatible) {
                    continue;
                }

                // Don't emit an issue if the method suppresses the issue
                if ($method->hasSuppressIssue(Issue::IncompatibleCompositionMethod)) {
                    continue;
                }

                Issue::maybeEmit(
                    $code_base,
                    $method->getContext(),
                    Issue::IncompatibleCompositionMethod,
                    $method->getFileRef()->getLineNumberStart(),
                    (string)$method,
                    (string)$inherited_method,
                    $inherited_method->getFileRef()->getFile(),
                    $inherited_method->getFileRef()->getLineNumberStart()
                );
            }
        }
        */
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CodeBase;
use Phan\Issue;
use Phan\Language\Element\Clazz;
use Phan\Language\FQSEN\FullyQualifiedClassName;

class ClassInheritanceAnalyzer
{
    /**
     * Check to see if the given Clazz is a duplicate
     *
     * @return void
     */
    public static function analyzeClassInheritance(
        CodeBase $code_base,
        Clazz $clazz
    ) {

        // Don't worry about internal classes
        if ($clazz->isPHPInternal()) {
            return;
        }

        if ($clazz->hasParentType()) {
            $class_exists = self::fqsenExistsForClass(
                $clazz->getParentClassFQSEN(),
                $code_base,
                $clazz,
                Issue::UndeclaredExtendedClass
            );

            if ($class_exists) {
                self::testClassAccess(
                    $clazz,
                    $clazz->getParentClass($code_base),
                    $code_base
                );
            }
        }

        foreach ($clazz->getInterfaceFQSENList() as $fqsen) {
            $class_exists = self::fqsenExistsForClass(
                $fqsen,
                $code_base,
                $clazz,
                Issue::UndeclaredInterface
            );

            if ($class_exists) {
                self::testClassAccess(
                    $clazz,
                    $code_base->getClassByFQSEN($fqsen),
                    $code_base
                );
            }
        }

        foreach ($clazz->getTraitFQSENList() as $fqsen) {
            $class_exists = self::fqsenExistsForClass(
                $fqsen,
                $code_base,
                $clazz,
                Issue::UndeclaredTrait
            );
            if ($class_exists) {
                self::testClassAccess(
                    $clazz,
                    $code_base->getClassByFQSEN($fqsen),
                    $code_base
                );
            }
        }
    }

    /**
     * @return bool
     * True if the FQSEN exists. If not, a log line is emitted
     */
    private static function fqsenExistsForClass(
        FullyQualifiedClassName $fqsen,
        CodeBase $code_base,
        Clazz $clazz,
        string $issue_type
    ) : bool {

        if (!$code_base->hasClassWithFQSEN($fqsen)) {
            Issue::maybeEmit(
                $code_base,
                $clazz->getContext(),
                $issue_type,
                $clazz->getFileRef()->getLineNumberStart(),
                (string)$fqsen
            );

            return false;
        }

        return true;
    }

    /**
     * @param Clazz $source_class
     * The class accessing the $target_class
     *
     * @param Clazz $target_class
     * The class being accessed from the $source_class
     *
     * @param CodeBase $code_base
     * The code base in which both classes exist
     */
    private static function testClassAccess(
        Clazz $source_class,
        Clazz $target_class,
        CodeBase $code_base
    ) {
        if ($target_class->isNSInternal($code_base)
            && !$target_class->isNSInternalAccessFromContext(
                $code_base,
                $source_class->getContext()
            )
        ) {
            Issue::maybeEmit(
                $code_base,
                $source_class->getContext(),
                Issue::AccessClassInternal,
                $source_class->getFileRef()->getLineNumberStart(),
                (string)$target_class,
                $target_class->getFileRef()->getFile(),
                (string)$target_class->getFileRef()->getLineNumberStart()
            );
        }

        /*
        if ($target_class->isDeprecated()) {
            Issue::maybeEmit(
                $code_base,
                $source_class->getContext(),
                Issue::DeprecatedClass,
                $source_class->getFileRef()->getLineNumberStart(),
                (string)$target_class->getFQSEN(),
                $target_class->getFileRef()->getFile(),
                (string)$target_class->getFileRef()->getLineNumberStart()
            );
        }
        */
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CLI;
use Phan\CodeBase;
use Phan\CodeBase\ClassMap;
use Phan\Config;
use Phan\Issue;
use Phan\Language\Element\AddressableElement;
use Phan\Language\Element\ClassConstant;
use Phan\Language\Element\ClassElement;
use Phan\Language\Element\Func;
use Phan\Language\Element\Method;
use Phan\Language\Element\Property;
use Phan\Library\Map;
use Phan\Language\FQSEN\FullyQualifiedClassConstantName;
use Phan\Language\FQSEN\FullyQualifiedClassElement;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\FQSEN\FullyQualifiedGlobalConstantName;
use Phan\Language\FQSEN\FullyQualifiedGlobalStructuralElement;
use Phan\Language\FQSEN\FullyQualifiedPropertyName;

/**
 * This emits PhanUnreferenced* issues for classlikes, constants, properties, and functions/methods.
 *
 * TODO: Make references to methods of interfaces also count as references to traits which are used by classes to implement those methods.
 * (Maybe track these in addMethod when checking for inheritance?)
 */
class ReferenceCountsAnalyzer
{
    /**
     * Take a look at all globally accessible elements and see if
     * we can find any dead code that is never referenced
     *
     * @return void
     */
    public static function analyzeReferenceCounts(CodeBase $code_base)
    {
        // Check to see if dead code detection is enabled. Keep
        // in mind that the results here are just a guess and
        // we can't tell with certainty that anything is
        // definitely unreferenced.
        if (!Config::getValue('dead_code_detection')) {
            return;
        }

        // Get the count of all known elements
        $total_count = $code_base->totalElementCount();
        $i = 0;

        // Functions
        self::analyzeGlobalElementListReferenceCounts(
            $code_base,
            $code_base->getFunctionMap(),
            Issue::UnreferencedFunction,
            $total_count,
            $i
        );

        // Constants
        self::analyzeGlobalElementListReferenceCounts(
            $code_base,
            $code_base->getGlobalConstantMap(),
            Issue::UnreferencedConstant,
            $total_count,
            $i
        );

        // Classes
        self::analyzeGlobalElementListReferenceCounts(
            $code_base,
            $code_base->getUserDefinedClassMap(),
            Issue::UnreferencedClass,
            $total_count,
            $i
        );

        // Class Maps
        $elements_to_analyze = [];
        foreach ($code_base->getClassMapMap() as $class_map) {
            foreach (self::getElementsFromClassMapForDeferredAnalysis(
                $code_base,
                $class_map,
                $total_count,
                $i
            ) as $element) {
                $elements_to_analyze[] = $element;
            }
        }

        static $issue_types = [
            ClassConstant::class => Issue::UnreferencedPublicClassConstant,  // This is overridden
            Method::class        => Issue::UnreferencedPublicMethod,  // This is overridden
            Property::class      => Issue::UnreferencedPublicProperty,  // This is overridden
        ];

        foreach ($elements_to_analyze as $element) {
            $issue_type = $issue_types[\get_class($element)];
            self::analyzeElementReferenceCounts($code_base, $element, $issue_type);
        }
    }

    /**
     * @param CodeBase $code_base
     * @param ClassMap $class_map
     * @param int $total_count
     * @param int $i
     *
     * @return \Generator|ClassElement[]
     */
    private static function getElementsFromClassMapForDeferredAnalysis(
        CodeBase $code_base,
        ClassMap $class_map,
        int $total_count,
        int &$i
    ) {
        // Constants
        yield from self::getElementsFromElementListForDeferredAnalysis(
            $code_base,
            $class_map->getClassConstantMap(),
            $total_count,
            $i
        );

        // Properties
        yield from self::getElementsFromElementListForDeferredAnalysis(
            $code_base,
            $class_map->getPropertyMap(),
            $total_count,
            $i
        );

        // Methods
        yield from self::getElementsFromElementListForDeferredAnalysis(
            $code_base,
            $class_map->getMethodMap(),
            $total_count,
            $i
        );
    }

    /**
     * @param CodeBase $code_base
     * @param Map|AddressableElement[] $element_list
     * @param string $issue_type
     * @param int $total_count
     * @param int $i
     *
     * @return void
     */
    private static function analyzeGlobalElementListReferenceCounts(
        CodeBase $code_base,
        $element_list,
        string $issue_type,
        int $total_count,
        int &$i
    ) {
        foreach ($element_list as $element) {
            CLI::progress('dead code', (++$i)/$total_count);
            // Don't worry about internal elements
            if ($element->isPHPInternal()) {
                continue;
            }
            self::analyzeElementReferenceCounts($code_base, $element, $issue_type);
        }
    }

    /**
     * @param CodeBase $code_base
     * @param Map|ClassElement[] $element_list
     * @param int $total_count
     * @param int $i
     *
     * @return \Generator|ClassElement[]
     */
    private static function getElementsFromElementListForDeferredAnalysis(
        CodeBase $code_base,
        $element_list,
        int $total_count,
        int &$i
    ) {
        foreach ($element_list as $element) {
            CLI::progress('dead code', (++$i)/$total_count);
            // Don't worry about internal elements
            if ($element->isPHPInternal()) {
                continue;
            }
            // Currently, deferred analysis is only needed for class elements, which can be inherited
            // (And we may track the references to the inherited version of the original)
            assert($element instanceof ClassElement);
            if ($element instanceof ClassConstant) {
                // should not warn about self::class
                if (strcasecmp($element->getName(), 'class') === 0) {
                    continue;
                }
            }
            $fqsen = $element->getFQSEN();
            if ($element instanceof Method || $element instanceof Property) {
                $defining_fqsen = $element->getRealDefiningFQSEN();
            } else {
                $defining_fqsen = $element->getDefiningFQSEN();
            }

            // copy references to methods, properties, and constants into the defining trait or class.
            if ($fqsen !== $defining_fqsen) {
                if ($element->getReferenceCount($code_base) > 0) {
                    $defining_element = null;
                    if ($defining_fqsen instanceof FullyQualifiedMethodName) {
                        if ($code_base->hasMethodWithFQSEN($defining_fqsen)) {
                            $defining_element = $code_base->getMethodByFQSEN($defining_fqsen);
                        }
                    } elseif ($defining_fqsen instanceof FullyQualifiedPropertyName) {
                        if ($code_base->hasPropertyWithFQSEN($defining_fqsen)) {
                            $defining_element = $code_base->getPropertyByFQSEN($defining_fqsen);
                        }
                    } elseif ($defining_fqsen instanceof FullyQualifiedClassConstantName) {
                        if ($code_base->hasClassConstantWithFQSEN($defining_fqsen)) {
                            $defining_element = $code_base->getClassConstantByFQSEN($defining_fqsen);
                        }
                    }
                    if ($defining_element !== null) {
                        $defining_element->copyReferencesFrom($element);
                    }
                }
                continue;
            }

            // Don't analyze elements defined in a parent class.
            // We copy references to methods, properties, and constants into the defining trait or class before this.
            if ($element->getIsOverride()) {
                continue;
            }

            $defining_class =
                $element->getClass($code_base);

            if ($element instanceof Method) {
                // Ignore magic methods
                if ($element->getIsMagic()) {
                    continue;
                }
                // Don't analyze abstract methods, as they're uncallable.
                // (Every method on an interface is abstract)
                if ($element->isAbstract() || $defining_class->isInterface()) {
                    continue;
                }
            } elseif ($element instanceof Property) {
                // Skip properties on classes that have a magic
                // __get or __set method given that we can't track
                // their access
                $defining_class = $element->getClass($code_base);

                if ($defining_class->hasGetOrSetMethod($code_base)) {
                    continue;
                }
            }
            yield $element;
        }
    }

    /**
     * Check to see if the given Clazz is a duplicate
     *
     * @return void
     */
    private static function analyzeElementReferenceCounts(
        CodeBase $code_base,
        AddressableElement $element,
        string $issue_type
    ) {
        /*
        print "digraph G {\n";
        foreach ($element->getReferenceList() as $file_ref) {
            print "\t\"{$file_ref->getFile()}\" -> \"{$element->getFileRef()->getFile()}\";\n";
        }
        print "}\n";
        */

        // Make issue types granular so that these can be fixed in smaller steps.
        // E.g. composer libraries may have unreferenced but used public methods, properties, and class constants,
        // and those would have higher false positives than private/protected elements.
        //
        // Make $issue_type specific **first**, so that issue suppressions are checked against the proper issue type
        if ($element instanceof ClassElement) {
            if ($element instanceof Method) {
                if ($element->isPrivate()) {
                    $issue_type = Issue::UnreferencedPrivateMethod;
                } elseif ($element->isProtected()) {
                    $issue_type = Issue::UnreferencedProtectedMethod;
                } else {
                    $issue_type = Issue::UnreferencedPublicMethod;
                }
            } elseif ($element instanceof Property) {
                if ($element->isPrivate()) {
                    $issue_type = Issue::UnreferencedPrivateProperty;
                } elseif ($element->isProtected()) {
                    $issue_type = Issue::UnreferencedProtectedProperty;
                } else {
                    $issue_type = Issue::UnreferencedPublicProperty;
                }
            } elseif ($element instanceof ClassConstant) {
                if ($element->isPrivate()) {
                    $issue_type = Issue::UnreferencedPrivateClassConstant;
                } elseif ($element->isProtected()) {
                    $issue_type = Issue::UnreferencedProtectedClassConstant;
                } else {
                    $issue_type = Issue::UnreferencedPublicClassConstant;
                }
            }
        } elseif ($element instanceof Func) {
            if (\strcasecmp($element->getName(), "__autoload") === 0) {
                return;
            }
            if ($element->getFQSEN()->isClosure()) {
                $issue_type = Issue::UnreferencedClosure;
            }
        }


        // If we're suppressing this element type being unreferenced, then exit early.
        if ($element->hasSuppressIssue($issue_type)) {
            $element->incrementSuppressIssueCount($issue_type);
            return;
        }

        if ($element->getReferenceCount($code_base) >= 1) {
            if ($element instanceof Property && !($element->hasReadReference())) {
                self::maybeWarnWriteOnlyProperty($code_base, $element);
            }
            return;
        }
        // getReferenceCount === 0

        $element_alt = self::findAlternateReferencedElementDeclaration($code_base, $element);
        if (!\is_null($element_alt)) {
            if ($element_alt->getReferenceCount($code_base) >= 1) {
                if ($element_alt instanceof Property && !($element_alt->hasReadReference())) {
                    self::maybeWarnWriteOnlyProperty($code_base, $element_alt);
                }
                // If there is a reference to the "canonical" declaration (the one which was parsed first),
                // then also treat it as a reference to the duplicate.
                return;
            }
            if ($element_alt->isPHPInternal()) {
                // For efficiency, Phan doesn't track references to internal classes.
                // Phan already emitted a warning about duplicating an internal class.
                return;
            }
        }
        // If there are duplicate declarations, display issues for unreferenced elements on each declaration.
        Issue::maybeEmit(
            $code_base,
            $element->getContext(),
            $issue_type,
            $element->getFileRef()->getLineNumberStart(),
            (string)$element->getFQSEN()
        );
    }

    private static function maybeWarnWriteOnlyProperty(CodeBase $code_base, Property $property)
    {
        if ($property->isPrivate()) {
            $issue_type = Issue::WriteOnlyPrivateProperty;
        } elseif ($property->isProtected()) {
            $issue_type = Issue::WriteOnlyProtectedProperty;
        } else {
            $issue_type = Issue::WriteOnlyPublicProperty;
        }
        if ($property->hasSuppressIssue($issue_type)) {
            $property->incrementSuppressIssueCount($issue_type);
            return;
        }
        $property_alt = self::findAlternateReferencedElementDeclaration($code_base, $property);
        if ($property_alt instanceof Property) {
            if ($property_alt->hasReadReference()) {
                return;
            }
        }
        Issue::maybeEmit(
            $code_base,
            $property->getContext(),
            $issue_type,
            $property->getFileRef()->getLineNumberStart(),
            (string)$property->getFQSEN()
        );
    }

    /**
     * @return ?AddressableElement
     */
    public static function findAlternateReferencedElementDeclaration(
        CodeBase $code_base,
        AddressableElement $element
    ) {
        $old_fqsen = $element->getFQSEN();
        if ($old_fqsen instanceof FullyQualifiedGlobalStructuralElement) {
            $fqsen = $old_fqsen->getCanonicalFQSEN();
            if ($fqsen === $old_fqsen) {
                return null;  // $old_fqsen was not an alternaive
            }
            if ($fqsen instanceof FullyQualifiedFunctionName) {
                if ($code_base->hasFunctionWithFQSEN($fqsen)) {
                    return $code_base->getFunctionByFQSEN($fqsen);
                }
                return null;
            } elseif ($fqsen instanceof FullyQualifiedClassName) {
                if ($code_base->hasClassWithFQSEN($fqsen)) {
                    return $code_base->getClassByFQSEN($fqsen);
                }
                return null;
            } elseif ($fqsen instanceof FullyQualifiedGlobalConstantName) {
                if ($code_base->hasGlobalConstantWithFQSEN($fqsen)) {
                    return $code_base->getGlobalConstantByFQSEN($fqsen);
                }
                return null;
            }
        } elseif ($old_fqsen instanceof FullyQualifiedClassElement) {
            // If this needed to be more thorough,
            // the code adding references could treat uses from within the classes differently from outside.
            $fqsen = $old_fqsen->getCanonicalFQSEN();
            if ($fqsen === $old_fqsen) {
                return null;  // $old_fqsen was not an alternative
            }

            if ($fqsen instanceof FullyQualifiedMethodName) {
                if ($code_base->hasMethodWithFQSEN($fqsen)) {
                    return $code_base->getMethodByFQSEN($fqsen);
                }
                return null;
            } elseif ($fqsen instanceof FullyQualifiedPropertyName) {
                if ($code_base->hasPropertyWithFQSEN($fqsen)) {
                    return $code_base->getPropertyByFQSEN($fqsen);
                }
                return null;
            } elseif ($fqsen instanceof FullyQualifiedClassConstantName) {
                if ($code_base->hasClassConstantWithFQSEN($fqsen)) {
                    return $code_base->getClassConstantByFQSEN($fqsen);
                }
                return null;
            }
        }
        return null;
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\PhanAnnotationAdder;
use Phan\BlockAnalysisVisitor;
use Phan\CodeBase;
use Phan\Config;
use Phan\Language\Context;
use ast\Node;

/**
 * Objects implementing this trait store a handle to
 * the AST node that defines them and allows us to
 * reanalyze them later on
 */
trait Analyzable
{

    /**
     * @var Node
     * The AST Node defining this object. We keep a
     * reference to this so that we can come to it
     * and
     */
    private $node = null;

    /**
     * @var bool
     */
    private $did_annotate_node = false;

    /**
     * @var int
     * The depth of recursion on this analyzable
     * object
     */
    private static $recursion_depth = 0;

    /**
     * @param Node $node
     * The AST Node defining this object. We keep a
     * reference to this so that we can come to it
     * and
     */
    public function setNode(Node $node)
    {
        // Don't waste the memory if we're in quick mode
        if (Config::get_quick_mode()) {
            return;
        }

        $this->node = $node;
    }

    /**
     * @return bool
     * True if we have a node defined on this object
     */
    public function hasNode() : bool
    {
        return !empty($this->node);
    }

    /**
     * @return Node
     * The AST node associated with this object
     */
    public function getNode()
    {
        return $this->node;
    }

    /**
     * @return Context
     * Analyze the node associated with this object
     * in the given context
     */
    public function analyze(Context $context, CodeBase $code_base) : Context
    {
        // Don't do anything if we care about being
        // fast
        if (Config::get_quick_mode()) {
            return $context;
        }

        $definition_node = $this->node;
        if (!$definition_node) {
            return $context;
        }
        if (!$this->did_annotate_node) {
            $this->did_annotate_node = true;
            PhanAnnotationAdder::applyToScope($definition_node);
        }

        // Closures depend on the context surrounding them such
        // as for getting `use(...)` variables. Since we don't
        // have them, we can't re-analyze them until we change
        // that.
        //
        // TODO: Store the parent context on Analyzable objects
        if ($definition_node->kind === \ast\AST_CLOSURE) {
            // TODO: Pick up 'uses' when this is a closure invoked inline (e.g. array_map(function($x) use($localVar) {...}, args
            // TODO: Investigate replacing the types of these with 'mixed' for quick mode re-analysis, or checking if the type will never vary.
            if (!empty($definition_node->children['uses'])) {
                return $context;
            }
        }
        // Don't go deeper than one level in
        // TODO: Due to optimizations in checking for duplicate parameter lists, it should now be possible to increase this depth limit.
        if (self::$recursion_depth >= 2) {
            return $context;
        }

        self::$recursion_depth++;

        try {
            // Analyze the node in a cloned context so that we
            // don't overwrite anything
            return (new BlockAnalysisVisitor($code_base, clone($context)))->__invoke(
                $definition_node
            );
        } finally {
            self::$recursion_depth--;
        }
    }

    /**
     * Gets the recursion depth. Starts at 0, increases the deeper the recursion goes
     */
    public function getRecursionDepth() : int
    {
        return self::$recursion_depth;
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\Visitor\Element;
use Phan\AST\Visitor\FlagVisitorImplementation;
use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\UnionType;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\FloatType;
use Phan\Language\Type\IntType;
use Phan\Language\Type\StringType;
use Phan\Issue;
use ast\Node;

// TODO: Improve analysis of bitwise operations, warn if non-int is provided and consistently return int if it's guaranteed
class AssignOperatorFlagVisitor extends FlagVisitorImplementation
{

    /**
     * @var CodeBase
     */
    private $code_base;

    /**
     * @var Context
     */
    private $context;

    /**
     * Create a new BinaryOperatorFlagVisitor
     */
    public function __construct(
        CodeBase $code_base,
        Context $context
    ) {
        $this->code_base = $code_base;
        $this->context = $context;
    }

    /**
     * @param Node $node
     * A node to visit
     */
    public function __invoke(Node $node)
    {
        // AST_ASSIGN_OP uses \ast\flags\BINARY_* in ast versions >= 20.
        // NOTE: Some operations currently don't exist in any php version, such as `$x ||= 2;`, `$x xor= 2;`
        return Element::acceptBinaryFlagVisitor($node, $this);
    }

    /**
     * Default visitor for node kinds that do not have
     * an overriding method
     *
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visit(Node $node) : UnionType
    {
        // TODO: For some types (e.g. xor, bitwise or), set the type of the variable?
        // Or should that be done in PreOrderAnalysisVisitor?
        $left = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['var']
        );

        $right = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr']
        );

        if ($left->isType(ArrayType::instance(false))
            || $right->isType(ArrayType::instance(false))
        ) {
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::TypeArrayOperator,
                $node->lineno ?? 0,
                $left,
                $right
            );

            return UnionType::empty();
        } elseif ($left->hasType(IntType::instance(false))
            && $right->hasType(IntType::instance(false))
        ) {
            return IntType::instance(false)->asUnionType();
        } elseif ($left->hasType(FloatType::instance(false))
            && $right->hasType(FloatType::instance(false))
        ) {
            return FloatType::instance(false)->asUnionType();
        }

        static $int_or_float;
        return $int_or_float ?? ($int_or_float = new UnionType([
            IntType::instance(false),
            FloatType::instance(false)
        ]));
    }

    public function visitBinaryBitwiseAnd(Node $node)
    {
        $left = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['var']
        );

        $right = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr']
        );
        if ($left->hasType(IntType::instance(false))
            && $right->hasType(IntType::instance(false))
        ) {
            return IntType::instance(false)->asUnionType();
        } elseif ($left->hasType(StringType::instance(false)) &&
            $right->hasType(StringType::instance(false))) {
            // $x = 'a'; $x &= 'c';
            return StringType::instance(false)->asUnionType();
        }
        return IntType::instance(false)->asUnionType();
    }

    public function visitBinaryBitwiseOr(Node $node)
    {
        $left = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['var']
        );

        $right = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr']
        );
        if ($left->hasType(IntType::instance(false))
            && $right->hasType(IntType::instance(false))
        ) {
            return IntType::instance(false)->asUnionType();
        } elseif ($left->hasType(StringType::instance(false)) &&
            $right->hasType(StringType::instance(false))) {
            // $x = 'a'; $x |= 'c';
            return StringType::instance(false)->asUnionType();
        }
        return IntType::instance(false)->asUnionType();
    }

    // Code can bitwise xor strings byte by byte in PHP
    public function visitBinaryBitwiseXor(Node $node)
    {
        $left = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['var']
        );

        $right = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr']
        );

        // TODO: check for other invalid types
        if ($left->isType(ArrayType::instance(false))
            || $right->isType(ArrayType::instance(false))
        ) {
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::TypeArrayOperator,
                $node->lineno ?? 0,
                $left,
                $right
            );

            return UnionType::empty();
        } elseif ($left->hasType(IntType::instance(false))
            && $right->hasType(IntType::instance(false))
        ) {
            return IntType::instance(false)->asUnionType();
        } elseif ($left->hasType(StringType::instance(false))
            && $right->hasType(StringType::instance(false))
        ) {
            return StringType::instance(false)->asUnionType();
        }

        return IntType::instance(false)->asUnionType();
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     *
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function visitBinaryConcat(Node $node) : UnionType
    {
        return StringType::instance(false)->asUnionType();
    }

    /**
     * @param Node $node
     * A node to check types on
     *
     * @return UnionType
     * The resulting type(s) of the binary operation
     */
    public function visitBinaryAdd(Node $node) : UnionType
    {
        $left = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['var']
        );

        $right = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr']
        );

        // fast-track common cases
        if ($left->isType(IntType::instance(false))
            && $right->isType(IntType::instance(false))
        ) {
            return IntType::instance(false)->asUnionType();
        }

        // If both left and right are arrays, then this is array
        // concatenation.
        if ($left->isGenericArray() && $right->isGenericArray()) {
            if ($left->isEqualTo($right)) {
                return $left;
            }

            return ArrayType::instance(false)->asUnionType();
        }

        if (($left->isType(IntType::instance(false))
            || $left->isType(FloatType::instance(false)))
            && ($right->isType(IntType::instance(false))
            || $right->isType(FloatType::instance(false)))
        ) {
            return FloatType::instance(false)->asUnionType();
        }

        $left_is_array = (
            !$left->genericArrayElementTypes()->isEmpty()
            && $left->nonArrayTypes()->isEmpty()
        ) || $left->isType(ArrayType::instance(false));

        $right_is_array = (
            !$right->genericArrayElementTypes()->isEmpty()
            && $right->nonArrayTypes()->isEmpty()
        ) || $right->isType(ArrayType::instance(false));

        if ($left_is_array
            && !$right->canCastToUnionType(
                ArrayType::instance(false)->asUnionType()
            )
        ) {
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::TypeInvalidRightOperand,
                $node->lineno ?? 0
            );
            return UnionType::empty();
        } elseif ($right_is_array
            && !$left->canCastToUnionType(ArrayType::instance(false)->asUnionType())
        ) {
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::TypeInvalidLeftOperand,
                $node->lineno ?? 0
            );
            return UnionType::empty();
        } elseif ($left_is_array || $right_is_array) {
            // If it is a '+' and we know one side is an array
            // and the other is unknown, assume array
            return ArrayType::instance(false)->asUnionType();
        }

        static $int_or_float;
        return $int_or_float ?? ($int_or_float = new UnionType([
            IntType::instance(false),
            FloatType::instance(false)
        ]));
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use ast\Node;
use Phan\AST\Visitor\KindVisitorImplementation;

/**
 * This simplifies a PHP AST into a form which is easier to analyze.
 * Precondition: The original \ast\Node objects are not modified.
 *
 * This caches the status for AST nodes, so references to this object
 * should be removed once the source transformation of a file/function is complete.
 *
 * This uses the \ast\Node's themselves in order to cache the status.
 * It reuses $node->flags whenever possible in order to avoid keeping around \ast\Node
 * instances for longer than those would be used.
 * This assumes that Nodes aren't manipulated, or manipulations to Nodes will preserve the semantics (including computed exit status) or clear $node->flags.
 *
 * - Creating an additional object property would increase overall memory usage, which is why properties are used.
 * - AST_IF, AST_IF_ELEM, AST_DO_WHILE, AST_FOR, AST_WHILE, AST_STMT_LIST,
 *   etc (e.g. switch and switch case, try/finally).
 *   are node types which are known to not have flags in AST version 40.
 * - In the future, add a new property such as $node->children['__exitStatus'] if used for a node type with flags, or use the higher bits.
 *
 * TODO: Change to AnalysisVisitor if this ever emits issues.
 * TODO: Analyze switch (if there is a default) in another PR (And handle fallthrough)
 *
 * @phan-file-suppress PhanPluginUnusedPublicFinalMethodArgument
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
final class BlockExitStatusChecker extends KindVisitorImplementation
{
    // These should be at most 1 << 31, in order to work in 32-bit php.
    // NOTE: Any exit status must be a combination of at least one of these bits
    // E.g. if STATUS_PROCEED is mixed with STATUS_RETURN, it would mean it is possible both to go to completion or return.
    const STATUS_PROCEED        = (1 << 20);       // At least one branch continues to completion.
    const STATUS_GOTO           = (1 << 21);       // At least one branch leads to a goto statement
    const STATUS_CONTINUE       = (1 << 22);       // At least one branch leads to a continue statement
    const STATUS_BREAK          = (1 << 23);       // At least one branch leads to a break statement
    const STATUS_THROW          = (1 << 24);       // At least one branch leads to a throw statement
    const STATUS_RETURN         = (1 << 25);       // At least one branch leads to a return/exit() statement (or an infinite loop)

    const STATUS_THROW_OR_RETURN_BITMASK =
        self::STATUS_THROW |
        self::STATUS_RETURN;

    // Any status which doesn't lead to proceeding.
    const STATUS_NOT_PROCEED_BITMASK =
        self::STATUS_GOTO |
        self::STATUS_CONTINUE |
        self::STATUS_BREAK |
        self::STATUS_THROW |
        self::STATUS_RETURN;

    const STATUS_BITMASK =
        self::STATUS_PROCEED |
        self::STATUS_NOT_PROCEED_BITMASK;

    const STATUS_MAYBE_PROCEED =
        self::STATUS_PROCEED |
        self::STATUS_GOTO;

    public function __construct()
    {
    }

    public function check(Node $node = null) : int
    {
        if (!$node) {
            return self::STATUS_PROCEED;
        }
        $result = $this->__invoke($node);
        \assert(\is_int($result), 'Expected int');
        \assert($result > 0, 'Expected positive int');
        return $result;
    }

    /**
     * If we don't know how to analyze a node type (or left it out), assume it always proceeds
     */
    public function visit(Node $node)
    {
        return self::STATUS_PROCEED;
    }

    private static function isTruthyLiteral($cond) : bool
    {
        if ($cond instanceof Node) {
            // TODO: Could look up values for remaining constants and inline expressions, but doing that has low value.
            if ($cond->kind === \ast\AST_CONST) {
                $cond_name = $cond->children['name'];
                if ($cond_name->kind === \ast\AST_NAME) {
                    $cond_name_string = $cond_name->children['name'];
                    return \is_string($cond_name_string) && strcasecmp($cond_name_string, 'true') === 0;
                }
            }
            return false;
        }
        // Cast string, int, etc. literal to a bool
        return (bool)$cond;
    }

    // A break statement unconditionally breaks out of a loop/switch
    public function visitBreak(Node $node)
    {
        return self::STATUS_BREAK;
    }

    // A continue statement unconditionally continues out of a loop.
    public function visitContinue(Node $node)
    {
        return self::STATUS_CONTINUE;
    }

    // A throw statement unconditionally throws
    public function visitThrow(Node $node)
    {
        return self::STATUS_THROW;
    }

    public function visitTry(Node $node)
    {
        $status = $node->flags & self::STATUS_BITMASK;
        if ($status) {
            return $status;
        }
        $status = $this->computeStatusOfTry($node);
        $node->flags = $status;
        return $status;
    }

    private function computeStatusOfTry(Node $node) : int
    {
        $main_status = $this->check($node->children['try']);
        // Finding good heuristics is difficult.
        // e.g. "return someFunctionThatMayThrow()" in try{} block would be inferred as STATUS_RETURN, but may actually be STATUS_THROW

        $finally_node = $node->children['finally'];
        if ($finally_node) {
            $finally_status = $this->check($finally_node);
            // TODO: Could emit an issue as a side effect
            // Having any sort of status in a finally statement is
            // likely to have unintuitive behavior.
            if ($finally_status & (~self::STATUS_THROW_OR_RETURN_BITMASK) === 0) {
                return $finally_status;
            }
        } else {
            $finally_status = self::STATUS_PROCEED;
        }
        $catches_node = $node->children['catches'];
        if (\count($catches_node->children) == 0) {
            return self::mergeFinallyStatus($main_status, $finally_status);
        }
        // TODO: Could enhance slightly by checking for catch nodes with the exact same types (or subclasses) as names of exception thrown.
        $combined_status = self::mergeFinallyStatus($main_status, $finally_status) | $this->visitCatchList($catches_node);
        if (($finally_status & self::STATUS_PROCEED) === 0) {
            $combined_status &= ~self::STATUS_PROCEED;
        }
        // No idea.
        return $combined_status;
    }

    public function visitCatchList(Node $node)
    {
        $status = $node->flags & self::STATUS_BITMASK;
        if ($status) {
            return $status;
        }
        $status = $this->computeStatusOfCatchList($node);
        $node->flags = $status;
        return $status;
    }

    private function computeStatusOfCatchList(Node $node) : int
    {
        $catch_list = $node->children;
        if (count($catch_list) === 0) {
            return self::STATUS_PROCEED;  // status probably won't matter
        }
        // TODO: Could enhance slightly by checking for catch nodes with the exact same types (or subclasses) as names of exception thrown.
        $combined_status = 0;
        // Try to cover all possible cases, such as try { return throwsException(); } catch(Exception $e) { break; }
        foreach ($node->children as $catch_node) {
            $catch_node_status = $this->visitStmtList($catch_node->children['stmts']);
            $combined_status = $combined_status | $catch_node_status;
        }
        // No idea.
        return $combined_status;
    }

    private static function mergeFinallyStatus(int $try_status, int $finally_status) : int
    {
        // If at least one of try or finally are guaranteed not to proceed to completion,
        // then combine those possibilities.
        if (($try_status & $finally_status & self::STATUS_PROCEED) === 0) {
            return ($try_status | $finally_status) & ~self::STATUS_PROCEED;
        }
        return $try_status | $finally_status;
    }

    public function visitSwitch(Node $node)
    {
        $status = $node->flags & self::STATUS_BITMASK;
        if ($status) {
            return $status;
        }
        $status = $this->computeStatusOfSwitch($node);
        $node->flags = $status;
        return $status;
    }

    private function computeStatusOfSwitch(Node $node) : int
    {
        $switch_stmt_case_nodes = $node->children['stmts']->children;
        if (\count($switch_stmt_case_nodes) === 0) {
            return self::STATUS_PROCEED;
        }
        $has_default = false;
        $combined_statuses = 0;
        foreach ($switch_stmt_case_nodes as $index => $case_node) {
            if ($case_node->children['cond'] === null) {
                $has_default = true;
            }
            $case_status = self::getStatusOfSwitchCase($case_node, $index, $switch_stmt_case_nodes);
            if (($case_status & self::STATUS_CONTINUE_OR_BREAK) !== 0) {
                // Ignore statuses such as break/continue. They take effect inside, but are a proceed status outside
                $case_status = ($case_status & ~self::STATUS_CONTINUE_OR_BREAK) | self::STATUS_PROCEED;
            }
            $combined_statuses |= $case_status;
        }
        if (!$has_default) {
            $combined_statuses |= self::STATUS_PROCEED;
        }
        return $combined_statuses;
    }

    /**
     * @param array<int,Node> $siblings
     */
    private function getStatusOfSwitchCase(Node $case_node, int $index, array $siblings) : int
    {
        $status = $case_node->flags & self::STATUS_BITMASK;
        if ($status) {
            return $status;
        }
        $status = $this->computeStatusOfSwitchCase($case_node, $index, $siblings);
        $case_node->flags = $status;
        return $status;
    }

    private function computeStatusOfSwitchCase(Node $case_node, int $index, array $siblings) : int
    {
        $status = $this->visitStmtList($case_node->children['stmts']);
        if (($status & self::STATUS_PROCEED) === 0) {
            // Check if the current switch case will not fall through.
            return $status;
        }
        $next_sibling = $siblings[$index + 1] ?? null;
        if (!$next_sibling) {
            return $status;
        }
        $next_status = self::getStatusOfSwitchCase($next_sibling, $index + 1, $siblings);
        // Combine the possibilities.
        // e.g. `case 1: if (cond()) { return; } case 2: throw;`, case 1 will either break or throw,
        // but won't proceed normally to the outside of the switch statement.
        return ($status & ~self::STATUS_PROCEED) | $next_status;
    }

    const UNEXITABLE_LOOP_INNER_STATUS = self::STATUS_PROCEED | self::STATUS_CONTINUE;

    const STATUS_CONTINUE_OR_BREAK = self::STATUS_CONTINUE | self::STATUS_BREAK;

    /**
     * @return int
     */
    public function visitForeach(Node $node)
    {
        // We assume foreach loops are over a finite sequence, and that it's possible for that sequence to have at least one element.
        $inner_status = $this->check($node->children['stmts']);


        // 1. break/continue apply to the inside of a loop, not outside. Not going to analyze "break 2;", may emit an info level issue in the future.
        // 2. We assume that it's possible that any given loop can have 0 iterations.
        //    A TODO exists above to check for special cases.
        return ($inner_status & ~self::STATUS_CONTINUE_OR_BREAK) | self::STATUS_PROCEED;
    }

    /**
     * @return int
     */
    public function visitWhile(Node $node)
    {
        $inner_status = $this->check($node->children['stmts']);
        // TODO: Check for unconditionally false conditions.
        if (self::isTruthyLiteral($node->children['cond'])) {
            // Use a special case to analyze "while (1) {exprs}" or "for (; true; ) {exprs}"
            // TODO: identify infinite loops, mark those as STATUS_NO_PROCEED or STATUS_RETURN.
            return $this->computeDerivedStatusOfInfiniteLoop($inner_status);
        }
        // This is (to our awareness) **not** an infinite loop


        // 1. break/continue apply to the inside of a loop, not outside. Not going to analyze "break 2;", may emit an info level issue in the future.
        // 2. We assume that it's possible that any given loop can have 0 iterations.
        //    A TODO exists above to check for special cases.
        return ($inner_status & ~self::STATUS_CONTINUE_OR_BREAK) | self::STATUS_PROCEED;
    }

    public function visitFor(Node $node)
    {
        $inner_status = $this->check($node->children['stmts']);
        // for loops have an expression list as a condition.
        $cond_nodes = $node->children['cond']->children ?? [];  // NOTE: $node->children['cond'] is null for the expression `for (;;)`
        // TODO: Check for unconditionally false conditions.
        if (count($cond_nodes) === 0 || self::isTruthyLiteral(\end($cond_nodes))) {
            // Use a special case to analyze "while (1) {exprs}" or "for (; true; ) {exprs}"
            // TODO: identify infinite loops, mark those as STATUS_NO_PROCEED or STATUS_RETURN.
            return $this->computeDerivedStatusOfInfiniteLoop($inner_status);
        }
        // This is (to our awareness) **not** an infinite loop


        // 1. break/continue apply to the inside of a loop, not outside. Not going to analyze "break 2;", may emit an info level issue in the future.
        // 2. We assume that it's possible that any given loop can have 0 iterations.
        //    A TODO exists above to check for special cases.
        return ($inner_status & ~self::STATUS_CONTINUE_OR_BREAK) | self::STATUS_PROCEED;
        // TODO: Improve this by checking for loops which almost definitely have at least one iteration,
        // such as "foreach ([$val] as $v)" or "for ($i = 0; $i < 10; $i++)"

        // if (($inner_status & ~self::STATUS_THROW_OR_RETURN_BITMASK) === 0) {
        //     // The inside of the loop will unconditionally throw or return.
        //     return $inner_status
        // }
    }

    // Logic to determine status of "while (1) {exprs}" or "for (; true; ) {exprs}"
    // TODO: identify infinite loops, mark those as STATUS_NO_PROCEED or STATUS_RETURN.
    private function computeDerivedStatusOfInfiniteLoop(int $inner_status) : int
    {
        $status = $inner_status & ~self::UNEXITABLE_LOOP_INNER_STATUS;
        if ($status === 0) {
            return self::STATUS_RETURN;  // this is an infinite loop, it didn't contain break/throw/return statements?
        }
        if (($status & self::STATUS_BREAK) !== 0) {
            // if the inside of "while (true) {} contains a break statement,
            // then execution can proceed past the end of the loop.
            return ($status & ~self::STATUS_BREAK) | self::STATUS_PROCEED;
        }
        return $status;
    }

    // A return statement unconditionally returns (Assume expression passed in doesn't throw)
    public function visitReturn(Node $node)
    {
        return self::STATUS_RETURN;
    }

    // A exit statement unconditionally exits (Assume expression passed in doesn't throw)
    public function visitExit(Node $node)
    {
        return self::STATUS_RETURN;
    }

    public function visitUnaryOp(Node $node)
    {
        // Don't modify $node->flags, use unmodified flags here
        if ($node->flags !== \ast\flags\UNARY_SILENCE) {
            return self::STATUS_PROCEED;
        }
        // Analyze exit status of `@expr` like `expr` (e.g. @trigger_error())
        $expr = $node->children['expr'];
        if (!($expr instanceof Node)) {
            return self::STATUS_PROCEED;
        }
        return $this->__invoke($expr);
    }

    // A trigger_error statement may or may not exit, depending on the constant and user configuration.
    public function visitCall(Node $node)
    {
        $status = $node->flags & self::STATUS_BITMASK;
        if ($status) {
            return $status;
        }
        $status = $this->computeStatusOfCall($node);
        $node->flags = $status;
        return $status;
    }

    private function computeStatusOfCall(Node $node) : int
    {
        $expression = $node->children['expr'];
        if ($expression instanceof Node) {
            if ($expression->kind !== \ast\AST_NAME) {
                return self::STATUS_PROCEED;  // best guess
            }
            $function_name = $expression->children['name'];
            if (!\is_string($function_name)) {
                return self::STATUS_PROCEED;
            }
        } else {
            if (!\is_string($expression)) {
                return self::STATUS_PROCEED;  // Probably impossible.
            }
            $function_name = $expression;
        }
        if (!$function_name) {
            return self::STATUS_THROW;  // nonsense such as ''();
        }
        if ($function_name[0] === '\\') {
            $function_name = \substr($function_name, 1);
        }
        if (\strcasecmp($function_name, 'trigger_error') === 0) {
            return $this->computeTriggerErrorStatusCodeForConstant($node->children['args']->children[1] ?? null);
        }
        // TODO: Could allow .phan/config.php or plugins to define additional behaviors, e.g. for methods.
        // E.g. if (!$var) {HttpFramework::generate_302_and_die(); }
        return self::STATUS_PROCEED;
    }

    private function computeTriggerErrorStatusCodeForConstant($constant_ast) : int
    {
        // return PROCEED if this can't be determined.
        if (!($constant_ast instanceof Node)) {
            return self::STATUS_PROCEED;
        }
        if ($constant_ast->kind !== \ast\AST_CONST) {
            return self::STATUS_PROCEED;
        }
        $name = $constant_ast->children['name']->children['name'] ?? null;
        if (!\is_string($name)) {
            return self::STATUS_PROCEED;
        }
        if (\in_array($name, ['E_ERROR', 'E_PARSE', 'E_CORE_ERROR', 'E_COMPILE_ERROR', 'E_USER_ERROR'], true)) {
            return self::STATUS_RETURN;
        }
        if ($name === 'E_RECOVERABLE_ERROR') {
            return self::STATUS_THROW;
        }

        return self::STATUS_PROCEED;  // Assume this is a warning or notice?
    }

    /**
     * A statement list has the weakest return status out of all of the (non-PROCEEDing) statements.
     * FIXME: This is buggy, doesn't account for one statement having STATUS_CONTINUE some of the time but not all of it.
     *       (We don't check for STATUS_CONTINUE yet, so this doesn't matter yet.)
     */
    public function visitStmtList(Node $node)
    {
        $status = $node->flags & self::STATUS_BITMASK;
        if ($status) {
            return $status;
        }
        $status = $this->computeStatusOfBlock($node->children);
        $node->flags = $status;
        return $status;
    }

    /**
     * Analyzes a node with kind \ast\AST_IF
     * @return int the exit status of a block (whether or not it would unconditionally exit, return, throw, etc.
     * @override
     */
    public function visitIf(Node $node)
    {
        $status = $node->flags & self::STATUS_BITMASK;
        if ($status) {
            return $status;
        }
        $status = $this->computeStatusOfIf($node);
        $node->flags = $status;
        return $status;
    }

    private function computeStatusOfIf(Node $node) : int
    {
        $has_if_elems_for_all_cases = false;
        $combined_statuses = 0;
        foreach ($node->children as $child_node) {
            $status = $this->visitStmtList($child_node->children['stmts']);
            $combined_statuses |= $status;

            $cond_node = $child_node->children['cond'];
            // check for "else" or "elseif (true)"
            if ($cond_node === null || self::isTruthyLiteral($cond_node)) {
                $has_if_elems_for_all_cases = true;
                break;
            }
        }
        if (!$has_if_elems_for_all_cases) {
            $combined_statuses |= self::STATUS_PROCEED;
        }
        return $combined_statuses;
    }


    /**
     * Analyzes a node with kind \ast\AST_DO_WHILE
     * @return int the exit status of a block (whether or not it would unconditionally exit, return, throw, etc.
     */
    public function visitDoWhile(Node $node)
    {
        $inner_status = $this->visitStmtList($node->children['stmts']);
        if (($inner_status & ~self::STATUS_THROW_OR_RETURN_BITMASK) === 0) {
            // The inner block throws or returns before the end can be reached.
            return $inner_status;
        }
        // TODO: Check for unconditionally false conditions.
        if (self::isTruthyLiteral($node->children['cond'])) {
            // Use a special case to analyze "while (1) {exprs}" or "for (; true; ) {exprs}"
            // TODO: identify infinite loops, mark those as STATUS_NO_PROCEED or STATUS_RETURN.
            return $this->computeDerivedStatusOfInfiniteLoop($inner_status);
        }
        // This is (to our awareness) **not** an infinite loop


        // 1. break/continue apply to the inside of a loop, not outside. Not going to analyze "break 2;", may emit an info level issue in the future.
        // 2. We assume that it's possible that any given loop can have 0 iterations.
        //    A TODO exists above to check for special cases.
        return ($inner_status & ~self::STATUS_CONTINUE_OR_BREAK) | self::STATUS_PROCEED;
    }

    /**
     * Analyzes a node with kind \ast\AST_IF_ELEM
     * @return int the exit status of a block (whether or not it would unconditionally exit, return, throw, etc.
     */
    public function visitIfElem(Node $node)
    {
        $status = $node->flags & self::STATUS_BITMASK;
        if ($status) {
            return $status;
        }
        $status = $this->visitStmtList($node->children['stmts']);
        $node->flags = $status;
        return $status;
    }

    public function visitGoto(Node $node)
    {
        return self::STATUS_GOTO;
    }

    /**
     * @param array<int,Node> $block
     * @return int the exit status of a block (whether or not it would unconditionally exit, return, throw, etc.
     */
    private function computeStatusOfBlock(array $block) : int
    {
        $maybe_status = 0;
        foreach ($block as $child) {
            if ($child === null) {
                continue;
            }
            // e.g. can be non-Node for statement lists such as `if ($a) { return; }echo "X";2;` (under unknown conditions)
            if (!($child instanceof Node)) {
                continue;
            }
            $status = $this->check($child);
            if (($status & self::STATUS_PROCEED) === 0) {
                // If it's guaranteed we won't stop after this statement,
                // then skip the subsequent statements.
                return $status | ($maybe_status & ~self::STATUS_PROCEED);
            }
            $maybe_status |= $status;
        }
        return self::STATUS_PROCEED | $maybe_status;
    }

    public static function willUnconditionallySkipRemainingStatements(Node $node) : bool
    {
        return ((new self())->__invoke($node) & self::STATUS_MAYBE_PROCEED) === 0;
    }

    public static function willUnconditionallyThrowOrReturn(Node $node) : bool
    {
        return ((new self())->__invoke($node) & ~self::STATUS_THROW_OR_RETURN_BITMASK) === 0;
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\ContextNode;
use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\CodeBaseException;
use Phan\Exception\NodeException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Func;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Variable;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\Scope\ClosureScope;
use Phan\Language\Type;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\VoidType;
use Phan\Language\UnionType;
use ast\Node;

class PreOrderAnalysisVisitor extends ScopeVisitor
{
    /**
     * @param CodeBase $code_base
     * The code base in which we're analyzing code
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     */
    /*
    public function __construct(
        CodeBase $code_base,
        Context $context
    ) {
        parent::__construct($code_base, $context);
    }
     */

    /** @param Node $unused_node implementation for unhandled nodes */
    public function visit(Node $unused_node) : Context
    {
        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_CLASS`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitClass(Node $node) : Context
    {
        if ($node->flags & \ast\flags\CLASS_ANONYMOUS) {
            $class_name =
                (new ContextNode(
                    $this->code_base,
                    $this->context,
                    $node
                ))->getUnqualifiedNameForAnonymousClass();
        } else {
            $class_name = (string)$node->children['name'];
        }

        \assert(!empty($class_name), "Class name cannot be empty");

        $alternate_id = 0;

        // Hunt for the alternate of this class defined
        // in this file
        do {
            $class_fqsen = FullyQualifiedClassName::fromStringInContext(
                $class_name,
                $this->context
            )->withAlternateId($alternate_id++);

            if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
                throw new CodeBaseException(
                    $class_fqsen,
                    "Can't find class {$class_fqsen} - aborting"
                );
            }

            $clazz = $this->code_base->getClassByFQSEN(
                $class_fqsen
            );
        } while ($this->context->getProjectRelativePath()
                != $clazz->getFileRef()->getProjectRelativePath()
            || $this->context->getLineNumberStart() != $clazz->getFileRef()->getLineNumberStart()
        );

        return $clazz->getContext()->withScope(
            $clazz->getInternalScope()
        );
    }

    /**
     * Visit a node with kind `\ast\AST_METHOD`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitMethod(Node $node) : Context
    {
        $method_name = (string)$node->children['name'];
        $code_base = $this->code_base;
        $context = $this->context;

        \assert(
            $context->isInClassScope(),
            "Must be in class context to see a method"
        );

        $clazz = $this->getContextClass();

        if (!$clazz->hasMethodWithName(
            $code_base,
            $method_name
        )) {
            throw new CodeBaseException(
                null,
                "Can't find method {$clazz->getFQSEN()}::$method_name() - aborting"
            );
        }

        $method = $clazz->getMethodByName(
            $code_base,
            $method_name
        );
        $method->ensureScopeInitialized($code_base);

        // Parse the comment above the method to get
        // extra meta information about the method.
        $comment = $method->getComment();

        $context = $this->context->withScope(
            $method->getInternalScope()
        );

        // For any @var references in the method declaration,
        // add them as variables to the method's scope
        if ($comment !== null) {
            foreach ($comment->getVariableList() as $parameter) {
                $context->addScopeVariable(
                    $parameter->asVariable($this->context)
                );
            }
        }

        // Add $this to the scope of non-static methods
        if (!($node->flags & \ast\flags\MODIFIER_STATIC)) {
            \assert(
                $clazz->getInternalScope()->hasVariableWithName('this'),
                "Classes must have a \$this variable."
            );

            $context->addScopeVariable(
                $clazz->getInternalScope()->getVariableByName('this')
            );
        }

        // Add each method parameter to the scope. We clone it
        // so that changes to the variable don't alter the
        // parameter definition
        if ($method->getRecursionDepth() === 0) {
            // Add each method parameter to the scope. We clone it
            // so that changes to the variable don't alter the
            // parameter definition
            foreach ($method->getParameterList() as $parameter) {
                $context->addScopeVariable(
                    $parameter->cloneAsNonVariadic()
                );
            }
        }

        // TODO: Why is the check for yield in PreOrderAnalysisVisitor?
        if ($method->getHasYield()) {
            $this->setReturnTypeOfGenerator($method, $node);
        }

        return $context;
    }

    /**
     * Visit a node with kind `\ast\AST_FUNC_DECL`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitFuncDecl(Node $node) : Context
    {
        $function_name = (string)$node->children['name'];
        $code_base = $this->code_base;
        $original_context = $this->context;

        try {
            $canonical_function = (new ContextNode(
                $code_base,
                $original_context,
                $node
            ))->getFunction($function_name, true);
        } catch (CodeBaseException $exception) {
            // This really ought not happen given that
            // we already successfully parsed the code
            // base
            throw $exception;
        }

        // Hunt for the alternate associated with the file we're
        // looking at currently in this context.
        $function = null;
        foreach ($canonical_function->alternateGenerator($code_base) as $alternate_function) {
            if ($alternate_function->getFileRef()->getProjectRelativePath()
                === $original_context->getProjectRelativePath()
            ) {
                $function = $alternate_function;
                break;
            }
        }

        if (empty($function)) {
            // No alternate was found
            throw new CodeBaseException(
                null,
                "Can't find function {$function_name} in context {$this->context} - aborting"
            );
        }

        \assert($function instanceof Func);
        $function->ensureScopeInitialized($code_base);

        $context = $original_context->withScope(
            $function->getInternalScope()
        );

        // Parse the comment above the function to get
        // extra meta information about the method.
        // TODO: Investigate caching information from Comment::fromStringInContext?
        $comment = $function->getComment();

        // For any @var references in the method declaration,
        // add them as variables to the method's scope
        if ($comment !== null) {
            foreach ($comment->getVariableList() as $parameter) {
                $context->addScopeVariable(
                    $parameter->asVariable($this->context)
                );
            }
        }

        if ($function->getRecursionDepth() === 0) {
            // Add each method parameter to the scope. We clone it
            // so that changes to the variable don't alter the
            // parameter definition
            foreach ($function->getParameterList() as $parameter) {
                $context->addScopeVariable(
                    $parameter->cloneAsNonVariadic()
                );
            }
        }

        if ($function->getHasYield()) {
            $this->setReturnTypeOfGenerator($function, $node);
        }
        if (!$function->getHasReturn() && $function->getUnionType()->isEmpty()) {
            $function->setUnionType(VoidType::instance(false)->asUnionType());
        }

        return $context;
    }

    /**
     * @return ?FullyQualifiedClassName
     */
    private static function getOverrideClassFQSEN(CodeBase $code_base, Func $func)
    {
        $closure_scope = $func->getInternalScope();
        if ($closure_scope instanceof ClosureScope) {
            $class_fqsen = $closure_scope->getOverrideClassFQSEN();
            if (!$class_fqsen) {
                return null;
            }

            // Postponed the check for undeclared closure scopes to the analysis phase,
            // because classes are still being parsed in the parse phase.
            if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
                $func_context = $func->getContext();
                Issue::maybeEmit(
                    $code_base,
                    $func_context,
                    Issue::UndeclaredClosureScope,
                    $func_context->getLineNumberStart(),
                    (string)$class_fqsen
                );
                $closure_scope->overrideClassFQSEN(null);  // Avoid an uncaught CodeBaseException due to missing class for @phan-closure-scope
                return null;
            }
            return $class_fqsen;
        }
        return null;
    }

    /**
     * If a Closure overrides the scope(class) it will be executed in (via doc comment)
     * then return a context with the new scope instead.
     * @return void
     */
    private static function addThisVariableToInternalScope(
        CodeBase $code_base,
        Context $context,
        Func $func
    ) {
        $override_this_fqsen = self::getOverrideClassFQSEN($code_base, $func);
        if ($override_this_fqsen !== null) {
            if ($context->getScope()->hasVariableWithName('this') || !$context->isInClassScope()) {
                // Handle @phan-closure-scope - Should set $this to the overriden class, as well as handling self:: and parent::
                $func->getInternalScope()->addVariable(
                    new Variable(
                        $context,
                        'this',
                        $override_this_fqsen->asUnionType(),
                        0
                    )
                );
            }
            return;
        }
        // If we have a 'this' variable in our current scope,
        // pass it down into the closure
        if ($context->getScope()->hasVariableWithName('this')) {
            // Normal case: Closures inherit $this from parent scope.
            $thisVarFromScope = $context->getScope()->getVariableByName('this');
            $func->getInternalScope()->addVariable($thisVarFromScope);
        }
    }


    /**
     * Visit a node with kind `\ast\AST_CLOSURE`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitClosure(Node $node) : Context
    {
        $code_base = $this->code_base;
        $context = $this->context;
        $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext(
            $context->withLineNumberStart($node->lineno ?? 0),
            $node
        );
        $func = $code_base->getFunctionByFQSEN($closure_fqsen);
        $func->ensureScopeInitialized($code_base);

        // If we have a 'this' variable in our current scope,
        // pass it down into the closure
        self::addThisVariableToInternalScope($code_base, $context, $func);

        // Make the closure reachable by FQSEN from anywhere
        $code_base->addFunction($func);

        if (!empty($node->children['uses'])
            && $node->children['uses']->kind == \ast\AST_CLOSURE_USES
        ) {
            $uses = $node->children['uses'];
            foreach ($uses->children as $use) {
                if (!($use instanceof Node) || $use->kind != \ast\AST_CLOSURE_VAR) {
                    $this->emitIssue(
                        Issue::VariableUseClause,
                        $node->lineno ?? 0
                    );
                    continue;
                }

                $variable_name = (new ContextNode(
                    $code_base,
                    $context,
                    $use->children['name']
                ))->getVariableName();

                if (empty($variable_name)) {
                    continue;
                }

                $variable = null;

                // Check to see if the variable exists in this scope
                if (!$context->getScope()->hasVariableWithName(
                    $variable_name
                )) {
                    // If this is not pass-by-reference variable we
                    // have a problem
                    if (!($use->flags & \ast\flags\PARAM_REF)) {
                        Issue::maybeEmitWithParameters(
                            $this->code_base,
                            clone($context)->withLineNumberStart($use->lineno),
                            Issue::UndeclaredVariable,
                            $node->lineno ?? 0,
                            [$variable_name]
                        );
                        continue;
                    } else {
                        // If the variable doesn't exist, but its
                        // a pass-by-reference variable, we can
                        // just create it
                        $variable = Variable::fromNodeInContext(
                            $use,
                            $context,
                            $this->code_base,
                            false
                        );
                        // And add it to the scope of the parent (For https://github.com/phan/phan/issues/367)
                        $context->getScope()->addVariable($variable);
                    }
                } else {
                    $variable = $context->getScope()->getVariableByName(
                        $variable_name
                    );

                    // If this isn't a pass-by-reference variable, we
                    // clone the variable so state within this scope
                    // doesn't update the outer scope
                    if (!($use->flags & \ast\flags\PARAM_REF)) {
                        $variable = clone($variable);
                    }
                }

                // Pass the variable into a new scope
                $func->getInternalScope()->addVariable($variable);
            }
        }
        if (!$func->getHasReturn() && $func->getUnionType()->isEmpty()) {
            $func->setUnionType(VoidType::instance(false)->asUnionType());
        }

        // Add parameters to the context.
        $context = $context->withScope($func->getInternalScope());

        $comment = $func->getComment();

        // For any @var references in the method declaration,
        // add them as variables to the method's scope
        if ($comment !== null) {
            foreach ($comment->getVariableList() as $parameter) {
                $context->addScopeVariable(
                    $parameter->asVariable($this->context)
                );
            }
        }
        if ($func->getRecursionDepth() === 0) {
            // Add each closure parameter to the scope. We clone it
            // so that changes to the variable don't alter the
            // parameter definition
            foreach ($func->getParameterList() as $parameter) {
                $context->addScopeVariable(
                    $parameter->cloneAsNonVariadic()
                );
            }
        }

        if ($func->getHasYield()) {
            $this->setReturnTypeOfGenerator($func, $node);
        }

        return $context;
    }

    /**
     * The return type of the given FunctionInterface to a Generator.
     * Emit an Issue if the documented return type is incompatible with that.
     * @return void
     */
    private function setReturnTypeOfGenerator(FunctionInterface $func, Node $node)
    {
        // Currently, there is no way to describe the types passed to
        // a Generator in phpdoc.
        // So, nothing bothers recording the types beyond \Generator.
        $func->setHasReturn(true);  // Returns \Generator, technically
        $func->setHasYield(true);
        if ($func->getUnionType()->isEmpty()) {
            $func->setIsReturnTypeUndefined(true);
            $func->setUnionType($func->getUnionType()->withType(Type::fromNamespaceAndName('\\', 'Generator', false)));
        }
        if (!$func->isReturnTypeUndefined()) {
            $func_return_type = $func->getUnionType();
            $func_return_type_can_cast = $func_return_type->canCastToExpandedUnionType(
                Type::fromNamespaceAndName('\\', 'Generator', false)->asUnionType(),
                $this->code_base
            );
            if (!$func_return_type_can_cast) {
                // At least one of the documented return types must
                // be Generator, Iterable, or Traversable.
                // Check for the issue here instead of in visitReturn/visitYield so that
                // the check is done exactly once.
                $this->emitIssue(
                    Issue::TypeMismatchReturn,
                    $node->lineno ?? 0,
                    '\\Generator',
                    $func->getName(),
                    (string)$func_return_type
                );
            }
        }
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * An unchanged context resulting from parsing the node
     */
    public function visitAssign(Node $node) : Context
    {
        // In php 7.0, a **valid** parsed AST would be an \ast\AST_LIST.
        // However, --force-polyfill-parser will emit \ast\AST_ARRAY.
        $var_node = $node->children['var'];
        if (Config::get_closest_target_php_version_id() < 70100 && $var_node instanceof Node && $var_node->kind === \ast\AST_ARRAY) {
            $this->analyzeArrayAssignBackwardsCompatibility($var_node);
        }
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitForeach(Node $node) : Context
    {
        $expression_union_type = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr']
        );

        // Check the expression type to make sure its
        // something we can iterate over
        if ($expression_union_type->isScalar()) {
            $this->emitIssue(
                Issue::TypeMismatchForeach,
                $node->lineno ?? 0,
                (string)$expression_union_type
            );
        }

        $value_node = $node->children['value'];
        if (!($value_node instanceof Node)) {
            return $this->context;
        }
        if ($value_node->kind == \ast\AST_ARRAY) {
            if (Config::get_closest_target_php_version_id() < 70100) {
                $this->analyzeArrayAssignBackwardsCompatibility($value_node);
            }
            $this->inferTypesForForeachArrayDestructuring($expression_union_type->genericArrayElementTypes(), $value_node);

        // Otherwise, read the value as regular variable and
        // add it to the scope
        } else {
            // Filter out the non-generic types of the
            // expression
            $non_generic_expression_union_type =
                $expression_union_type->genericArrayElementTypes();
            // Create a variable for the value
            $variable = Variable::fromNodeInContext(
                $value_node,
                $this->context,
                $this->code_base,
                false
            );


            // If we were able to figure out the type and its
            // a generic type, then set its element types as
            // the type of the variable
            if (!$non_generic_expression_union_type->isEmpty()) {
                $variable->setUnionType($non_generic_expression_union_type);
            }

            // Add the variable to the scope
            $this->context->addScopeVariable($variable);
        }

        // If there's a key, make a variable out of that too
        $key_node = $node->children['key'];
        if ($key_node instanceof \ast\Node) {
            if ($key_node->kind == \ast\AST_LIST) {
                throw new NodeException(
                    $node,
                    "Can't use list() as a key element - aborting"
                );
            }

            $variable = Variable::fromNodeInContext(
                $key_node,
                $this->context,
                $this->code_base,
                false
            );
            if (!$expression_union_type->isEmpty()) {
                // TODO: Support Traversable<Key, T> then return Key.
                // If we see array<int,T> or array<string,T> and no other array types, we're reasonably sure the foreach key is an integer or a string, so set it.
                $union_type_of_array_key = UnionTypeVisitor::arrayKeyUnionTypeOfUnionType($expression_union_type);
                if ($union_type_of_array_key !== null) {
                    $variable->setUnionType($union_type_of_array_key);
                }
            }

            $this->context->addScopeVariable($variable);
        }

        // Note that we're not creating a new scope, just
        // adding variables to the existing scope
        return $this->context;
    }

    private function analyzeArrayAssignBackwardsCompatibility(Node $node)
    {
        if ($node->flags !== \ast\flags\ARRAY_SYNTAX_LIST) {
            $this->emitIssue(
                Issue::CompatibleShortArrayAssignPHP70,
                $node->lineno ?? 0
            );
        }
        foreach ($node->children as $array_elem) {
            if (isset($array_elem->children['key'])) {
                $this->emitIssue(
                    Issue::CompatibleKeyedArrayAssignPHP70,
                    $array_elem->lineno ?? 0
                );
                break;
            }
        }
    }

    /**
     * @return void
     */
    private function inferTypesForForeachArrayDestructuring(
        UnionType $element_union_type,
        Node $value_node
    ) {
        if ($element_union_type->hasTopLevelArrayShapeTypeInstances()) {
            $this->inferTypesForShapedForeachArrayDestructuring($element_union_type, $value_node);
        } else {
            $this->inferTypesForGenericForeachArrayDestructuring($element_union_type, $value_node);
        }
    }

    /**
     * @param UnionType $element_union_type (The T[]|S[] types from the expression of the foreach)
     * @param Node $value_node a node of type ast\AST_ARRAY that is in the foreach's `as` value
     * @return void
     */
    private function inferTypesForShapedForeachArrayDestructuring(
        UnionType $element_union_type,
        Node $value_node
    ) {
        $key_set = [];

        $expect_int_keys_lineno = false;
        $expect_string_keys_lineno = false;

        $fallback_second_order_element_type = null;
        $get_fallback_second_order_element_type = function () use (&$fallback_second_order_element_type, $element_union_type) : UnionType {
            return $fallback_second_order_element_type ?? ($fallback_second_order_element_type = $element_union_type->genericArrayElementTypes());
        };
        foreach ($value_node->children as $child_node) {
            // $key_node = $child_node->children['key'] ?? null;
            $value_elem_node = $child_node->children['value'] ?? null;

            // for syntax like: foreach ([] as list(, $a));
            if ($value_elem_node === null) {
                continue;
            }
            \assert($value_elem_node instanceof Node);

            // Get the key and value nodes for each
            // array element we're assigning to
            // TODO: Check key types are valid?
            $key_node = $child_node->children['key'];
            $key_value = null;

            if ($key_node === null) {
                $key_set[] = true;
                \end($key_set);
                $key_value = \key($key_set);

                $expect_int_keys_lineno = $child_node->lineno;  // list($x, $y) = ... is equivalent to list(0 => $x, 1 => $y) = ...
            } else {
                if ($key_node instanceof Node) {
                    $key_value = (new ContextNode($this->code_base, $this->context, $key_node))->getEquivalentPHPScalarValue();
                } else {
                    $key_value = $key_node;
                }
                if (\is_scalar($key_value)) {
                    $key_set[$key_value] = true;
                    if (\is_int($key_value)) {
                        $expect_int_keys_lineno = $child_node->lineno;
                    } elseif (\is_string($key_value)) {
                        $expect_string_keys_lineno = $child_node->lineno;
                    }
                } else {
                    $key_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $key_node);
                    $key_type_enum = GenericArrayType::keyTypeFromUnionTypeValues($key_type);
                    // TODO: Warn about types that can't cast to int|string
                    if ($key_type_enum === GenericArrayType::KEY_INT) {
                        $expect_int_keys_lineno = $child_node->lineno;
                    } elseif ($key_type_enum === GenericArrayType::KEY_STRING) {
                        $expect_string_keys_lineno = $child_node->lineno;
                    }
                }
            }

            $variable = Variable::fromNodeInContext(
                $value_elem_node,
                $this->context,
                $this->code_base,
                false
            );

            if (\is_scalar($key_value)) {
                $second_order_non_generic_expression_union_type = UnionTypeVisitor::resolveArrayShapeElementTypesForOffset($element_union_type, $key_value);
                if ($second_order_non_generic_expression_union_type === null) {
                    $second_order_non_generic_expression_union_type = $get_fallback_second_order_element_type();
                } elseif ($second_order_non_generic_expression_union_type === false) {
                    $this->emitIssue(
                        Issue::TypeInvalidDimOffsetArrayDestructuring,
                        $child_node->lineno,
                        json_encode($key_value),
                        (string)$element_union_type
                    );
                    $second_order_non_generic_expression_union_type = $get_fallback_second_order_element_type();
                }
            } else {
                $second_order_non_generic_expression_union_type = $get_fallback_second_order_element_type();
            }

            // If we were able to figure out the type and its
            // a generic type, then set its element types as
            // the type of the variable
            $variable->setUnionType(
                $second_order_non_generic_expression_union_type
            );

            $this->context->addScopeVariable($variable);
        }

        $this->checkMismatchForeachArrayDestructuringKey($expect_int_keys_lineno, $expect_string_keys_lineno, $element_union_type);
    }

    /**
     * @param UnionType $element_union_type (The T[]|S[] types from the expression of the foreach)
     * @param Node $value_node a node of type ast\AST_ARRAY that is in the foreach's `as` value
     * @return void
     */
    private function inferTypesForGenericForeachArrayDestructuring(
        UnionType $element_union_type,
        Node $value_node
    ) {
        $scalar_array_key_cast = Config::getValue('scalar_array_key_cast');
        $expect_string_keys_lineno = false;
        $expect_int_keys_lineno = false;

        foreach ($value_node->children as $child_node) {
            $value_elem_node = $child_node->children['value'] ?? null;

            // for syntax like: foreach ([] as list(, $a));
            if ($value_elem_node === null) {
                continue;
            }
            \assert($value_elem_node instanceof Node);

            $key_node = $child_node->children['key'];
            if (!$scalar_array_key_cast) {
                if ($key_node === null) {
                    $expect_int_keys_lineno = $child_node->lineno;  // list($x, $y) = ... is equivalent to list(0 => $x, 1 => $y) = ...
                } else {
                    $key_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $key_node);
                    $key_type_enum = GenericArrayType::keyTypeFromUnionTypeValues($key_type);
                    // TODO: Warn about types that can't cast to int|string
                    if ($key_type_enum === GenericArrayType::KEY_INT) {
                        $expect_int_keys_lineno = $child_node->lineno;
                    } elseif ($key_type_enum === GenericArrayType::KEY_STRING) {
                        $expect_string_keys_lineno = $child_node->lineno;
                    }
                }
            }

            $variable = Variable::fromNodeInContext(
                $value_elem_node,
                $this->context,
                $this->code_base,
                false
            );

            // If we were able to figure out the type and its
            // a generic type, then set its element types as
            // the type of the variable
            if (!$element_union_type->isEmpty()) {
                $second_order_non_generic_expression_union_type =
                    $element_union_type->genericArrayElementTypes();

                if (!$second_order_non_generic_expression_union_type->isEmpty()) {
                    $variable->setUnionType(
                        $second_order_non_generic_expression_union_type
                    );
                }
            }

            $this->context->addScopeVariable($variable);
        }
        // TODO: Issue::TypeMismatchArrayDestructuringKey,

        $this->checkMismatchForeachArrayDestructuringKey($expect_int_keys_lineno, $expect_string_keys_lineno, $element_union_type);
    }

    /**
     * TODO: Deduplicate these checks in a trait
     * @param int|false $expect_int_keys_lineno
     * @param int|false $expect_string_keys_lineno
     * @param UnionType $element_union_type
     * @return void
     */
    private function checkMismatchForeachArrayDestructuringKey($expect_int_keys_lineno, $expect_string_keys_lineno, UnionType $element_union_type)
    {
        if ($expect_int_keys_lineno !== false || $expect_string_keys_lineno !== false) {
            $right_hand_key_type = GenericArrayType::keyTypeFromUnionTypeKeys($element_union_type);
            if ($expect_int_keys_lineno !== false && ($right_hand_key_type & GenericArrayType::KEY_INT) === 0) {
                Issue::maybeEmit(
                    $this->code_base,
                    $this->context,
                    Issue::TypeMismatchArrayDestructuringKey,
                    $expect_int_keys_lineno,
                    'int',
                    'string'
                );
            } elseif ($expect_string_keys_lineno !== false && ($right_hand_key_type & GenericArrayType::KEY_STRING) === 0) {
                Issue::maybeEmit(
                    $this->code_base,
                    $this->context,
                    Issue::TypeMismatchArrayDestructuringKey,
                    $expect_string_keys_lineno,
                    'string',
                    'int'
                );
            }
        }
    }


    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitCatch(Node $node) : Context
    {
        $union_type = UnionTypeVisitor::unionTypeFromClassNode(
            $this->code_base,
            $this->context,
            $node->children['class']
        );

        try {
            $class_list = (new ContextNode(
                $this->code_base,
                $this->context,
                $node->children['class']
            ))->getClassList(false, ContextNode::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME);

            foreach ($class_list as $class) {
                $class->addReference($this->context);
            }
        } catch (CodeBaseException $exception) {
            $this->emitIssue(
                Issue::UndeclaredClassCatch,
                $node->lineno ?? 0,
                (string)$exception->getFQSEN()
            );

            $union_type = $union_type->withType(Type::fromFullyQualifiedString('\Throwable'));
        }

        $variable_name = (new ContextNode(
            $this->code_base,
            $this->context,
            $node->children['var']
        ))->getVariableName();

        if (!empty($variable_name)) {
            $variable = Variable::fromNodeInContext(
                $node->children['var'],
                $this->context,
                $this->code_base,
                false
            );

            if (!$union_type->isEmpty()) {
                $variable->setUnionType($union_type);
            }

            $this->context->addScopeVariable($variable);
        }

        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitIfElem(Node $node) : Context
    {
        $cond = $node->children['cond'] ?? null;
        if (!($cond instanceof Node)) {
            return $this->context;
        }

        // Look to see if any proofs we do within the condition
        // can say anything about types within the statement
        // list.
        return (new ConditionVisitor(
            $this->code_base,
            $this->context
        ))->__invoke($cond);
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitWhile(Node $node) : Context
    {
        $cond = $node->children['cond'];
        if (!($cond instanceof Node)) {
            return $this->context;
        }

        // Look to see if any proofs we do within the condition of the while
        // can say anything about types within the statement
        // list.
        return (new ConditionVisitor(
            $this->code_base,
            $this->context
        ))->__invoke($cond);
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitFor(Node $node) : Context
    {
        $cond = $node->children['cond'];
        if (!($cond instanceof Node)) {
            return $this->context;
        }

        // Look to see if any proofs we do within the condition of the while
        // can say anything about types within the statement
        // list.
        return (new ConditionVisitor(
            $this->code_base,
            $this->context
        ))->__invoke($cond);
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitCall(Node $node) : Context
    {
        $name = $node->children['expr']->children['name'] ?? null;
        // Look only at nodes of the form `assert(expr, ...)`.
        if ($name !== 'assert') {
            return $this->context;
        }
        $args_first_child = $node->children['args']->children[0] ?? null;
        if (!($args_first_child instanceof Node)) {
            return $this->context;
        }

        // Look to see if the asserted expression says anything about
        // the types of any variables.
        return (new ConditionVisitor(
            $this->code_base,
            $this->context
        ))->__invoke($args_first_child);
    }

    /**
     * @return Clazz
     * Get the class on this scope or fail real hard
     */
    private function getContextClass() : Clazz
    {
        return $this->context->getClassInScope($this->code_base);
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CodeBase;
use Phan\Config;
use Phan\Issue;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Method;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\TemplateType;
use Phan\Language\UnionType;

class ReturnTypesAnalyzer
{

    /**
     * Check method return types (phpdoc and real) to make sure they're valid
     *
     * @return void
     */
    public static function analyzeReturnTypes(
        CodeBase $code_base,
        FunctionInterface $method
    ) {
        $return_type = $method->getUnionType();
        $real_return_type = $method->getRealReturnType();
        $phpdoc_return_type = $method->getPHPDocReturnType();
        // TODO: use method->getPHPDocUnionType() to check compatibility, like analyzeParameterTypesDocblockSignaturesMatch

        // Look at each parameter to make sure their types
        // are valid

        // Look at each type in the function's return union type
        foreach ($return_type->getTypeSet() as $outer_type) {
            $type = $outer_type;
            // TODO: Expand this to ArrayShapeType, add unit test of `@return array{key:MissingClazz}`
            while ($type instanceof GenericArrayType) {
                $type = $type->genericArrayElementType();
            }

            // If its a native type or a reference to
            // self, its OK
            if ($type->isNativeType() || ($method instanceof Method && ($type->isSelfType() || $type->isStaticType()))) {
                continue;
            }

            if ($type instanceof TemplateType) {
                if ($method instanceof Method) {
                    if ($method->isStatic()) {
                        Issue::maybeEmit(
                            $code_base,
                            $method->getContext(),
                            Issue::TemplateTypeStaticMethod,
                            $method->getFileRef()->getLineNumberStart(),
                            (string)$method->getFQSEN()
                        );
                    }
                }
            } else {
                // Make sure the class exists
                $type_fqsen = $type->asFQSEN();
                \assert($type_fqsen instanceof FullyQualifiedClassName, 'non-native types must be class names');
                if (!$code_base->hasClassWithFQSEN($type_fqsen)) {
                    Issue::maybeEmit(
                        $code_base,
                        $method->getContext(),
                        Issue::UndeclaredTypeReturnType,
                        $method->getFileRef()->getLineNumberStart(),
                        $method->getName(),
                        (string)$outer_type
                    );
                }
            }
        }
        if (Config::getValue('check_docblock_signature_return_type_match') && !$real_return_type->isEmpty() && ($phpdoc_return_type instanceof UnionType) && !$phpdoc_return_type->isEmpty()) {
            $context = $method->getContext();
            $resolved_real_return_type = $real_return_type->withStaticResolvedInContext($context);
            foreach ($phpdoc_return_type->getTypeSet() as $phpdoc_type) {
                $is_exclusively_narrowed = $phpdoc_type->isExclusivelyNarrowedFormOrEquivalentTo(
                    $resolved_real_return_type,
                    $context,
                    $code_base
                );
                // Make sure that the commented type is a narrowed
                // or equivalent form of the syntax-level declared
                // return type.
                if (!$is_exclusively_narrowed) {
                    if (!$method->hasSuppressIssue(Issue::TypeMismatchDeclaredReturn)) {
                        Issue::maybeEmit(
                            $code_base,
                            $context,
                            Issue::TypeMismatchDeclaredReturn,
                            $context->getLineNumberStart(),
                            $method->getName(),
                            $phpdoc_type->__toString(),
                            $real_return_type->__toString()
                        );
                    }
                }
                if ($is_exclusively_narrowed && Config::getValue('prefer_narrowed_phpdoc_return_type')) {
                    $normalized_phpdoc_return_type = ParameterTypesAnalyzer::normalizeNarrowedParamType($phpdoc_return_type, $real_return_type);
                    if ($normalized_phpdoc_return_type) {
                        $method->setUnionType($normalized_phpdoc_return_type);
                    } else {
                        // This check isn't urgent to fix, and is specific to nullable casting rules,
                        // so use a different issue type.
                        if (!$method->hasSuppressIssue(Issue::TypeMismatchDeclaredReturnNullable)) {
                            Issue::maybeEmit(
                                $code_base,
                                $context,
                                Issue::TypeMismatchDeclaredReturnNullable,
                                $context->getLineNumberStart(),
                                $method->getName(),
                                $phpdoc_type->__toString(),
                                $real_return_type->__toString()
                            );
                        }
                    }
                }
            }
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\CodeBaseException;
use Phan\Issue;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Method;
use Phan\Language\Element\Parameter;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\IterableType;
use Phan\Language\Type\MixedType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\ObjectType;
use Phan\Language\Type\TemplateType;
use Phan\Language\Type\VoidType;
use Phan\Language\UnionType;

class ParameterTypesAnalyzer
{

    /**
     * Check method parameters to make sure they're valid
     *
     * @return void
     */
    public static function analyzeParameterTypes(
        CodeBase $code_base,
        FunctionInterface $method
    ) {
        if (Config::getValue('check_docblock_signature_param_type_match')) {
            self::analyzeParameterTypesDocblockSignaturesMatch($code_base, $method);
        }

        self::checkCommentParametersAreInOrder($code_base, $method);
        $target_php_version = Config::get_closest_target_php_version_id();
        if ($target_php_version < 70200 && !$method->isFromPHPDoc()) {
            self::analyzeRealSignatureCompatibility($code_base, $method, $target_php_version);
        }

        // Look at each parameter to make sure their types
        // are valid
        foreach ($method->getParameterList() as $parameter) {
            $union_type = $parameter->getUnionType();

            // Look at each type in the parameter's Union Type
            foreach ($union_type->getTypeSet() as $outer_type) {
                $type = $outer_type;

                // TODO: Add unit test of `array{key:MissingClazz}`
                while ($type instanceof GenericArrayType) {
                    $type = $type->genericArrayElementType();
                }

                // If its a native type or a reference to
                // self, its OK
                if ($type->isNativeType() || ($method instanceof Method && ($type->isSelfType() || $type->isStaticType()))) {
                    continue;
                }

                if ($type instanceof TemplateType) {
                    if ($method instanceof Method) {
                        if ($method->isStatic()) {
                            Issue::maybeEmit(
                                $code_base,
                                $method->getContext(),
                                Issue::TemplateTypeStaticMethod,
                                $method->getFileRef()->getLineNumberStart(),
                                (string)$method->getFQSEN()
                            );
                        }
                    }
                } else {
                    // Make sure the class exists
                    $type_fqsen = $type->asFQSEN();
                    \assert($type_fqsen instanceof FullyQualifiedClassName, 'non-native types must be class names');
                    if (!$code_base->hasClassWithFQSEN($type_fqsen)) {
                        Issue::maybeEmit(
                            $code_base,
                            $method->getContext(),
                            Issue::UndeclaredTypeParameter,
                            $method->getFileRef()->getLineNumberStart(),
                            (string)$outer_type
                        );
                    }
                }
            }
        }

        if ($method instanceof Method) {
            if ($method->getName() === '__construct') {
                $class = $method->getClass($code_base);
                if ($class->isGeneric()) {
                    $class->hydrate($code_base);
                    self::analyzeGenericClassConstructor($code_base, $class, $method);
                }
            }
            self::analyzeOverrideSignature($code_base, $method);
        }
    }

    /**
     * Precondition: $target_php_version < 70200
     */
    private static function analyzeRealSignatureCompatibility(CodeBase $code_base, FunctionInterface $method, int $target_php_version)
    {
        $php70_checks = $target_php_version < 70100;

        foreach ($method->getRealParameterList() as $real_parameter) {
            foreach ($real_parameter->getUnionType()->getTypeSet() as $type) {
                $type_class = \get_class($type);
                if ($php70_checks) {
                    if ($type->getIsNullable()) {
                        if ($real_parameter->getIsUsingNullableSyntax()) {
                            Issue::maybeEmit(
                                $code_base,
                                $method->getContext(),
                                Issue::CompatibleNullableTypePHP70,
                                $real_parameter->getFileRef()->getLineNumberStart(),
                                (string)$type
                            );
                        }
                    }
                    if ($type_class === IterableType::class) {
                        Issue::maybeEmit(
                            $code_base,
                            $method->getContext(),
                            Issue::CompatibleIterableTypePHP70,
                            $real_parameter->getFileRef()->getLineNumberStart(),
                            (string)$type
                        );
                    }
                }
                if ($type_class === ObjectType::class) {
                    Issue::maybeEmit(
                        $code_base,
                        $method->getContext(),
                        Issue::CompatibleObjectTypePHP71,
                        $real_parameter->getFileRef()->getLineNumberStart(),
                        (string)$type
                    );
                }
            }
        }
        foreach ($method->getRealReturnType()->getTypeSet() as $type) {
            $type_class = \get_class($type);
            if ($php70_checks) {
                if ($type->getIsNullable()) {
                    Issue::maybeEmit(
                        $code_base,
                        $method->getContext(),
                        Issue::CompatibleNullableTypePHP70,
                        $method->getFileRef()->getLineNumberStart(),
                        (string)$type
                    );
                }
                // Could check for use statements, but `php7.1 -l path/to/file.php` would do that already.
                if ($type_class === VoidType::class) {
                    Issue::maybeEmit(
                        $code_base,
                        $method->getContext(),
                        Issue::CompatibleVoidTypePHP70,
                        $method->getFileRef()->getLineNumberStart(),
                        (string)$type
                    );
                } elseif ($type_class === IterableType::class) {
                    Issue::maybeEmit(
                        $code_base,
                        $method->getContext(),
                        Issue::CompatibleIterableTypePHP70,
                        $method->getFileRef()->getLineNumberStart(),
                        (string)$type
                    );
                }
            }
            if ($type_class === ObjectType::class) {
                Issue::maybeEmit(
                    $code_base,
                    $method->getContext(),
                    Issue::CompatibleObjectTypePHP71,
                    $method->getFileRef()->getLineNumberStart(),
                    (string)$type
                );
            }
        }
    }

    /**
     * @return void
     * @suppress PhanPluginUnusedVariable
     */
    private static function checkCommentParametersAreInOrder(CodeBase $code_base, FunctionInterface $method)
    {
        $comment = $method->getComment();
        if ($comment === null) {
            return;
        }
        $parameter_map = $comment->getParameterMap();
        if (\count($parameter_map) < 2) {
            // There have to be at least two comment parameters for the parameters to be out of order
            return;
        }
        $prev_index = -1;
        $prev_name = -1;
        $expected_parameter_order = \array_flip(\array_keys($comment->getParameterMap()));
        foreach ($method->getParameterList() as $parameter) {
            $parameter_name = $parameter->getName();
            $parameter_index_in_comment = $expected_parameter_order[$parameter_name] ?? null;
            if ($parameter_index_in_comment === null) {
                continue;
            }
            if ($parameter_index_in_comment < $prev_index) {
                Issue::maybeEmit(
                    $code_base,
                    $method->getContext(),
                    Issue::CommentParamOutOfOrder,
                    $method->getFileRef()->getLineNumberStart(),
                    $prev_name,
                    $parameter_name
                );
                return;
            }
            $prev_name = $parameter_name;
            $prev_index = $parameter_index_in_comment;
        }
    }

    /**
     * Make sure signatures line up between methods and the
     * methods they override
     *
     * @see https://en.wikipedia.org/wiki/Liskov_substitution_principle
     */
    private static function analyzeOverrideSignature(
        CodeBase $code_base,
        Method $method
    ) {
        if (!Config::getValue('analyze_signature_compatibility')) {
            return;
        }

        // Hydrate the class this method is coming from in
        // order to understand if its an override or not
        $class = $method->getClass($code_base);
        $class->hydrate($code_base);

        // Check to see if the method is an override
        // $method->analyzeOverride($code_base);

        // Make sure we're actually overriding something
        // TODO(in another PR): check that signatures of magic methods are valid, if not done already (e.g. __get expects one param, most can't define return types, etc.)?
        $is_actually_override = $method->getIsOverride();

        if (!$is_actually_override && $method->isOverrideIntended()) {
            self::analyzeOverrideComment($code_base, $method);
        }

        if (!$is_actually_override) {
            return;
        }

        // Get the method(s) that are being overridden
        // E.g. if the subclass, the parent class, and an interface the subclass implements implement a method,
        //      then this has to check two different overrides (Subclass overriding parent class, and subclass overriding abstract method in interface)
        try {
            $o_method_list = $method->getOverriddenMethods($code_base);
        } catch (CodeBaseException $e) {
            // TODO: Remove if no edge cases are seen.
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                Issue::UnanalyzableInheritance,
                $method->getFileRef()->getLineNumberStart(),
                $method->getFQSEN()
            );
            return;
        }
        foreach ($o_method_list as $o_method) {
            self::analyzeOverrideSignatureForOverriddenMethod($code_base, $method, $class, $o_method);
        }
    }

    /**
     * @return void
     */
    private static function analyzeOverrideComment(CodeBase $code_base, Method $method)
    {
        if ($method->getIsMagic()) {
            return;
        }
        // Only emit this issue on the base class, not for the subclass which inherited it
        if ($method->getDefiningFQSEN() !== $method->getFQSEN()) {
            return;
        }
        if ($method->hasSuppressIssue(Issue::CommentOverrideOnNonOverrideMethod)) {
            return;
        }
        Issue::maybeEmit(
            $code_base,
            $method->getContext(),
            Issue::CommentOverrideOnNonOverrideMethod,
            $method->getFileRef()->getLineNumberStart(),
            $method->getFQSEN()
        );
    }

    private static function analyzeGenericClassConstructor(CodeBase $code_base, Clazz $class, Method $method)
    {
        // Get the set of template type identifiers defined on
        // the class
        $template_type_identifiers = \array_keys(
            $class->getTemplateTypeMap()
        );

        // Get the set of template type identifiers defined
        // across all parameter types
        $parameter_template_type_identifiers = [];
        foreach ($method->getParameterList() as $parameter) {
            foreach ($parameter->getUnionType()->getTypeSet() as $type) {
                if ($type instanceof TemplateType) {
                    $parameter_template_type_identifiers[] =
                        $type->getName();
                }
            }
        }

        $missing_template_type_identifiers = \array_diff(
            $template_type_identifiers,
            $parameter_template_type_identifiers
        );

        if ($missing_template_type_identifiers) {
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                Issue::GenericConstructorTypes,
                $method->getFileRef()->getLineNumberStart(),
                implode(',', $missing_template_type_identifiers),
                (string)$class->getFQSEN()
            );
        }
    }

    /**
     * Make sure signatures line up between methods and a method it overrides.
     *
     * @see https://en.wikipedia.org/wiki/Liskov_substitution_principle
     *
     * @param CodeBase $code_base
     * @param Method $method the overriding method
     * @param Clazz  $class the subclass where the overrides take place
     * @param Method $o_method the overridden method method
     */
    private static function analyzeOverrideSignatureForOverriddenMethod(
        CodeBase $code_base,
        Method $method,
        Clazz $class,
        Method $o_method
    ) {
        if ($o_method->isFinal()) {
            // Even if it is a constructor, verify that a method doesn't override a final method.
            // TODO: different warning for trait (#1126)
            self::warnOverridingFinalMethod($code_base, $method, $class, $o_method);
        }

        // Unless it is an abstract constructor,
        // don't worry about signatures lining up on
        // constructors. We just want to make sure that
        // calling a method on a subclass won't cause
        // a runtime error. We usually know what we're
        // constructing at instantiation time, so there
        // is less of a risk.
        if ($method->getName() === '__construct') {
            if (!$o_method->isAbstract()) {
                return;
            }
        }

        // Don't bother warning about incompatible signatures for private methods.
        // (But it is an error to override a private final method)
        if ($o_method->isPrivate()) {
            return;
        }

        // Get the class that the overridden method lives on
        $o_class = $o_method->getClass($code_base);

        // A lot of analyzeOverrideRealSignature is redundant.
        // However, phan should consistently emit both issue types if one of them is suppressed.
        self::analyzeOverrideRealSignature($code_base, $method, $class, $o_method, $o_class);

        // Phan needs to complain in some cases, such as a trait existing for an abstract method defined in the class.
        // PHP also checks if a trait redefines a method in the class.
        if ($o_class->isTrait() && $method->getDefiningFQSEN()->getFullyQualifiedClassName() === $class->getFQSEN()) {
            // Give up on analyzing if the class **directly** overrides any (abstract OR non-abstract) method defined by the trait
            // TODO: Fix edge cases caused by hack changing FQSEN of private methods
            return;
        }

        // Get the parameters for that method
        $o_parameter_list = $o_method->getParameterList();

        // If we have a parent type defined, map the method's
        // return type and parameter types through it
        $type_option = $class->getParentTypeOption();

        // Map overridden method parameter types through any
        // template type parameters we may have
        if ($type_option->isDefined()) {
            $o_parameter_list =
                \array_map(function (Parameter $parameter) use ($type_option, $code_base) : Parameter {

                    if (!$parameter->getUnionType()->hasTemplateType()) {
                        return $parameter;
                    }

                    $mapped_parameter = clone($parameter);

                    $mapped_parameter->setUnionType(
                        $mapped_parameter->getUnionType()->withTemplateParameterTypeMap(
                            $type_option->get()->getTemplateParameterTypeMap(
                                $code_base
                            )
                        )
                    );

                    return $mapped_parameter;
                }, $o_parameter_list);
        }

        // Map overridden method return type through any template
        // type parameters we may have
        $o_return_union_type = $o_method->getUnionType();
        if ($type_option->isDefined()
            && $o_return_union_type->hasTemplateType()
        ) {
            $o_return_union_type =
                $o_return_union_type->withTemplateParameterTypeMap(
                    $type_option->get()->getTemplateParameterTypeMap(
                        $code_base
                    )
                );
        }

        // Determine if the signatures match up
        $signatures_match = true;

        // Make sure the count of parameters matches
        if ($method->getNumberOfRequiredParameters()
            > $o_method->getNumberOfRequiredParameters()
        ) {
            $signatures_match = false;
        } elseif ($method->getNumberOfParameters()
            < $o_method->getNumberOfParameters()
        ) {
            $signatures_match = false;

        // If parameter counts match, check their types
        } else {
            $real_parameter_list = $method->getRealParameterList();
            $o_real_parameter_list = $o_method->getRealParameterList();

            foreach ($method->getParameterList() as $i => $parameter) {
                if (!isset($o_parameter_list[$i])) {
                    continue;
                }

                $o_parameter = $o_parameter_list[$i];

                // Changing pass by reference is not ok
                // @see https://3v4l.org/Utuo8
                if ($parameter->isPassByReference() != $o_parameter->isPassByReference()) {
                    $signatures_match = false;
                    break;
                }

                // Variadic parameters must match up.
                if ($o_parameter->isVariadic() !== $parameter->isVariadic()) {
                    $signatures_match = false;
                    break;
                }

                // Check for the presence of real types first, warn if the override has a type but the original doesn't.
                $o_real_parameter = $o_real_parameter_list[$i] ?? null;
                $real_parameter = $real_parameter_list[$i] ?? null;
                if ($o_real_parameter !== null && $real_parameter !== null && !$real_parameter->getUnionType()->isEmpty() && $o_real_parameter->getUnionType()->isEmpty()) {
                    $signatures_match = false;
                    break;
                }

                // A stricter type on an overriding method is cool
                if ($parameter->getUnionType()->isEmpty()
                    || $parameter->getUnionType()->isType(MixedType::instance(false))
                ) {
                    continue;
                }

                if ($o_parameter->getUnionType()->isEmpty() || $o_parameter->getUnionType()->isType(MixedType::instance(false))) {
                    continue;
                }

                // In php 7.2, it's ok to have a more relaxed type on an overriding method.
                // In earlier versions it isn't.
                // Because this check is analyzing phpdoc types, so it's fine for php < 7.2 as well. Use `PhanParamSignatureRealMismatch*` for detecting **real** mismatches.
                //
                // https://3v4l.org/XTm3P

                // If we have types, make sure they line up
                //
                // TODO: should we be expanding the types on $o_parameter
                //       via ->asExpandedTypes($code_base)?
                //
                //       @see https://3v4l.org/ke3kp
                if (!$o_parameter->getUnionType()->canCastToUnionType(
                    $parameter->getUnionType()
                )) {
                    $signatures_match = false;
                    break;
                }
            }
        }

        // Return types should be mappable for LSP
        // Note: PHP requires return types to be identical
        // The return type should be stricter than or identical to the overridden union type.
        // E.g. there is no issue if the overridden return type is empty.
        // See https://github.com/phan/phan/issues/1397
        if (!$o_return_union_type->isEmpty()) {
            if (!$method->getUnionType()->asExpandedTypes($code_base)->canCastToUnionType(
                $o_return_union_type
            )) {
                $signatures_match = false;
            }
        }

        // Static or non-static should match
        if ($method->isStatic() != $o_method->isStatic()) {
            if ($o_method->isStatic()) {
                Issue::maybeEmit(
                    $code_base,
                    $method->getContext(),
                    Issue::AccessStaticToNonStatic,
                    $method->getFileRef()->getLineNumberStart(),
                    $o_method->getFQSEN()
                );
            } else {
                Issue::maybeEmit(
                    $code_base,
                    $method->getContext(),
                    Issue::AccessNonStaticToStatic,
                    $method->getFileRef()->getLineNumberStart(),
                    $o_method->getFQSEN()
                );
            }
        }


        if ($o_method->returnsRef() && !$method->returnsRef()) {
            $signatures_match = false;
        }

        if (!$signatures_match) {
            if ($o_method->isPHPInternal()) {
                if (!$method->hasSuppressIssue(Issue::ParamSignatureMismatchInternal)) {
                    Issue::maybeEmit(
                        $code_base,
                        $method->getContext(),
                        Issue::ParamSignatureMismatchInternal,
                        $method->getFileRef()->getLineNumberStart(),
                        $method,
                        $o_method
                    );
                }
            } else {
                if (!$method->hasSuppressIssue(Issue::ParamSignatureMismatch)) {
                    Issue::maybeEmit(
                        $code_base,
                        $method->getContext(),
                        Issue::ParamSignatureMismatch,
                        $method->getFileRef()->getLineNumberStart(),
                        $method,
                        $o_method,
                        $o_method->getFileRef()->getFile(),
                        $o_method->getFileRef()->getLineNumberStart()
                    );
                }
            }
        }

        // Access must be compatible
        if ($o_method->isProtected() && $method->isPrivate()
            || $o_method->isPublic() && !$method->isPublic()
        ) {
            if ($o_method->isPHPInternal()) {
                if (!$method->hasSuppressIssue(Issue::AccessSignatureMismatchInternal)) {
                    Issue::maybeEmit(
                        $code_base,
                        $method->getContext(),
                        Issue::AccessSignatureMismatchInternal,
                        $method->getFileRef()->getLineNumberStart(),
                        $method,
                        $o_method
                    );
                }
            } else {
                if (!$method->hasSuppressIssue(Issue::AccessSignatureMismatch)) {
                    Issue::maybeEmit(
                        $code_base,
                        $method->getContext(),
                        Issue::AccessSignatureMismatch,
                        $method->getFileRef()->getLineNumberStart(),
                        $method,
                        $o_method,
                        $o_method->getFileRef()->getFile(),
                        $o_method->getFileRef()->getLineNumberStart()
                    );
                }
            }
        }
    }

    /**
     * Previously, Phan bases the analysis off of phpdoc.
     * Keeping that around(e.g. to check that string[] is compatible with string[])
     * and also checking the **real**(non-phpdoc) types.
     *
     * @param $code_base
     * @param $method - The overriding method
     * @param $o_method - The overridden method. E.g. if a subclass overrid a base class implementation, then $o_method would be from the base class.
     * @param $o_class the overridden class
     * @return void
     */
    private static function analyzeOverrideRealSignature(
        CodeBase $code_base,
        Method $method,
        Clazz $class,
        Method $o_method,
        Clazz $o_class
    ) {
        if ($o_class->isTrait() && $method->getDefiningFQSEN()->getFullyQualifiedClassName() === $class->getFQSEN()) {
            // Give up on analyzing if the class **directly** overrides any (abstract OR non-abstract) method defined by the trait
            // TODO: Fix edge cases caused by hack changing FQSEN of private methods
            return;
        }

        // Get the parameters for that method
        $o_parameter_list = $o_method->getRealParameterList();

        // Map overridden method return type through any template
        // type parameters we may have
        $o_return_union_type = $o_method->getRealReturnType();

        // Make sure the count of parameters matches
        if ($method->getNumberOfRequiredRealParameters()
            > $o_method->getNumberOfRequiredRealParameters()
        ) {
            self::emitSignatureRealMismatchIssue(
                $code_base,
                $method,
                $o_method,
                Issue::ParamSignatureRealMismatchTooManyRequiredParameters,
                Issue::ParamSignatureRealMismatchTooManyRequiredParametersInternal,
                Issue::ParamSignaturePHPDocMismatchTooManyRequiredParameters,
                $method->getNumberOfRequiredRealParameters(),
                $o_method->getNumberOfRequiredRealParameters()
            );
            return;
        } elseif ($method->getNumberOfRealParameters()
            < $o_method->getNumberOfRealParameters()
        ) {
            self::emitSignatureRealMismatchIssue(
                $code_base,
                $method,
                $o_method,
                Issue::ParamSignatureRealMismatchTooFewParameters,
                Issue::ParamSignatureRealMismatchTooFewParametersInternal,
                Issue::ParamSignaturePHPDocMismatchTooFewParameters,
                $method->getNumberOfRealParameters(),
                $o_method->getNumberOfRealParameters()
            );
            return;
            // If parameter counts match, check their types
        }
        $is_possibly_compatible = true;

        foreach ($method->getRealParameterList() as $i => $parameter) {
            $offset = $i + 1;
            // TODO: check if variadic
            if (!isset($o_parameter_list[$i])) {
                continue;
            }

            // TODO: check that the variadic types match up?
            $o_parameter = $o_parameter_list[$i];

            // Changing pass by reference is not ok
            // @see https://3v4l.org/Utuo8
            if ($parameter->isPassByReference() != $o_parameter->isPassByReference()) {
                $is_reference = $parameter->isPassByReference();
                self::emitSignatureRealMismatchIssue(
                    $code_base,
                    $method,
                    $o_method,
                    ($is_reference ? Issue::ParamSignatureRealMismatchParamIsReference         : Issue::ParamSignatureRealMismatchParamIsNotReference),
                    ($is_reference ? Issue::ParamSignatureRealMismatchParamIsReferenceInternal : Issue::ParamSignatureRealMismatchParamIsNotReferenceInternal),
                    ($is_reference ? Issue::ParamSignaturePHPDocMismatchParamIsReference       : Issue::ParamSignaturePHPDocMismatchParamIsNotReference),
                    $offset
                );
                $is_possibly_compatible = false;
                return;
            }

            // Changing variadic to/from non-variadic is not ok?
            // (Not absolutely sure about that)
            if ($parameter->isVariadic() != $o_parameter->isVariadic()) {
                $is_variadic = $parameter->isVariadic();
                self::emitSignatureRealMismatchIssue(
                    $code_base,
                    $method,
                    $o_method,
                    ($is_variadic ? Issue::ParamSignatureRealMismatchParamVariadic         : Issue::ParamSignatureRealMismatchParamNotVariadic),
                    ($is_variadic ? Issue::ParamSignatureRealMismatchParamVariadicInternal : Issue::ParamSignatureRealMismatchParamNotVariadicInternal),
                    ($is_variadic ? Issue::ParamSignaturePHPDocMismatchParamVariadic       : Issue::ParamSignaturePHPDocMismatchParamNotVariadic),
                    $offset
                );
                $is_possibly_compatible = false;
                return;
            }

            // Either 0 or both of the params must have types for the signatures to be compatible.
            $o_parameter_union_type = $o_parameter->getUnionType();
            $parameter_union_type = $parameter->getUnionType();
            if ($parameter_union_type->isEmpty() != $o_parameter_union_type->isEmpty()) {
                if ($parameter_union_type->isEmpty()) {
                    if (Config::getValue('allow_method_param_type_widening') === false) {
                        $is_possibly_compatible = false;
                        self::emitSignatureRealMismatchIssue(
                            $code_base,
                            $method,
                            $o_method,
                            Issue::ParamSignatureRealMismatchHasNoParamType,
                            Issue::ParamSignatureRealMismatchHasNoParamTypeInternal,
                            Issue::ParamSignaturePHPDocMismatchHasNoParamType,
                            $offset,
                            (string)$o_parameter_union_type
                        );
                    }
                    continue;
                } else {
                    $is_possibly_compatible = false;
                    self::emitSignatureRealMismatchIssue(
                        $code_base,
                        $method,
                        $o_method,
                        Issue::ParamSignatureRealMismatchHasParamType,
                        Issue::ParamSignatureRealMismatchHasParamTypeInternal,
                        Issue::ParamSignaturePHPDocMismatchHasParamType,
                        $offset,
                        (string)$parameter_union_type
                    );
                    continue;
                }
            }

            // If both have types, make sure they are identical.
            // Non-nullable param types can be substituted with the nullable equivalents.
            // E.g. A::foo(?int $x) can override BaseClass::foo(int $x)
            if (!$parameter_union_type->isEmpty()) {
                if (!$o_parameter_union_type->isEqualTo($parameter_union_type) &&
                    !($parameter_union_type->containsNullable() && $o_parameter_union_type->isEqualTo($parameter_union_type->nonNullableClone()))
                ) {
                    // There is one exception to this in php 7.1 - the pseudo-type "iterable" can replace ArrayAccess/array in a subclass
                    // TODO: Traversable and array work, but Iterator doesn't. Check for those specific cases?
                    $is_exception_to_rule = $parameter_union_type->hasIterable() &&
                        $o_parameter_union_type->hasIterable() &&
                        ($parameter_union_type->hasType(IterableType::instance(true)) ||
                         $parameter_union_type->hasType(IterableType::instance(false)) && !$o_parameter_union_type->containsNullable());

                    if (!$is_exception_to_rule) {
                        $is_possibly_compatible = false;
                        self::emitSignatureRealMismatchIssue(
                            $code_base,
                            $method,
                            $o_method,
                            Issue::ParamSignatureRealMismatchParamType,
                            Issue::ParamSignatureRealMismatchParamTypeInternal,
                            Issue::ParamSignaturePHPDocMismatchParamType,
                            $offset,
                            (string)$parameter_union_type,
                            (string)$o_parameter_union_type
                        );
                        continue;
                    }
                }
            }
        }

        $return_union_type = $method->getRealReturnType();
        // If the parent has a return type, then return types should be equal.
        // A non-nullable return type can override a nullable return type of the same type.
        if (!$o_return_union_type->isEmpty()) {
            if (!($o_return_union_type->isEqualTo($return_union_type) || (
                $o_return_union_type->containsNullable() && !($o_return_union_type->nonNullableClone()->isEqualTo($return_union_type)))
                )) {
                $is_possibly_compatible = false;

                self::emitSignatureRealMismatchIssue(
                    $code_base,
                    $method,
                    $o_method,
                    Issue::ParamSignatureRealMismatchReturnType,
                    Issue::ParamSignatureRealMismatchReturnTypeInternal,
                    Issue::ParamSignaturePHPDocMismatchReturnType,
                    (string)$return_union_type,
                    (string)$o_return_union_type
                );
            }
        }
        if ($is_possibly_compatible) {
            if (Config::getValue('inherit_phpdoc_types')) {
                self::inheritPHPDoc($method, $o_method);
            }
        }
    }

    /**
     * Inherit any missing phpdoc types for (at)return and (at)param of $method from $o_method.
     * This is the default behaviour, see https://www.phpdoc.org/docs/latest/guides/inheritance.html
     *
     * @return void
     */
    private static function inheritPHPDoc(
        Method $method,
        Method $o_method
    ) {
        // Get the parameters for that method
        $phpdoc_parameter_list = $method->getParameterList();
        $o_phpdoc_parameter_list = $o_method->getParameterList();
        foreach ($phpdoc_parameter_list as $i => $parameter) {
            $parameter_type = $parameter->getUnionType();
            if (!$parameter_type->isEmpty()) {
                continue;
            }
            $parent_parameter = $o_phpdoc_parameter_list[$i] ?? null;
            if ($parent_parameter) {
                $parent_parameter_type = $parent_parameter->getUnionType();
                if ($parent_parameter_type->isEmpty()) {
                    continue;
                }
                $parameter->setUnionType($parent_parameter_type);
            }
        }

        $phpdoc_return_type = $method->getUnionType();
        if ($phpdoc_return_type->isEmpty()) {
            $parent_phpdoc_return_type = $o_method->getUnionType();
            if (!$parent_phpdoc_return_type->isEmpty()) {
                $method->setUnionType($parent_phpdoc_return_type);
            }
        }
    }

    /**
     * Emit an $issue_type instance corresponding to a potential runtime inheritance warning/error
     *
     * @param CodeBase $code_base
     * @param Method $method
     * @param Method $o_method the overridden method
     * @param string $issue_type the ParamSignatureRealMismatch* (issue type if overriding user-defined method)
     * @param string $internal_issue_type the ParamSignatureRealMismatch* (issue type if overriding internal method)
     * @param int|string ...$args
     * @return void
     */
    private static function emitSignatureRealMismatchIssue(CodeBase $code_base, Method $method, Method $o_method, string $issue_type, string $internal_issue_type, string $phpdoc_issue_type, ...$args)
    {
        if ($method->isFromPHPDoc() || $o_method->isFromPHPDoc()) {
            // TODO: for overriding methods defined in phpdoc, going to need to add issue suppressions from the class phpdoc?
            if ($method->hasSuppressIssue($phpdoc_issue_type)) {
                return;
            }
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                $phpdoc_issue_type,
                $method->getFileRef()->getLineNumberStart(),
                $method->toRealSignatureString(),
                $o_method->toRealSignatureString(),
                ...array_merge($args, [
                    $o_method->getFileRef()->getFile(),
                    $o_method->getFileRef()->getLineNumberStart(),
                ])
            );
        } elseif ($o_method->isPHPInternal()) {
            if ($method->hasSuppressIssue($internal_issue_type)) {
                return;
            }
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                $internal_issue_type,
                $method->getFileRef()->getLineNumberStart(),
                $method->toRealSignatureString(),
                $o_method->toRealSignatureString(),
                ...$args
            );
        } else {
            if ($method->hasSuppressIssue($issue_type)) {
                return;
            }
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                $issue_type,
                $method->getFileRef()->getLineNumberStart(),
                $method->toRealSignatureString(),
                $o_method->toRealSignatureString(),
                ...array_merge($args, [
                    $o_method->getFileRef()->getFile(),
                    $o_method->getFileRef()->getLineNumberStart(),
                ])
            );
        }
    }

    /**
     * @return void
     */
    private static function analyzeParameterTypesDocblockSignaturesMatch(
        CodeBase $code_base,
        FunctionInterface $method
    ) {
        $phpdoc_parameter_map = $method->getPHPDocParameterTypeMap();
        if (count($phpdoc_parameter_map) === 0) {
            // nothing to check.
            return;
        }
        $real_parameter_list = $method->getRealParameterList();
        foreach ($real_parameter_list as $i => $parameter) {
            $real_param_type = $parameter->getNonVariadicUnionType();
            if ($real_param_type->isEmpty()) {
                continue;
            }
            $phpdoc_param_union_type = $phpdoc_parameter_map[$parameter->getName()] ?? null;
            if ($phpdoc_param_union_type && !$phpdoc_param_union_type->isEmpty()) {
                self::tryToAssignPHPDocTypeToParameter($code_base, $method, $i, $parameter, $real_param_type, $phpdoc_param_union_type);
            }
        }
        self::recordOutputReferences($method);
    }

    /**
     * @return void
     */
    private static function tryToAssignPHPDocTypeToParameter(
        CodeBase $code_base,
        FunctionInterface $method,
        int $i,
        Parameter $parameter,
        UnionType $real_param_type,
        UnionType $phpdoc_param_union_type
    ) {
        $context = $method->getContext();
        $resolved_real_param_type = $real_param_type->withStaticResolvedInContext($context);
        $is_exclusively_narrowed = true;
        foreach ($phpdoc_param_union_type->getTypeSet() as $phpdoc_type) {
            // Make sure that the commented type is a narrowed
            // or equivalent form of the syntax-level declared
            // return type.
            if (!$phpdoc_type->isExclusivelyNarrowedFormOrEquivalentTo(
                $resolved_real_param_type,
                $context,
                $code_base
            )
            ) {
                $is_exclusively_narrowed = false;
                if (!$method->hasSuppressIssue(Issue::TypeMismatchDeclaredParam)) {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::TypeMismatchDeclaredParam,
                        $context->getLineNumberStart(),
                        $parameter->getName(),
                        $method->getName(),
                        $phpdoc_type->__toString(),
                        $real_param_type->__toString()
                    );
                }
            }
        }
        // TODO: test edge cases of variadic signatures
        if ($is_exclusively_narrowed && Config::getValue('prefer_narrowed_phpdoc_param_type')) {
            $normalized_phpdoc_param_union_type = self::normalizeNarrowedParamType($phpdoc_param_union_type, $real_param_type);
            if ($normalized_phpdoc_param_union_type) {
                $param_to_modify = $method->getParameterList()[$i] ?? null;
                if ($param_to_modify) {
                    // TODO: Maybe have two different sets of methods for setUnionType and setCallerUnionType, this is easy to mix up for variadics.
                    $param_to_modify->setUnionType($normalized_phpdoc_param_union_type);
                }
            } else {
                // This check isn't urgent to fix, and is specific to nullable casting rules,
                // so use a different issue type.
                if (!$method->hasSuppressIssue(Issue::TypeMismatchDeclaredParamNullable)) {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::TypeMismatchDeclaredParamNullable,
                        $context->getLineNumberStart(),
                        $parameter->getName(),
                        $method->getName(),
                        $phpdoc_param_union_type->__toString(),
                        $real_param_type->__toString()
                    );
                }
            }
        }
    }

    /**
     * @param FunctionInterface $method
     */
    private static function recordOutputReferences(FunctionInterface $method)
    {
        foreach ($method->getOutputReferenceParamNames() as $output_param_name) {
            foreach ($method->getRealParameterList() as $parameter) {
                // TODO: Emit an issue if the (at)phan-output-reference is on a non-reference (at)param?
                if ($parameter->getName() === $output_param_name && $parameter->isPassByReference()) {
                    $parameter->setIsOutputReference();
                }
            }
        }
    }

    /**
     * Forbid these two types of narrowing:
     * 1. Forbid inferring a type of null from "(at)param null $x" for foo(?int $x = null)
     *    The phpdoc is probably nonsense.
     * 2. Forbid inferring a type of `T` from "(at)param T $x" for foo(?T $x = null)
     *    The phpdoc is probably shorthand.
     *
     * Annotations may be added in the future to support this, e.g. "(at)param T $x (at)phan-not-null"
     *
     * @return ?UnionType
     *         - normalized version of $phpdoc_param_union_type (possibly same object)
     *           if Phan should proceed using phpdoc type instead of real types. (Converting T|null to ?T)
     *         - null if the type is an invalid narrowing, and Phan should warn.
     */
    public static function normalizeNarrowedParamType(UnionType $phpdoc_param_union_type, UnionType $real_param_type)
    {
        // "@param null $x" is almost always a mistake. Forbid it for now.
        // But allow "@param T|null $x"
        $has_null = $phpdoc_param_union_type->hasType(NullType::instance(false));
        if ($has_null && $phpdoc_param_union_type->typeCount() === 1) {
            // "@param null"
            return null;
        }
        if (!$real_param_type->containsNullable() || $phpdoc_param_union_type->containsNullable()) {
            // We already validated that the other casts were supported.
            return $phpdoc_param_union_type;
        }
        if (!$has_null) {
            // Attempting to narrow nullable to non-nullable is usually a mistake, currently not supported.
            return null;
        }
        // Create a clone, converting "T|S|null" to "?T|?S"
        return $phpdoc_param_union_type->nullableClone()->withoutType(NullType::instance(false));
    }

    /**
     * Warns if a method is overriding a final method
     * @return void
     */
    private static function warnOverridingFinalMethod(CodeBase $code_base, Method $method, Clazz $class, Method $o_method)
    {
        if ($method->isFromPHPDoc()) {
            // TODO: Track phpdoc methods separately from real methods
            if ($method->hasSuppressIssue(Issue::AccessOverridesFinalMethodPHPDoc) || $class->hasSuppressIssue(Issue::AccessOverridesFinalMethodPHPDoc)) {
                return;
            }
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                Issue::AccessOverridesFinalMethodPHPDoc,
                $method->getFileRef()->getLineNumberStart(),
                $method->getFQSEN(),
                $o_method->getFQSEN(),
                $o_method->getFileRef()->getFile(),
                $o_method->getFileRef()->getLineNumberStart()
            );
        } elseif ($o_method->isPHPInternal()) {
            if ($method->hasSuppressIssue(Issue::AccessOverridesFinalMethodInternal)) {
                return;
            }
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                Issue::AccessOverridesFinalMethodInternal,
                $method->getFileRef()->getLineNumberStart(),
                $method->getFQSEN(),
                $o_method->getFQSEN()
            );
        } else {
            if ($method->hasSuppressIssue(Issue::AccessOverridesFinalMethod)) {
                return;
            }
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                Issue::AccessOverridesFinalMethod,
                $method->getFileRef()->getLineNumberStart(),
                $method->getFQSEN(),
                $o_method->getFQSEN(),
                $o_method->getFileRef()->getFile(),
                $o_method->getFileRef()->getLineNumberStart()
            );
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\UnionTypeVisitor;
use Phan\AST\Visitor\KindVisitorImplementation;
use Phan\BlockAnalysisVisitor;
use Phan\CodeBase;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\AST\ContextNode;
use Phan\Language\Element\Variable;
use Phan\Language\Type;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\CallableType;
use Phan\Language\Type\IterableType;
use Phan\Language\Type\MixedType;
use Phan\Language\Type\ObjectType;
use Phan\Language\Type\StringType;
use Phan\Language\UnionType;
use Phan\Language\UnionTypeBuilder;
use ast\Node;
use ast\flags;
use Closure;

/**
 * TODO: Make $x != null remove FalseType and NullType from $x
 * TODO: Make $x > 0, $x < 0, $x >= 50, etc.  remove FalseType and NullType from $x
 * TODO: if (a || b || c || d) might get really slow, due to creating both ConditionVisitor and NegatedConditionVisitor
 *
 * @phan-file-suppress PhanPluginUnusedClosureArgument
 */
class ConditionVisitor extends KindVisitorImplementation
{
    use ConditionVisitorUtil;

    /**
     * @var Context
     * The context in which the node we're going to be looking
     * at exits.
     */
    protected $context;

    /**
     * @param CodeBase $code_base
     * A code base needs to be passed in because we require
     * it to be initialized before any classes or files are
     * loaded.
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     */
    public function __construct(
        CodeBase $code_base,
        Context $context
    ) {
        $this->code_base = $code_base;
        $this->context = $context;
    }

    /**
     * Default visitor for node kinds that do not have
     * an overriding method
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visit(Node $node) : Context
    {
        $this->checkVariablesDefined($node);
        return $this->context;
    }

    /**
     * Check if variables from within a generic condition are defined.
     * @param Node $node
     * A node to parse
     * @return void
     */
    private function checkVariablesDefined(Node $node)
    {
        while ($node->kind === \ast\AST_UNARY_OP) {
            $node = $node->children['expr'];
            if (!($node instanceof Node)) {
                return;
            }
        }
        // Get the type just to make sure everything
        // is defined.
        UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node,
            true
        );
    }

    /**
     * Check if variables from within a generic condition are defined.
     * @param Node $node
     * A node to parse
     * @return void
     */
    private function checkVariablesDefinedInIsset(Node $node)
    {
        while ($node->kind === \ast\AST_UNARY_OP) {
            $node = $node->children['expr'];
            if (!($node instanceof Node)) {
                return;
            }
        }
        if ($node->kind === \ast\AST_DIM) {
            $this->checkArrayAccessDefined($node);
            return;
        }
        // Get the type just to make sure everything
        // is defined.
        UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node,
            true
        );
    }

    /**
     * Analyzes (isset($x['field']))
     * @return void
     *
     * TODO: Add to NegatedConditionVisitor
     */
    private function checkArrayAccessDefined(Node $node)
    {
        $code_base = $this->code_base;
        $context = $this->context;

        // TODO: Infer that the offset exists after this check
        UnionTypeVisitor::unionTypeFromNode(
            $code_base,
            $context,
            $node->children['dim'],
            true
        );
        // Check the array type to trigger TypeArraySuspicious
        /* $array_type = */
        UnionTypeVisitor::unionTypeFromNode(
            $code_base,
            $context,
            $node->children['expr'],
            true
        );
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitBinaryOp(Node $node) : Context
    {
        $flags = $node->flags;
        if ($flags === flags\BINARY_BOOL_AND) {
            return $this->analyzeShortCircuitingAnd($node->children['left'], $node->children['right']);
        } elseif ($flags === flags\BINARY_BOOL_OR) {
            return $this->analyzeShortCircuitingOr($node->children['left'], $node->children['right']);
        } elseif ($flags === flags\BINARY_IS_IDENTICAL || $flags === flags\BINARY_IS_EQUAL) {
            // TODO: Could be more precise, and preserve 0, [], etc. for `$x == null`
            $this->checkVariablesDefined($node);
            return $this->analyzeAndUpdateToBeIdentical($node->children['left'], $node->children['right']);
        } elseif ($flags === flags\BINARY_IS_NOT_IDENTICAL) {
            $this->checkVariablesDefined($node);
            // TODO: Add a different function for IS_NOT_EQUAL, e.g. analysis of != null should be different from !== null (First would remove FalseType)
            return $this->analyzeAndUpdateToBeNotIdentical($node->children['left'], $node->children['right']);
        } elseif ($flags === flags\BINARY_IS_NOT_EQUAL) {
            return $this->analyzeAndUpdateToBeNotEqual($node->children['left'], $node->children['right']);
        } else {
            $this->checkVariablesDefined($node);
        }
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitAnd(Node $node) : Context
    {
        return $this->analyzeShortCircuitingAnd($node->children['left'], $node->children['right']);
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitOr(Node $node) : Context
    {
        return $this->analyzeShortCircuitingOr($node->children['left'], $node->children['right']);
    }

    /**
     * Helper method
     * @param Node|mixed $left
     * a Node or non-node to parse (possibly an AST literal)
     *
     * @param Node|mixed $right
     * a Node or non-node to parse (possibly an AST literal)
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    private function analyzeShortCircuitingAnd($left, $right) : Context
    {
        // Aside: If left/right is not a node, left/right is a literal such as a number/string, and is either always truthy or always falsey.
        // Inside of this conditional may be dead or redundant code.
        if ($left instanceof Node) {
            $this->context = $this($left);
        }
        // TODO: Warn if !$left
        if ($right instanceof Node) {
            return $this($right);
        }
        return $this->context;
    }

    /**
     * Helper method
     * @param Node|mixed $left
     * a Node or non-node to parse (possibly an AST literal)
     *
     * @param Node|mixed $right
     * a Node or non-node to parse (possibly an AST literal)
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    private function analyzeShortCircuitingOr($left, $right) : Context
    {
        // Aside: If left/right is not a node, left/right is a literal such as a number/string, and is either always truthy or always falsey.
        // Inside of this conditional may be dead or redundant code.
        if (!($left instanceof Node)) {
            if ($left) {
                return $this->context;
            }
            return $this($right);
        }
        if (!($right instanceof Node)) {
            if ($right) {
                return $this->context;
            }
            return $this($left);
        }
        $code_base = $this->code_base;
        $context = $this->context;
        $left_false_context = (new NegatedConditionVisitor($code_base, $context))->__invoke($left);
        $left_true_context = (new ConditionVisitor($code_base, $context))->__invoke($left);
        // We analyze the right hand side of `cond($x) || cond2($x)` as if `cond($x)` was false.
        $right_true_context = (new ConditionVisitor($code_base, $left_false_context))->__invoke($right);
        // When the ConditionVisitor is true, at least one of the left or right contexts must be true.
        return (new ContextMergeVisitor($context, [$left_true_context, $right_true_context]))->combineChildContextList();
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitUnaryOp(Node $node) : Context
    {
        $expr_node = $node->children['expr'];
        $flags = $node->flags;
        if ($flags !== flags\UNARY_BOOL_NOT) {
            // TODO: Emit dead code issue for non-nodes
            if ($expr_node instanceof Node) {
                if ($flags === flags\UNARY_SILENCE) {
                    return $this->__invoke($expr_node);
                }
                $this->checkVariablesDefined($expr_node);
            }
            return $this->context;
        }
        // TODO: Emit dead code issue for non-nodes
        if ($expr_node instanceof Node) {
            return (new NegatedConditionVisitor($this->code_base, $this->context))->__invoke($expr_node);
        }
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitCoalesce(Node $node) : Context
    {
        $this->checkVariablesDefined($node);
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitIsset(Node $node) : Context
    {
        $context = $this->context;
        $var_node = $node->children['var'];
        if (!($var_node instanceof Node)) {
            return $context;
        }
        if (($var_node->kind ?? null) !== \ast\AST_VAR) {
            return $this->checkComplexIsset($var_node);
        }

        $var_name = $var_node->children['name'];
        if (!\is_string($var_name)) {
            $this->checkVariablesDefinedInIsset($var_node);
            return $context;
        }
        if (!$context->getScope()->hasVariableWithName($var_name)) {
            // Support analyzing cases such as `if (isset($x)) { use($x); }`, or `assert(isset($x))`
            $context->setScope($context->getScope()->withVariable(new Variable(
                $context->withLineNumberStart($var_node->lineno ?? 0),
                $var_name,
                UnionType::empty(),
                $var_node->flags
            )));
            return $context;
        }
        return $this->removeNullFromVariable($var_node, $context, true);
    }

    private function checkComplexIsset(Node $var_node) : Context
    {
        // TODO: isset($obj->prop['offset']) should imply $obj is not null (removeNullFromVariable)
        $context = $this->context;
        if ($var_node->kind === \ast\AST_DIM) {
            $expr_node = $var_node;
            do {
                $parent_node = $expr_node;
                $expr_node = $expr_node->children['expr'];
                if (!($expr_node instanceof Node)) {
                    return $context;
                }
            } while ($expr_node->kind === \ast\AST_DIM);

            if ($expr_node->kind === \ast\AST_VAR) {
                $var_name = $expr_node->children['name'];
                if (!\is_string($var_name)) {
                    return $context;
                }
                if (!$context->getScope()->hasVariableWithName($var_name)) {
                    // Support analyzing cases such as `if (isset($x['key'])) { use($x); }`, or `assert(isset($x['key']))`
                    $context->setScope($context->getScope()->withVariable(new Variable(
                        $context->withLineNumberStart($expr_node->lineno ?? 0),
                        $var_name,
                        ArrayType::instance(false)->asUnionType(),
                        $expr_node->flags
                    )));
                    return $context;
                }
                $context = $this->removeNullFromVariable($expr_node, $context, true);

                $variable = $context->getScope()->getVariableByName($var_name);
                $var_node_union_type = $variable->getUnionType();

                if ($var_node_union_type->hasTopLevelArrayShapeTypeInstances()) {
                    $context = $this->withSetArrayShapeTypes($variable, $parent_node->children['dim'], $context, true);
                    $this->context = $context;
                }
            }
        }
        return $context;
    }

    /**
     * @param Variable $variable the variable being modified by inferences from isset or array_key_exists
     * @param Node|string|float|int|bool $dim_node represents the dimension being accessed. (E.g. can be a literal or an AST_CONST, etc.
     * @param Context $context the context with inferences made prior to this condition
     *
     * @param bool $non_nullable if an offset is created, will it be non-nullable?
     */
    private function withSetArrayShapeTypes(Variable $variable, $dim_node, Context $context, bool $non_nullable) : Context
    {
        $dim_value = $dim_node instanceof Node ? (new ContextNode($this->code_base, $this->context, $dim_node))->getEquivalentPHPScalarValue() : $dim_node;
        // TODO: detect and warn about null
        if (!\is_scalar($dim_value)) {
            return $context;
        }

        $union_type = $variable->getUnionType();
        $dim_union_type = UnionTypeVisitor::resolveArrayShapeElementTypesForOffset($union_type, $dim_value);
        if (!$dim_union_type) {
            // There are other types, this dimension does not exist yet
            if (!$union_type->hasTopLevelArrayShapeTypeInstances()) {
                return $context;
            }
            $new_union_type = ArrayType::combineArrayShapeTypesWithField($union_type, $dim_value, MixedType::instance(false)->asUnionType());
            $variable = clone($variable);
            $variable->setUnionType($new_union_type);
            return $context->withScopeVariable(
                $variable
            );
            // TODO finish
        } elseif ($dim_union_type->containsNullableOrUndefined()) {
            if (!$non_nullable) {
                // The offset in question already exists in the array shape type, and we won't be changing it.
                // (E.g. array_key_exists('key', $x) where $x is array{key:?int,other:string})
                return $context;
            }

            $variable = clone($variable);

            $variable->setUnionType(
                ArrayType::combineArrayShapeTypesWithField($union_type, $dim_value, $dim_union_type->nonNullableClone())
            );

            // Overwrite the variable with its new type in this
            // scope without overwriting other scopes
            return $context->withScopeVariable(
                $variable
            );
            // TODO finish
        }
        return $context;
    }

    /**
     * @param Node $node
     * A node to parse, with kind \ast\AST_VAR
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitVar(Node $node) : Context
    {
        $this->checkVariablesDefined($node);
        return $this->removeFalseyFromVariable($node, $this->context, false);
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitInstanceof(Node $node) : Context
    {
        //$this->checkVariablesDefined($node);
        // Only look at things of the form
        // `$variable instanceof ClassName`
        $expr_node = $node->children['expr'];
        if (!($expr_node instanceof Node) || $expr_node->kind !== \ast\AST_VAR) {
            return $this->context;
        }

        $context = $this->context;

        try {
            // Get the variable we're operating on
            $variable = $this->getVariableFromScope($expr_node, $context);
            if (\is_null($variable)) {
                return $context;
            }

            // Get the type that we're checking it against
            $class_node = $node->children['class'];
            $type = UnionTypeVisitor::unionTypeFromNode(
                $this->code_base,
                $this->context,
                $class_node
            );
            // Make a copy of the variable
            $variable = clone($variable);
            $object_types = $type->objectTypes();
            if (!$object_types->isEmpty()) {
                // See https://secure.php.net/instanceof -

                // Add the type to the variable
                // $variable->getUnionType()->addUnionType($type);
                $variable->setUnionType($object_types);
            } else {
                if ($class_node->kind !== \ast\AST_NAME &&
                        !$type->canCastToUnionType(StringType::instance(false)->asUnionType())) {
                    Issue::maybeEmit(
                        $this->code_base,
                        $context,
                        Issue::TypeInvalidInstanceof,
                        $context->getLineNumberStart(),
                        (string)$type
                    );
                }
                self::analyzeIsObjectAssertion($variable);
            }
            // Overwrite the variable with its new type
            $context = $context->withScopeVariable(
                $variable
            );
        } catch (IssueException $exception) {
            Issue::maybeEmitInstance($this->code_base, $context, $exception->getIssueInstance());
        } catch (\Exception $exception) {
            // Swallow it
        }

        return $context;
    }

    /**
     * @param Variable $variable (Node argument in a call to is_object)
     * @return void
     */
    private static function analyzeIsObjectAssertion(Variable $variable)
    {
        // Change the type to match is_object relationship
        // If we already have the `object` type or generic object types, then keep those
        // (E.g. T|false becomes T, T[]|iterable|null becomes Traversable, object|bool becomes object)
        $new_type_builder = new UnionTypeBuilder();
        foreach ($variable->getUnionType()->getTypeSet() as $type) {
            if ($type->isObject()) {
                $new_type_builder->addType($type->withIsNullable(false));
                continue;
            }
            if (\get_class($type) === IterableType::class) {
                // An iterable is either an array or a Traversable.
                $new_type_builder->addType(Type::fromFullyQualifiedString('\Traversable'));
            }
        }
        $variable->setUnionType($new_type_builder->isEmpty() ? ObjectType::instance(false)->asUnionType() : $new_type_builder->getUnionType());
    }

    /**
     * This function is called once, and returns closures to modify the types of variables.
     *
     * This contains Phan's logic for inferring the resulting union types of variables, e.g. in \is_array($x).
     *
     * @return array<string,Closure> - The closures to call for a given global function
     * @phan-return array<string,Closure(Variable,array):void>
     */
    private static function initTypeModifyingClosuresForVisitCall() : array
    {
        $make_basic_assertion_callback = static function (string $union_type_string) : Closure {
            $type = UnionType::fromFullyQualifiedString(
                $union_type_string
            );

            /** @return void */
            return static function (Variable $variable, array $args) use ($type) {
                // Otherwise, overwrite the type for any simple
                // primitive types.
                $variable->setUnionType($type);
            };
        };

        /** @return void */
        $array_type = ArrayType::instance(false);
        $array_callback = static function (Variable $variable, array $args) use ($array_type) {
            // Change the type to match the is_a relationship
            // If we already have generic array types, then keep those
            // (E.g. T[]|false becomes T[], ?array|null becomes array
            $new_type_builder = new UnionTypeBuilder();
            foreach ($variable->getUnionType()->getTypeSet() as $type) {
                if ($type->isGenericArray()) {
                    $new_type_builder->addType($type->withIsNullable(false));
                    continue;
                }
                if (\get_class($type) === IterableType::class) {
                    // An iterable is either an array or a Traversable.
                    $new_type_builder->addType($array_type);
                }
            }
            $variable->setUnionType($new_type_builder->isEmpty() ? $array_type->asUnionType() : $new_type_builder->getUnionType());
        };

        /** @return void */
        $object_callback = static function (Variable $variable, array $args) {
            self::analyzeIsObjectAssertion($variable);
        };
        /** @return void */
        $is_a_callback = function (Variable $variable, array $args) use ($object_callback) {
            $class_name = $args[1] ?? null;
            if (!\is_string($class_name)) {
                // Limit the types of $variable to an object if we can't infer the class name.
                $object_callback($variable, $args);
                return;
            }
            $class_name = ltrim($class_name, '\\');
            if (empty($class_name)) {
                return;
            }
            // TODO: validate argument
            $class_name = '\\' . $class_name;
            $class_type = Type::fromStringInContext($class_name, new Context(), Type::FROM_NODE);
            $variable->setUnionType($class_type->asUnionType());
        };

        /** @return void */
        $scalar_callback = static function (Variable $variable, array $args) {
            // Change the type to match the is_a relationship
            // If we already have possible scalar types, then keep those
            // (E.g. T|false becomes bool, T becomes int|float|bool|string|null)
            $newType = $variable->getUnionType()->scalarTypes();
            if ($newType->containsNullable()) {
                $newType = $newType->nonNullableClone();
            }
            if ($newType->isEmpty()) {
                // If there are no inferred types, or the only type we saw was 'null',
                // assume there this can be any possible scalar.
                // (Excludes `resource`, which is technically a scalar)
                $newType = UnionType::fromFullyQualifiedString('int|float|bool|string');
            }
            $variable->setUnionType($newType);
        };
        $callable_callback = static function (Variable $variable, array $args) {
            // Change the type to match the is_a relationship
            // If we already have possible callable types, then keep those
            // (E.g. Closure|false becomes Closure)
            $new_type = $variable->getUnionType()->callableTypes();
            if ($new_type->isEmpty()) {
                // If there are no inferred types, or the only type we saw was 'null',
                // assume there this can be any possible scalar.
                // (Excludes `resource`, which is technically a scalar)
                $new_type = CallableType::instance(false)->asUnionType();
            } elseif ($new_type->containsNullable()) {
                $new_type = $new_type->nonNullableClone();
            }
            $variable->setUnionType($new_type);
        };

        $float_callback = $make_basic_assertion_callback('float');
        $int_callback = $make_basic_assertion_callback('int');
        $null_callback = $make_basic_assertion_callback('null');
        // Note: isset() is handled in visitIsset()

        return [
            'is_a' => $is_a_callback,
            'is_array' => $array_callback,
            'is_bool' => $make_basic_assertion_callback('bool'),
            'is_callable' => $callable_callback,
            'is_double' => $float_callback,
            'is_float' => $float_callback,
            'is_int' => $int_callback,
            'is_integer' => $int_callback,
            'is_iterable' => $make_basic_assertion_callback('iterable'),  // TODO: Could keep basic array types and classes extending iterable
            'is_long' => $int_callback,
            'is_null' => $null_callback,
            'is_numeric' => $make_basic_assertion_callback('string|int|float'),
            'is_object' => $object_callback,
            'is_real' => $float_callback,
            'is_resource' => $make_basic_assertion_callback('resource'),
            'is_scalar' => $scalar_callback,
            'is_string' => $make_basic_assertion_callback('string'),
            'empty' => $null_callback,
        ];
    }

    /**
     * Look at elements of the form `is_array($v)` and modify
     * the type of the variable.
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitCall(Node $node) : Context
    {
        $raw_function_name = self::getFunctionName($node);
        if (!\is_string($raw_function_name)) {
            return $this->context;
        }
        $args = $node->children['args']->children;
        $first_arg = $args[0] ?? null;

        // Only look at things of the form
        // `\is_string($variable)`
        if (!($first_arg instanceof Node && $first_arg->kind === \ast\AST_VAR)) {
            if (\strcasecmp($raw_function_name, 'array_key_exists') === 0 && \count($args) === 2) {
                return $this->analyzeArrayKeyExists($args);
            }
            return $this->context;
        }

        if (\count($args) !== 1) {
            if (!(\strcasecmp($raw_function_name, 'is_a') === 0 && \count($args) === 2)) {
                return $this->context;
            }
        }
        // Translate the function name into the UnionType it asserts
        static $map = null;

        if ($map === null) {
             $map = self::initTypeModifyingClosuresForVisitCall();
        }

        $function_name = \strtolower($raw_function_name);
        $type_modification_callback = $map[$function_name] ?? null;
        if ($type_modification_callback === null) {
            return $this->context;
        }

        $context = $this->context;

        try {
            // Get the variable we're operating on
            $variable = $this->getVariableFromScope($first_arg, $context);

            if (\is_null($variable)) {
                return $context;
            }

            // Make a copy of the variable
            $variable = clone($variable);

            // Modify the types of that variable.
            $type_modification_callback($variable, $args);

            // Overwrite the variable with its new type in this
            // scope without overwriting other scopes
            $context = $context->withScopeVariable(
                $variable
            );
        } catch (IssueException $exception) {
            Issue::maybeEmitInstance($this->code_base, $context, $exception->getIssueInstance());
        } catch (\Exception $exception) {
            // Swallow it (E.g. IssueException for undefined variable)
        }

        return $context;
    }

    private function analyzeArrayKeyExists(array $args) : Context
    {
        $context = $this->context;
        if (\count($args) !== 2) {
            return $context;
        }
        $var_node = $args[1];
        if (($var_node->kind ?? null) !== \ast\AST_VAR) {
            return $context;
        }
        $var_name = $var_node->children['name'];
        if (!\is_string($var_name)) {
            return $context;
        }
        if (!$context->getScope()->hasVariableWithName($var_name)) {
            return $context;
        }
        $context = $this->removeNullFromVariable($var_node, $context, true);
        $variable = $context->getScope()->getVariableByName($var_name);

        if ($variable->getUnionType()->hasTopLevelArrayShapeTypeInstances()) {
            $context = $this->withSetArrayShapeTypes($variable, $args[0], $context, false);
            $this->context = $context;
        }
        return $context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitEmpty(Node $node) : Context
    {
        $var_node = $node->children['expr'];
        if (!($var_node instanceof Node)) {
            return $this->context;
        }
        // Should always be a node for valid ASTs, tolerant-php-parser may produce invalid nodes
        if ($var_node->kind === \ast\AST_VAR) {
            // Don't emit notices for if (empty($x)) {}, etc.
            return $this->removeTruthyFromVariable($var_node, $this->context, true);
        }
        $this->checkVariablesDefinedInIsset($var_node);
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitExprList(Node $node) : Context
    {
        $children = $node->children;
        $count = \count($children);
        if ($count > 1) {
            foreach ($children as $sub_node) {
                --$count;
                if ($count > 0 && $sub_node instanceof Node) {
                    $this->checkVariablesDefined($sub_node);
                }
            }
        }
        // Only analyze the last expression in the expression list for conditions.
        $last_expression = \end($node->children);
        if ($last_expression instanceof Node) {
            return $this($last_expression);
        } else {
            // TODO: emit no-op warning
            return $this->context;
        }
    }

    /**
     * Useful for analyzing `if ($x = foo() && $x->method())`
     * TODO: Remove empty/false/null types from $x
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitAssign(Node $node) : Context
    {
        return (new BlockAnalysisVisitor($this->code_base, $this->context))->visitAssign($node);
    }

    /**
     * Useful for analyzing `if ($x = foo() && $x->method())`
     * TODO: Remove empty/false/null types from $x
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitAssignRef(Node $node) : Context
    {
        return (new BlockAnalysisVisitor($this->code_base, $this->context))->visitAssignRef($node);
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\ContextNode;
use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\CodeBaseException;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Method;
use Phan\Language\Element\Parameter;
use Phan\Language\Element\Variable;
use Phan\Language\Type;
use Phan\Language\Type\FalseType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\StringType;
use Phan\Language\UnionType;
use Phan\PluginV2\StopParamAnalysisException;
use ast\Node;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class ArgumentType
{

    /**
     * @param FunctionInterface $method
     * The function/method we're analyzing arguments for
     *
     * @param Node $node
     * The node holding the method call we're looking at
     *
     * @param Context $context
     * The context in which we see the call
     *
     * @param CodeBase $code_base
     * The global code base
     *
     * @return void
     */
    public static function analyze(
        FunctionInterface $method,
        Node $node,
        Context $context,
        CodeBase $code_base
    ) {
        self::checkIsDeprecatedOrInternal($code_base, $context, $method);
        if ($method->hasFunctionCallAnalyzer()) {
            try {
                $method->analyzeFunctionCall($code_base, $context->withLineNumberStart($node->lineno ?? 0), $node->children['args']->children);
            } catch (StopParamAnalysisException $e) {
                return;
            }
        }

        // Emit an issue if this is an externally accessed internal method
        $arglist = $node->children['args'];
        $argcount = \count($arglist->children);

        // Make sure we have enough arguments
        if ($argcount < $method->getNumberOfRequiredParameters() && !self::isUnpack($arglist->children)) {
            $alternate_found = false;
            foreach ($method->alternateGenerator($code_base) as $alternate_method) {
                $alternate_found = $alternate_found || (
                    $argcount >=
                    $alternate_method->getNumberOfRequiredParameters()
                );
            }

            if (!$alternate_found) {
                if ($method->isPHPInternal()) {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::ParamTooFewInternal,
                        $node->lineno ?? 0,
                        $argcount,
                        $method->getRepresentationForIssue(),
                        $method->getNumberOfRequiredParameters()
                    );
                } else {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::ParamTooFew,
                        $node->lineno ?? 0,
                        $argcount,
                        $method->getRepresentationForIssue(),
                        $method->getNumberOfRequiredParameters(),
                        $method->getFileRef()->getFile(),
                        $method->getFileRef()->getLineNumberStart()
                    );
                }
            }
        }

        // Make sure we don't have too many arguments
        if ($argcount > $method->getNumberOfParameters() && !self::isVarargs($code_base, $method)) {
            $alternate_found = false;
            foreach ($method->alternateGenerator($code_base) as $alternate_method) {
                if ($argcount <= $alternate_method->getNumberOfParameters()) {
                    $alternate_found = true;
                    break;
                }
            }

            if (!$alternate_found) {
                $max = $method->getNumberOfParameters();
                if ($method->isPHPInternal()) {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::ParamTooManyInternal,
                        $node->lineno ?? 0,
                        $argcount,
                        $method->getRepresentationForIssue(),
                        $max
                    );
                } else {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::ParamTooMany,
                        $node->lineno ?? 0,
                        $argcount,
                        $method->getRepresentationForIssue(),
                        $max,
                        $method->getFileRef()->getFile(),
                        $method->getFileRef()->getLineNumberStart()
                    );
                }
            }
        }

        // Check the parameter types
        self::analyzeParameterList(
            $code_base,
            $method,
            $arglist,
            $context
        );
    }

    /**
     * @return void
     */
    private static function checkIsDeprecatedOrInternal(CodeBase $code_base, Context $context, FunctionInterface $method)
    {
        // Special common cases where we want slightly
        // better multi-signature error messages
        if ($method->isPHPInternal()) {
            // Emit an error if this internal method is marked as deprecated
            if ($method->isDeprecated()) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::DeprecatedFunctionInternal,
                    $context->getLineNumberStart(),
                    $method->getRepresentationForIssue()
                );
            }
        } else {
            // Emit an error if this user-defined method is marked as deprecated
            if ($method->isDeprecated()) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::DeprecatedFunction,
                    $context->getLineNumberStart(),
                    $method->getRepresentationForIssue(),
                    $method->getFileRef()->getFile(),
                    $method->getFileRef()->getLineNumberStart()
                );
            }
        }

        // Emit an issue if this is an externally accessed internal method
        if ($method->isNSInternal($code_base)
            && !$method->isNSInternalAccessFromContext(
                $code_base,
                $context
            )
        ) {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::AccessMethodInternal,
                $context->getLineNumberStart(),
                $method->getRepresentationForIssue(),
                $method->getElementNamespace() ?: '\\',
                $method->getFileRef()->getFile(),
                $method->getFileRef()->getLineNumberStart(),
                ($context->getNamespace()) ?: '\\'
            );
        }
    }

    private static function isVarargs(CodeBase $code_base, FunctionInterface $method) : bool
    {
        foreach ($method->alternateGenerator($code_base) as $alternate_method) {
            foreach ($alternate_method->getParameterList() as $parameter) {
                if ($parameter->isVariadic()) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Figure out if any of the arguments are a call to unpack()
     * @param array $children
     */
    private static function isUnpack(array $children) : bool
    {
        foreach ($children as $child) {
            if ($child instanceof Node) {
                if ($child->kind === \ast\AST_UNPACK) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @param FunctionInterface $method
     * The function/method we're analyzing arguments for
     *
     * @param array<int,Node|string|int|float> $arg_nodes $node
     * The node holding the arguments of the call we're looking at
     *
     * @param Context $context
     * The context in which we see the call
     *
     * @param CodeBase $code_base
     * The global code base
     *
     * @param ?\Closure $get_argument_type (Node|string|int $node, int $i) -> UnionType
     * Fetches the types of individual arguments.
     */
    public static function analyzeForCallback(
        FunctionInterface $method,
        array $arg_nodes,
        Context $context,
        CodeBase $code_base,
        \Closure $get_argument_type
    ) {
        // Special common cases where we want slightly
        // better multi-signature error messages
        self::checkIsDeprecatedOrInternal($code_base, $context, $method);
        // TODO: analyzeInternalArgumentType

        $argcount = \count($arg_nodes);

        // Make sure we have enough arguments
        if ($argcount < $method->getNumberOfRequiredParameters() && !self::isUnpack($arg_nodes)) {
            $alternate_found = false;
            foreach ($method->alternateGenerator($code_base) as $alternate_method) {
                $alternate_found = $alternate_found || (
                    $argcount >=
                    $alternate_method->getNumberOfRequiredParameters()
                );
            }

            if (!$alternate_found) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::ParamTooFewCallable,
                    $context->getLineNumberStart(),
                    $argcount,
                    $method->getRepresentationForIssue(),
                    $method->getNumberOfRequiredParameters(),
                    $method->getFileRef()->getFile(),
                    $method->getFileRef()->getLineNumberStart()
                );
            }
        }

        // Make sure we don't have too many arguments
        if ($argcount > $method->getNumberOfParameters() && !self::isVarargs($code_base, $method)) {
            $alternate_found = false;
            foreach ($method->alternateGenerator($code_base) as $alternate_method) {
                if ($argcount <= $alternate_method->getNumberOfParameters()) {
                    $alternate_found = true;
                    break;
                }
            }

            if (!$alternate_found) {
                $max = $method->getNumberOfParameters();
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::ParamTooManyCallable,
                    $context->getLineNumberStart(),
                    $argcount,
                    $method->getRepresentationForIssue(),
                    $max,
                    $method->getFileRef()->getFile(),
                    $method->getFileRef()->getLineNumberStart()
                );
            }
        }

        // Check the parameter types
        self::analyzeParameterListForCallback(
            $code_base,
            $method,
            $arg_nodes,
            $context,
            $get_argument_type
        );
    }

    /**
     * @param CodeBase $code_base
     * The global code base
     *
     * @param FunctionInterface $method
     * The method we're analyzing arguments for
     *
     * @param array<int,Node|string|int|float> $arg_nodes $node
     * The node holding the arguments of the call we're looking at
     *
     * @param Context $context
     * The context in which we see the call
     *
     * @param \Closure $get_argument_type (Node|string|int $node, int $i) -> UnionType
     *
     * @return void
     */
    private static function analyzeParameterListForCallback(
        CodeBase $code_base,
        FunctionInterface $method,
        array $arg_nodes,
        Context $context,
        \Closure $get_argument_type
    ) {
        // There's nothing reasonable we can do here
        if ($method instanceof Method) {
            if ($method->getIsMagicCall() || $method->getIsMagicCallStatic()) {
                return;
            }
        }

        foreach ($arg_nodes as $i => $argument) {
            // Get the parameter associated with this argument
            $parameter = $method->getParameterForCaller($i);

            // This issue should be caught elsewhere
            if (!$parameter) {
                continue;
            }

            // TODO: Warnings about call-by-reference are different for array_map, etc.

            // Get the type of the argument. We'll check it against
            // the parameter in a moment
            $argument_type = $get_argument_type($argument, $i);
            self::analyzeParameter($code_base, $context, $method, $argument_type, $argument->lineno ?? 0, $i);
        }
    }


    /**
     * @param CodeBase $code_base
     * The global code base
     *
     * @param FunctionInterface $method
     * The method we're analyzing arguments for
     *
     * @param Node $node
     * The node holding the arguments of the function/method call we're looking at
     *
     * @param Context $context
     * The context in which we see the call
     *
     * @return void
     */
    private static function analyzeParameterList(
        CodeBase $code_base,
        FunctionInterface $method,
        Node $node,
        Context $context
    ) {
        // There's nothing reasonable we can do here
        if ($method instanceof Method) {
            if ($method->getIsMagicCall() || $method->getIsMagicCallStatic()) {
                return;
            }
        }

        foreach ($node->children as $i => $argument) {
            \assert(\is_int($i));

            // Get the parameter associated with this argument
            $parameter = $method->getParameterForCaller($i);

            // This issue should be caught elsewhere
            if (!$parameter) {
                continue;
            }

            $argument_kind = $argument->kind ?? 0;

            // If this is a pass-by-reference parameter, make sure
            // we're passing an allowable argument
            if ($parameter->isPassByReference()) {
                if ((!$argument instanceof \ast\Node)
                    || ($argument_kind !== \ast\AST_VAR
                        && $argument_kind !== \ast\AST_DIM
                        && $argument_kind !== \ast\AST_PROP
                        && $argument_kind !== \ast\AST_STATIC_PROP
                    )
                ) {
                    $is_possible_reference = self::isFunctionReturningReference($code_base, $context, $argument);

                    if (!$is_possible_reference) {
                        Issue::maybeEmit(
                            $code_base,
                            $context,
                            Issue::TypeNonVarPassByRef,
                            $node->lineno ?? 0,
                            ($i+1),
                            $method->getRepresentationForIssue()
                        );
                    }
                } else {
                    $variable_name = (new ContextNode(
                        $code_base,
                        $context,
                        $argument
                    ))->getVariableName();

                    if (Type::isSelfTypeString($variable_name)
                        && !$context->isInClassScope()
                        && ($argument_kind === \ast\AST_STATIC_PROP || $argument_kind === \ast\AST_PROP)
                    ) {
                        Issue::maybeEmit(
                            $code_base,
                            $context,
                            Issue::ContextNotObject,
                            $node->lineno ?? 0,
                            "$variable_name"
                        );
                    }
                }
            }

            // Get the type of the argument. We'll check it against
            // the parameter in a moment
            $argument_type = UnionTypeVisitor::unionTypeFromNode(
                $code_base,
                $context,
                $argument,
                true
            );
            self::analyzeParameter($code_base, $context, $method, $argument_type, $argument->lineno ?? $node->lineno ?? 0, $i);
        }
    }

    /**
     * @param CodeBase $code_base
     * @param Context $context
     * @param FunctionInterface $method
     * @param UnionType $argument_type
     * @param int $lineno
     * @return void
     */
    public static function analyzeParameter(CodeBase $code_base, Context $context, FunctionInterface $method, UnionType $argument_type, int $lineno, int $i)
    {
        // Expand it to include all parent types up the chain
        $argument_type_expanded =
            $argument_type->asExpandedTypes($code_base);

        // Check the method to see if it has the correct
        // parameter types. If not, keep hunting through
        // alternates of the method until we find one that
        // takes the correct types
        $alternate_parameter = null;

        foreach ($method->alternateGenerator($code_base) as $alternate_method) {
            // Get the parameter associated with this argument
            $candidate_alternate_parameter = $alternate_method->getParameterForCaller($i);
            if (\is_null($candidate_alternate_parameter)) {
                continue;
            }

            $alternate_parameter = $candidate_alternate_parameter;
            \assert($alternate_parameter instanceof Variable);

            // See if the argument can be cast to the
            // parameter
            if ($argument_type_expanded->canCastToUnionType(
                $alternate_parameter->getNonVariadicUnionType()
            )) {
                if (Config::get_strict_param_checking() && $argument_type->typeCount() > 1) {
                    self::analyzeParameterStrict($code_base, $context, $method, $argument_type, $alternate_parameter, $lineno, $i);
                }
                return;
            }
        }

        if (!($alternate_parameter instanceof Parameter)) {
            return;  // skip type check - is this possible?
        }

        if ($alternate_parameter->isPassByReference() && $alternate_parameter->getReferenceType() === Parameter::REFERENCE_WRITE_ONLY) {
            return;
        }

        $parameter_type = $alternate_parameter->getNonVariadicUnionType();

        if ($parameter_type->hasTemplateType()) {
            // Don't worry about template types
            return;
        }

        if ($method->isPHPInternal()) {
            // If we are not in strict mode and we accept a string parameter
            // and the argument we are passing has a __toString method then it is ok
            if (!$context->getIsStrictTypes() && $parameter_type->hasType(StringType::instance(false))) {
                try {
                    foreach ($argument_type_expanded->asClassList($code_base, $context) as $clazz) {
                        if ($clazz->hasMethodWithName($code_base, "__toString")) {
                            return;
                        }
                    }
                } catch (CodeBaseException $e) {
                    // Swallow "Cannot find class", go on to emit issue
                }
            }
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::TypeMismatchArgumentInternal,
                $lineno,
                ($i+1),
                $alternate_parameter->getName(),
                $argument_type_expanded,
                $method->getRepresentationForIssue(),
                (string)$parameter_type
            );
            return;
        }
        Issue::maybeEmit(
            $code_base,
            $context,
            Issue::TypeMismatchArgument,
            $lineno,
            ($i+1),
            $alternate_parameter->getName(),
            $argument_type_expanded,
            $method->getRepresentationForIssue(),
            (string)$parameter_type,
            $method->getFileRef()->getFile(),
            $method->getFileRef()->getLineNumberStart()
        );
    }

    private static function analyzeParameterStrict(CodeBase $code_base, Context $context, FunctionInterface $method, UnionType $argument_type, Variable $alternate_parameter, int $lineno, int $i)
    {
        $type_set = $argument_type->getTypeSet();
        \assert(\count($type_set) >= 2);

        $parameter_type = $alternate_parameter->getNonVariadicUnionType();

        $mismatch_type_set = UnionType::empty();
        $mismatch_expanded_types = null;

        // For the strict
        foreach ($type_set as $type) {
            // Expand it to include all parent types up the chain
            $individual_type_expanded = $type->asExpandedTypes($code_base);

            // See if the argument can be cast to the
            // parameter
            if (!$individual_type_expanded->canCastToUnionType(
                $parameter_type
            )) {
                if ($method->isPHPInternal()) {
                    // If we are not in strict mode and we accept a string parameter
                    // and the argument we are passing has a __toString method then it is ok
                    if (!$context->getIsStrictTypes() && $parameter_type->hasType(StringType::instance(false))) {
                        if ($individual_type_expanded->hasClassWithToStringMethod($code_base, $context)) {
                            continue;  // don't warn about $type
                        }
                    }
                }
                $mismatch_type_set = $mismatch_type_set->withType($type);
                if ($mismatch_expanded_types === null) {
                    // Warn about the first type
                    $mismatch_expanded_types = $individual_type_expanded;
                }
            }
        }


        if ($mismatch_expanded_types === null) {
            // No mismatches
            return;
        }

        if ($method->isPHPInternal()) {
            Issue::maybeEmit(
                $code_base,
                $context,
                self::getStrictIssueType($mismatch_type_set, true),
                $lineno,
                ($i+1),
                $alternate_parameter->getName(),
                $argument_type,
                $method->getRepresentationForIssue(),
                (string)$parameter_type,
                $mismatch_expanded_types
            );
            return;
        }
        Issue::maybeEmit(
            $code_base,
            $context,
            self::getStrictIssueType($mismatch_type_set, false),
            $lineno,
            ($i+1),
            $alternate_parameter->getName(),
            $argument_type,
            $method->getRepresentationForIssue(),
            (string)$parameter_type,
            $mismatch_expanded_types,
            $method->getFileRef()->getFile(),
            $method->getFileRef()->getLineNumberStart()
        );
    }

    private static function getStrictIssueType(UnionType $union_type, bool $is_internal) : string
    {
        if ($union_type->typeCount() === 1) {
            $type = $union_type->getTypeSet()[0];
            if ($type instanceof NullType) {
                return $is_internal ? Issue::PossiblyNullTypeArgumentInternal : Issue::PossiblyNullTypeArgument;
            }
            if ($type instanceof FalseType) {
                return $is_internal ? Issue::PossiblyFalseTypeArgumentInternal : Issue::PossiblyFalseTypeArgument;
            }
        }
        return $is_internal ? Issue::PartialTypeMismatchArgumentInternal : Issue::PartialTypeMismatchArgument;
    }

    /**
     * Used to check if a place expecting a reference is actually getting a reference from a node.
     * Obvious types which are always references (properties, variables) must be checked for before calling this.
     *
     * @return bool - True if this node is a call to a function that may return a reference?
     */
    private static function isFunctionReturningReference(CodeBase $code_base, Context $context, $node) : bool
    {
        if (!($node instanceof Node)) {
            return false;
        }
        $node_kind = $node->kind;
        if ($node_kind === \ast\AST_CALL) {
            foreach ((new ContextNode(
                $code_base,
                $context,
                $node->children['expr']
            ))->getFunctionFromNode() as $function) {
                if ($function->returnsRef()) {
                    return true;
                }
            }
        } elseif ($node_kind === \ast\AST_STATIC_CALL || $node_kind === \ast\AST_METHOD_CALL) {
            $method_name = $node->children['method'] ?? null;
            if (is_string($method_name)) {
                $class_node = $node->children['class'] ?? $node->children['expr'];
                if (!($class_node instanceof Node)) {
                    return false;
                }
                try {
                    foreach (UnionTypeVisitor::classListFromNodeAndContext(
                        $code_base,
                        $context,
                        $class_node
                    ) as $class) {
                        if (!$class->hasMethodWithName(
                            $code_base,
                            $method_name
                        )) {
                            continue;
                        }

                        $method = $class->getMethodByName(
                            $code_base,
                            $method_name
                        );
                        // Return true if any of the possible methods (expect that just one is found) returns a reference.
                        if ($method->returnsRef()) {
                            return true;
                        }
                    }
                } catch (IssueException $e) {
                    // Swallow any issue esceptions here. They'll be caught elsewhere.
                }
            }
        }
        return false;
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CodeBase;
use Phan\Issue;
use Phan\Language\Element\Clazz;

class DuplicateClassAnalyzer
{
    /**
     * Check to see if the given Clazz is a duplicate
     *
     * @return void
     *
     * @suppress PhanPartialTypeMismatchArgument static handling has ambiguity
     */
    public static function analyzeDuplicateClass(
        CodeBase $code_base,
        Clazz $clazz
    ) {
        // Determine if its a duplicate by looking to see if
        // the FQSEN is suffixed with an alternate ID.

        if (!$clazz->getFQSEN()->isAlternate()) {
            return;
        }

        $original_fqsen = $clazz->getFQSEN()->getCanonicalFQSEN();

        if (!$code_base->hasClassWithFQSEN($original_fqsen)) {
            // If there's a missing class we'll catch that
            // elsewhere
            return;
        }

        // Get the original class
        $original_class = $code_base->getClassByFQSEN($original_fqsen);

        // Check to see if the original definition was from
        // an internal class
        if ($original_class->isPHPInternal()) {
            if (!$clazz->hasSuppressIssue(Issue::RedefineClassInternal)) {
                Issue::maybeEmit(
                    $code_base,
                    $clazz->getContext(),
                    Issue::RedefineClassInternal,
                    $clazz->getFileRef()->getLineNumberStart(),
                    (string)$clazz,
                    $clazz->getFileRef()->getFile(),
                    $clazz->getFileRef()->getLineNumberStart(),
                    (string)$original_class
                );
            }

        // Otherwise, print the coordinates of the original
        // definition
        } else {
            if (!$clazz->hasSuppressIssue(Issue::RedefineClass)) {
                Issue::maybeEmit(
                    $code_base,
                    $clazz->getContext(),
                    Issue::RedefineClass,
                    $clazz->getFileRef()->getLineNumberStart(),
                    (string)$clazz,
                    $clazz->getFileRef()->getFile(),
                    $clazz->getFileRef()->getLineNumberStart(),
                    (string)$original_class,
                    $original_class->getFileRef()->getFile(),
                    $original_class->getFileRef()->getLineNumberStart()
                );
            }
        }

        return;
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\AnalysisVisitor;
use Phan\AST\PhanAnnotationAdder;
use Phan\AST\ContextNode;
use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\CodeBaseException;
use Phan\Exception\IssueException;
use Phan\Exception\NodeException;
use Phan\Exception\UnanalyzableException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Method;
use Phan\Language\Element\Parameter;
use Phan\Language\Element\PassByReferenceVariable;
use Phan\Language\Element\Property;
use Phan\Language\Element\Variable;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\Type;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\FalseType;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\StringType;
use Phan\Language\Type\VoidType;
use Phan\Language\UnionType;
use ast\Node;
use ast\flags;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class PostOrderAnalysisVisitor extends AnalysisVisitor
{
    /**
     * @var array<int,Node>
     */
    private $parent_node_list;

    /**
     * @param CodeBase $code_base
     * A code base needs to be passed in because we require
     * it to be initialized before any classes or files are
     * loaded.
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     *
     * @param array<int,Node> $parent_node_list
     * The parent node list of the node being analyzed
     */
    public function __construct(
        CodeBase $code_base,
        Context $context,
        array $parent_node_list
    ) {
        parent::__construct($code_base, $context);
        $this->parent_node_list = $parent_node_list;
    }

    /**
     * Default visitor for node kinds that do not have
     * an overriding method
     *
     * @param Node $node (@phan-unused-param)
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visit(Node $node) : Context
    {
        // Many nodes don't change the context and we
        // don't need to read them.
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitAssign(Node $node) : Context
    {
        // Get the type of the right side of the
        // assignment
        $right_type = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr'],
            true
        );

        $var_node = $node->children['var'];
        \assert(
            $var_node instanceof Node,
            "Expected left side of assignment to be a var"
        );

        if ($right_type->isType(VoidType::instance(false))) {
            $this->emitIssue(
                Issue::TypeVoidAssignment,
                $node->lineno ?? 0
            );
        }

        // Handle the assignment based on the type of the
        // right side of the equation and the kind of item
        // on the left
        $context = (new AssignmentVisitor(
            $this->code_base,
            $this->context,
            $node,
            $right_type
        ))->__invoke($var_node);

        $expr_node = $node->children['expr'];
        if ($expr_node instanceof Node
            && $expr_node->kind == \ast\AST_CLOSURE
        ) {
            $method = (new ContextNode(
                $this->code_base,
                $this->context->withLineNumberStart(
                    $expr_node->lineno ?? 0
                ),
                $expr_node
            ))->getClosure();

            $method->addReference($this->context);
        }

        return $context;
    }

    /**
     * @param Node $node (@phan-unused-param)
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitAssignRef(Node $node) : Context
    {
        return $this->visitAssign($node);
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitUnset(Node $node) : Context
    {
        $context = $this->context;
        // Get the type of the thing being unset
        $var_node = $node->children['var'];
        if (!($var_node instanceof Node)) {
            return $context;
        }

        $kind = $var_node->kind;
        if ($kind === \ast\AST_VAR) {
            $var_name = $var_node->children['name'];
            if (\is_string($var_name)) {
                // TODO: Make this work in branches
                $context->unsetScopeVariable($var_name);
            }
            // I think DollarDollarPlugin already warns, so don't warn here.
        } elseif ($kind === \ast\AST_DIM) {
            $this->analyzeUnsetDim($var_node);
        }
        return $context;
    }

    /**
     * @param Node $node a node of type AST_DIM in unset()
     * @return void
     * @see UnionTypeVisitor::resolveArrayShapeElementTypes()
     * @see UnionTypeVisitor::visitDim()
     */
    private function analyzeUnsetDim(Node $node)
    {
        $expr_node = $node->children['expr'];
        if (!($expr_node instanceof Node)) {
            // php -l would warn
            return;
        }

        // For now, just handle a single level of dimensions for unset($x['field']);
        if ($expr_node->kind === \ast\AST_VAR) {
            $var_name = $expr_node->children['name'];
            if (!\is_string($var_name)) {
                return;
            }

            $context = $this->context;
            $scope = $context->getScope();
            if (!$scope->hasVariableWithName($var_name)) {
                // TODO: Warn about potentially pointless unset in function scopes?
                return;
            }
            // TODO: Could warn about invalid offsets for isset
            $variable = $scope->getVariableByName($var_name);
            $union_type = $variable->getUnionType();
            if ($union_type->isEmpty()) {
                return;
            }
            if (!$union_type->asExpandedTypes($this->code_base)->hasArrayLike() && !$union_type->hasMixedType()) {
                $this->emitIssue(
                    Issue::TypeArrayUnsetSuspicious,
                    $node->lineno ?? 0,
                    (string)$union_type
                );
            }
            if (!$union_type->hasTopLevelArrayShapeTypeInstances()) {
                return;
            }
            $dim_node = $node->children['dim'];
            $dim_value = $dim_node instanceof Node ? (new ContextNode($this->code_base, $this->context, $dim_node))->getEquivalentPHPScalarValue() : $dim_node;
            // TODO: detect and warn about null
            if (!\is_scalar($dim_value)) {
                return;
            }
            $variable->setUnionType($variable->getUnionType()->withoutArrayShapeField($dim_value));
        }
    }

    /**
     * @param Node $node (@phan-unused-param)
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitIfElem(Node $node) : Context
    {
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function visitWhile(Node $node) : Context
    {
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function visitSwitch(Node $node) : Context
    {
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function visitSwitchCase(Node $node) : Context
    {
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function visitExprList(Node $node) : Context
    {
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitEncapsList(Node $node) : Context
    {
        foreach ((array)$node->children as $child_node) {
            // Confirm that variables exists
            if ($child_node instanceof Node
                && $child_node->kind == \ast\AST_VAR
            ) {
            }
        }

        return $this->context;
    }

    /**
     * Check if a given variable is undeclared.
     * @param Node $node Node with kind AST_VAR
     * @return void
     */
    private function checkForUndeclaredVariable(Node $node)
    {
        $variable_name = $node->children['name'];

        // Ignore $$var type things
        if (!\is_string($variable_name)) {
            return;
        }

        // Don't worry about non-existent undeclared variables
        // in the global scope if configured to do so
        if (Config::getValue('ignore_undeclared_variables_in_global_scope')
            && $this->context->isInGlobalScope()
        ) {
            return;
        }

        if (!$this->context->getScope()->hasVariableWithName($variable_name)
            && !Variable::isHardcodedVariableInScopeWithName($variable_name, $this->context->isInGlobalScope())
        ) {
            $this->emitIssue(
                Issue::UndeclaredVariable,
                $node->lineno ?? 0,
                $variable_name
            );
        }
    }

    /**
     * @param Node $node (@phan-unused-param)
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitDoWhile(Node $node) : Context
    {
        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_GLOBAL`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitGlobal(Node $node) : Context
    {
        $variable = Variable::fromNodeInContext(
            $node->children['var'],
            $this->context,
            $this->code_base,
            false
        );
        $variable_name = $variable->getName();
        $optional_global_variable_type = Variable::getUnionTypeOfHardcodedGlobalVariableWithName($variable_name);
        if ($optional_global_variable_type) {
            $variable->setUnionType($optional_global_variable_type);
        } else {
            $scope = $this->context->getScope();
            if ($scope->hasGlobalVariableWithName($variable_name)) {
                // TODO: Support @global, add a clone to the method context?
                $actual_global_variable = $scope->getGlobalVariableByName($variable_name);
                $this->context->addScopeVariable($actual_global_variable);
                return $this->context;
            }
        }

        // Note that we're not creating a new scope, just
        // adding variables to the existing scope
        $this->context->addScopeVariable($variable);

        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitStatic(Node $node) : Context
    {
        $variable = Variable::fromNodeInContext(
            $node->children['var'],
            $this->context,
            $this->code_base,
            false
        );

        // If the element has a default, set its type
        // on the variable
        if (isset($node->children['default'])) {
            $default_type = UnionTypeVisitor::unionTypeFromNode(
                $this->code_base,
                $this->context,
                $node->children['default']
            );

            $variable->setUnionType($default_type);
        }

        // Note that we're not creating a new scope, just
        // adding variables to the existing scope
        $this->context->addScopeVariable($variable);

        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitEcho(Node $node) : Context
    {
        return $this->visitPrint($node);
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     * @suppress PhanAccessMethodInternal
     */
    public function visitPrint(Node $node) : Context
    {
        $code_base = $this->code_base;
        $context = $this->context;
        $expr_node = $node->children['expr'];
        $type = UnionTypeVisitor::unionTypeFromNode(
            $code_base,
            $context,
            $expr_node,
            true
        );

        if (!$type->hasPrintableScalar()) {
            if ($type->isType(ArrayType::instance(false))
                || $type->isType(ArrayType::instance(true))
                || $type->isGenericArray()
            ) {
                $this->emitIssue(
                    Issue::TypeConversionFromArray,
                    $expr_node->lineno ?? $node->lineno,
                    'string'
                );
                return $context;
            }
            if (!$context->getIsStrictTypes()) {
                try {
                    foreach ($type->asExpandedTypes($code_base)->asClassList($code_base, $context) as $clazz) {
                        if ($clazz->hasMethodWithName($code_base, "__toString")) {
                            return $context;
                        }
                    }
                } catch (CodeBaseException $e) {
                    // Swallow "Cannot find class", go on to emit issue
                }
            }
            $this->emitIssue(
                Issue::TypeSuspiciousEcho,
                $expr_node->lineno ?? $node->lineno,
                (string)$type
            );
        }

        return $context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitVar(Node $node) : Context
    {
        $this->analyzeNoOp($node, Issue::NoopVariable);
        $parent_node = \end($this->parent_node_list);
        if ($parent_node instanceof Node) {
            $parent_kind = $parent_node->kind;
            /**
             * These types are either types which create variables,
             * or types which will be checked in other parts of Phan
             */
            static $skip_var_check_types = [
                \ast\AST_ARG_LIST       => true,  // may be a reference
                \ast\AST_ARRAY_ELEM     => true,  // [$X, $y] = expr() is an AST_ARRAY_ELEM. visitArray() checks the right hand side.
                \ast\AST_ASSIGN_OP      => true,  // checked in visitAssignOp
                \ast\AST_ASSIGN_REF     => true,  // Creates by reference?
                \ast\AST_ASSIGN         => true,  // checked in visitAssign
                \ast\AST_DIM            => true,  // should be checked elsewhere, as part of check for array access to non-array/string
                \ast\AST_EMPTY          => true,  // TODO: Enable this in the future?
                \ast\AST_GLOBAL         => true,  // global $var;
                \ast\AST_ISSET          => true,  // TODO: Enable this in the future?
                \ast\AST_PARAM_LIST     => true,  // this creates the variable
                \ast\AST_STATIC         => true,  // static $var;
                \ast\AST_STMT_LIST      => true,  // ;$var; (Implicitly creates the variable. Already checked to emit PhanNoopVariable)
                \ast\AST_USE_ELEM       => true,  // may be a reference, checked elsewhere
            ];

            if (!\array_key_exists($parent_kind, $skip_var_check_types)) {
                $this->checkForUndeclaredVariable($node);
            }
        }
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitArray(Node $node) : Context
    {
        $this->analyzeNoOp($node, Issue::NoopArray);
        return $this->context;
    }

    /** @internal */
    const NAME_FOR_BINARY_OP = [
        flags\BINARY_BOOL_AND            => '&&',
        flags\BINARY_BOOL_OR             => '||',
        flags\BINARY_BOOL_XOR            => 'xor',
        flags\BINARY_BITWISE_OR          => '|',
        flags\BINARY_BITWISE_AND         => '&',
        flags\BINARY_BITWISE_XOR         => '^',
        flags\BINARY_CONCAT              => '.',
        flags\BINARY_ADD                 => '+',
        flags\BINARY_SUB                 => '-',
        flags\BINARY_MUL                 => '*',
        flags\BINARY_DIV                 => '/',
        flags\BINARY_MOD                 => '%',
        flags\BINARY_POW                 => '**',
        flags\BINARY_SHIFT_LEFT          => '<<',
        flags\BINARY_SHIFT_RIGHT         => '>>',
        flags\BINARY_IS_IDENTICAL        => '===',
        flags\BINARY_IS_NOT_IDENTICAL    => '!==',
        flags\BINARY_IS_EQUAL            => '==',
        flags\BINARY_IS_NOT_EQUAL        => '!=',
        flags\BINARY_IS_SMALLER          => '<',
        flags\BINARY_IS_SMALLER_OR_EQUAL => '<=',
        flags\BINARY_IS_GREATER          => '>',
        flags\BINARY_IS_GREATER_OR_EQUAL => '>=',
        flags\BINARY_SPACESHIP           => '<=>',
        flags\BINARY_COALESCE            => '??',
    ];

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitBinaryOp(Node $node) : Context
    {
        if ((\end($this->parent_node_list)->kind ?? null) === \ast\AST_STMT_LIST) {
            if (!\in_array($node->flags, [flags\BINARY_BOOL_AND, flags\BINARY_BOOL_OR, flags\BINARY_COALESCE])) {
                $this->emitIssue(
                    Issue::NoopBinaryOperator,
                    $node->lineno,
                    self::NAME_FOR_BINARY_OP[$node->flags] ?? ''
                );
            }
        }
        return $this->context;
    }

    const NAME_FOR_UNARY_OP = [
        flags\UNARY_BOOL_NOT => '!',
        flags\UNARY_BITWISE_NOT => '~',
        flags\UNARY_SILENCE => '@',
        flags\UNARY_PLUS => '+',
        flags\UNARY_MINUS => '-',
    ];

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitUnaryOp(Node $node) : Context
    {
        if ($node->flags !== flags\UNARY_SILENCE) {
            if ((\end($this->parent_node_list)->kind ?? null) === \ast\AST_STMT_LIST) {
                $this->emitIssue(
                    Issue::NoopUnaryOperator,
                    $node->lineno,
                    self::NAME_FOR_UNARY_OP[$node->flags] ?? ''
                );
            }
        }
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitConst(Node $node) : Context
    {
        $context = $this->context;
        try {
            $nameNode = $node->children['name'];
            // Based on UnionTypeVisitor::visitConst
            if ($nameNode->kind == \ast\AST_NAME) {
                $constant = (new ContextNode(
                    $this->code_base,
                    $context,
                    $node
                ))->getConst();

                // Mark that this constant has been referenced from
                // this context
                $constant->addReference($context);
            }
        } catch (IssueException $exception) {
            // We need to do this in order to check keys and (after the first 5) values in AST arrays.
            // Other parts of the AST may also not be covered.
            // (This issue may be a duplicate)
            Issue::maybeEmitInstance(
                $this->code_base,
                $context,
                $exception->getIssueInstance()
            );
        } catch (\Exception $exception) {
            // Swallow any other types of exceptions. We'll log the errors
            // elsewhere.
        }

        // Check to make sure we're doing something with the
        // constant
        $this->analyzeNoOp($node, Issue::NoopConstant);

        return $context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitClassConst(Node $node) : Context
    {
        try {
            $constant = (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ))->getClassConst();

            // Mark that this class constant has been referenced
            // from this context
            $constant->addReference($this->context);
        } catch (IssueException $exception) {
            // We need to do this in order to check keys and (after the first 5) values in AST arrays, possibly other types.
            Issue::maybeEmitInstance(
                $this->code_base,
                $this->context,
                $exception->getIssueInstance()
            );
        } catch (\Exception $exception) {
            // Swallow any other types of exceptions. We'll log the errors
            // elsewhere.
        }

        // Check to make sure we're doing something with the
        // class constant
        $this->analyzeNoOp($node, Issue::NoopConstant);

        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitClosure(Node $node) : Context
    {
        $func = $this->context->getFunctionLikeInScope($this->code_base);

        $return_type = $func->getUnionType();

        if (!$return_type->isEmpty()
            && !$func->getHasReturn()
            && !$this->declOnlyThrows($node)
            && !$return_type->hasType(VoidType::instance(false))
            && !$return_type->hasType(NullType::instance(false))
        ) {
            $this->emitIssue(
                Issue::TypeMissingReturn,
                $node->lineno ?? 0,
                (string)$func->getFQSEN(),
                (string)$return_type
            );
        }
        $this->analyzeNoOp($node, Issue::NoopClosure);
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitReturn(Node $node) : Context
    {
        $context = $this->context;
        // Make sure we're actually returning from a method.
        if (!$context->isInFunctionLikeScope()) {
            return $context;
        }
        $code_base = $this->code_base;

        // Check real return types instead of phpdoc return types in traits for #800
        // TODO: Why did Phan originally not analyze return types of traits at all in 4c6956c05222e093b29393ceaa389ffb91041bdc
        $is_trait = false;
        if ($context->isInClassScope()) {
            $clazz = $context->getClassInScope($code_base);
            $is_trait = $clazz->isTrait();
        }


        // Get the method/function/closure we're in
        $method = $context->getFunctionLikeInScope($code_base);

        \assert(
            !empty($method),
            "We're supposed to be in either method or closure scope."
        );

        // Figure out what we intend to return
        // (For traits, lower the false positive rate by comparing against the real return type instead of the phpdoc type (#800))
        $method_return_type = $is_trait ? $method->getRealReturnType() : $method->getUnionType();

        if ($method->getHasYield()) {  // Function that is syntactically a Generator.
            return $context;  // Analysis was completed in PreOrderAnalysisVisitor
        }
        // This leaves functions which aren't syntactically generators.

        // Figure out what is actually being returned
        // TODO: Properly check return values of array shapes
        foreach ($this->getReturnTypes($context, $node->children['expr']) as $expression_type) {
            // If there is no declared type, see if we can deduce
            // what it should be based on the return type
            if ($method_return_type->isEmpty()
                || $method->isReturnTypeUndefined()
            ) {
                if (!$is_trait) {
                    $method->setIsReturnTypeUndefined(true);

                    // Set the inferred type of the method based
                    // on what we're returning
                    $method->setUnionType($method->getUnionType()->withUnionType($expression_type));
                }

                // No point in comparing this type to the
                // type we just set
                continue;
            }

            // Check if the return type is compatible with the declared return type.
            if (!$method->isReturnTypeUndefined()) {
                // We allow base classes to cast to subclasses, and subclasses to cast to baseclasses,
                // but don't allow subclasses to cast to subclasses on a separate branch of the inheritance tree
                if (!$this->checkCanCastToReturnType($code_base, $expression_type, $method_return_type)) {
                    $this->emitIssue(
                        Issue::TypeMismatchReturn,
                        $node->lineno,
                        (string)$expression_type,
                        $method->getName(),
                        (string)$method_return_type
                    );
                } elseif (Config::get_strict_return_checking() && $expression_type->typeCount() > 1) {
                    self::analyzeReturnStrict($code_base, $method, $expression_type, $method_return_type, $node);
                }
            }
            // For functions that aren't syntactically Generators,
            // update the set/existence of return values.

            if ($method->isReturnTypeUndefined()) {
                // Add the new type to the set of values returned by the
                // method
                $method->getUnionType()->addUnionType($expression_type);
            }

            // Mark the method as returning something (even if void)
            if (null !== $node->children['expr']) {
                $method->setHasReturn(true);
            }
        }

        return $context;
    }

    private function checkCanCastToReturnType(CodeBase $code_base, UnionType $expression_type, UnionType $method_return_type)
    {
        if ($method_return_type->hasTemplateParameterTypes()) {
            // TODO: Better casting logic for template types (E.g. should be able to cast None to Option<MyClass>, but not Some<int> to Option<MyClass>
            return $expression_type->canCastToExpandedUnionType($method_return_type, $code_base);
        }
        // We allow base classes to cast to subclasses, and subclasses to cast to baseclasses,
        // but don't allow subclasses to cast to subclasses on a separate branch of the inheritance tree
        return $expression_type->asExpandedTypes($code_base)->canCastToUnionType($method_return_type) ||
            $expression_type->canCastToUnionType($method_return_type->asExpandedTypes($code_base));
    }

    /**
     * @return void
     */
    private function analyzeReturnStrict(
        CodeBase $code_base,
        FunctionInterface $method,
        UnionType $expression_type,
        UnionType $method_return_type,
        $node
    ) {
        $type_set = $expression_type->getTypeSet();
        $context = $this->context;
        \assert(\count($type_set) >= 2);

        $mismatch_type_set = UnionType::empty();
        $mismatch_expanded_types = null;

        // For the strict
        foreach ($type_set as $type) {
            // Expand it to include all parent types up the chain
            $individual_type_expanded = $type->asExpandedTypes($code_base);

            // See if the argument can be cast to the
            // parameter
            if (!$individual_type_expanded->canCastToUnionType(
                $method_return_type
            )) {
                if ($method->isPHPInternal()) {
                    // If we are not in strict mode and we accept a string parameter
                    // and the argument we are passing has a __toString method then it is ok
                    if (!$context->getIsStrictTypes() && $method_return_type->hasType(StringType::instance(false))) {
                        if ($individual_type_expanded->hasClassWithToStringMethod($code_base, $context)) {
                            continue;
                        }
                    }
                }
                $mismatch_type_set = $mismatch_type_set->withType($type);
                if ($mismatch_expanded_types === null) {
                    // Warn about the first type
                    $mismatch_expanded_types = $individual_type_expanded;
                }
            }
        }


        if ($mismatch_expanded_types === null) {
            // No mismatches
            return;
        }

        // If we have TypeMismatchReturn already, then also suppress the partial mismatch warnings as well.
        if ($this->context->hasSuppressIssue($code_base, Issue::TypeMismatchReturn)) {
            return;
        }
        $this->emitIssue(
            self::getStrictIssueType($mismatch_type_set),
            $node->lineno ?? 0,
            (string)$expression_type,
            $method->getName(),
            (string)$method_return_type,
            $mismatch_expanded_types
        );
    }

    private static function getStrictIssueType(UnionType $union_type) : string
    {
        if ($union_type->typeCount() === 1) {
            $type = $union_type->getTypeSet()[0];
            if ($type instanceof NullType) {
                return Issue::PossiblyNullTypeReturn;
            }
            if ($type instanceof FalseType) {
                return Issue::PossiblyFalseTypeReturn;
            }
        }
        return Issue::PartialTypeMismatchReturn;
    }

    /**
     * @return \Generator|UnionType[]
     */
    private function getReturnTypes(Context $context, $node)
    {
        if (!($node instanceof Node)) {
            if (null === $node) {
                yield VoidType::instance(false)->asUnionType();
                return;
            }
            yield UnionTypeVisitor::unionTypeFromNode(
                $this->code_base,
                $context,
                $node,
                true
            );
            return;
        }
        $kind = $node->kind;
        if ($kind === \ast\AST_CONDITIONAL) {
            yield from self::deduplicateUnionTypes($this->getReturnTypesOfConditional($context, $node));
            return;
        } elseif ($kind === \ast\AST_ARRAY) {
            $key_type_enum = GenericArrayType::getKeyTypeOfArrayNode($this->code_base, $context, $node);
            foreach (self::deduplicateUnionTypes($this->getReturnTypesOfArray($context, $node)) as $elem_type) {
                yield $elem_type->asGenericArrayTypes($key_type_enum);  // TODO: Infer corresponding key types
            }
            return;
        }

        $expression_type = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $context,
            $node,
            true
        );

        if ($expression_type->hasStaticType()) {
            $expression_type =
                $expression_type->withStaticResolvedInContext(
                    $context
                );
        }
        yield $expression_type;
    }

    /**
     * @return \Generator|UnionType[]
     */
    private function getReturnTypesOfConditional(Context $context, Node $node)
    {
        $cond_node = $node->children['cond'];
        $cond_truthiness = UnionTypeVisitor::checkCondUnconditionalTruthiness($cond_node);
        // For the shorthand $a ?: $b, the cond node will be the truthy value.
        // Note: an ast node will never be null(can be unset), it will be a const AST node with the name null.
        $true_node = $node->children['true'] ?? $cond_node;

        // Rarely, a conditional will always be true or always be false.
        if ($cond_truthiness !== null) {
            // TODO: Add no-op checks in another PR, if they don't already exist for conditional.
            if ($cond_truthiness === true) {
                // The condition is unconditionally true
                yield from $this->getReturnTypes($context, $true_node);
                return;
            } else {
                // The condition is unconditionally false

                // Add the type for the 'false' side
                yield from $this->getReturnTypes($context, $node->children['false']);
                return;
            }
        }

        // TODO: false_context once there is a NegatedConditionVisitor
        // TODO: emit no-op if $cond_node is a literal, such as `if (2)`
        // - Also note that some things such as `true` and `false` are \ast\AST_NAME nodes.

        if ($cond_node instanceof Node) {
            // TODO: Use different contexts and merge those, in case there were assignments or assignments by reference in both sides of the conditional?
            // Reuse the BranchScope (sort of unintuitive). The ConditionVisitor returns a clone and doesn't modify the original.
            $base_context = $this->context;
            // We don't bother analyzing visitReturn in PostOrderAnalysisVisitor, right now.
            // This may eventually change, just to ensure the expression is checked for issues
            assert($base_context->isInFunctionLikeScope());
            $true_context = (new ConditionVisitor(
                $this->code_base,
                $base_context
            ))->__invoke($cond_node);
            $false_context = (new NegatedConditionVisitor(
                $this->code_base,
                $base_context
            ))->__invoke($cond_node);
        } else {
            $true_context = $context;
            $false_context = $this->context;
        }

        // Allow nested ternary operators, or arrays within ternary operators
        if (($node->children['true'] ?? null) !== null) {
            yield from $this->getReturnTypes($true_context, $true_node);
        } else {
            // E.g. From the left hand side of yield (int|false) ?: default,
            // yielding false is impossible.
            foreach ($this->getReturnTypes($true_context, $true_node) as $raw_union_type) {
                if ($raw_union_type->isEmpty() || !$raw_union_type->containsFalsey()) {
                    yield $raw_union_type;
                } else {
                    $raw_union_type = $raw_union_type->nonFalseyClone();
                    if (!$raw_union_type->isEmpty()) {
                        yield $raw_union_type;
                    }
                }
            }
        }

        yield from $this->getReturnTypes($false_context, $node->children['false']);
    }

    /**
     * @param \Generator|UnionType[] $types
     * @return \Generator|UnionType[]
     * @suppress PhanPluginUnusedVariable
     */
    private static function deduplicateUnionTypes($types)
    {
        $unique_types = [];
        foreach ($types as $type) {
            foreach ($unique_types as $old_type) {
                if ($type->isEqualTo($old_type)) {
                    break;
                }
            }
            yield $type;
            $unique_types[] = $type;
        }
    }

    /**
     * @return \Generator|UnionType[]
     */
    private function getReturnTypesOfArray(Context $context, Node $node)
    {
        if (!empty($node->children)
            && $node->children[0] instanceof Node
            && $node->children[0]->kind == \ast\AST_ARRAY_ELEM
        ) {
            // Check the first 5 (completely arbitrary) elements
            // and assume the rest are the same type
            for ($i=0; $i<5; $i++) {
                // Check to see if we're out of elements
                if (empty($node->children[$i])) {
                    return;
                }

                // Don't bother recursing more than one level to iterate over possible types.
                $value_node = $node->children[$i]->children['value'];
                if ($value_node instanceof Node) {
                    yield UnionTypeVisitor::unionTypeFromNode(
                        $this->code_base,
                        $context,
                        $value_node,
                        true
                    );
                } else {
                    yield Type::fromObject(
                        $value_node
                    )->asUnionType();
                }
            }
            return;
        }
        yield ArrayType::instance(false)->asUnionType();
    }

    /**
     * @param Node $node (@phan-unused-param)
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitPropDecl(Node $node) : Context
    {
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitCall(Node $node) : Context
    {
        $expression = $node->children['expr'];
        try {
            $function_list_generator = (new ContextNode(
                $this->code_base,
                $this->context,
                $expression
            ))->getFunctionFromNode();

            foreach ($function_list_generator as $function) {
                assert($function instanceof FunctionInterface);
                // Check the call for parameter and argument types
                $this->analyzeCallToMethod(
                    $function,
                    $node
                );
            }
        } catch (CodeBaseException $e) {
            // ignore it.
        }

        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitNew(Node $node) : Context
    {
        try {
            $context_node = (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ));

            $method = $context_node->getMethod(
                '__construct',
                false,
                false,
                true
            );

            $class_list = $context_node->getClassList(false, ContextNode::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME);
            // Add a reference to each class this method
            // could be called on
            foreach ($class_list as $class) {
                $class->addReference($this->context);
                if ($class->isDeprecated()) {
                    $this->emitIssue(
                        Issue::DeprecatedClass,
                        $node->lineno ?? 0,
                        (string)$class->getFQSEN(),
                        $class->getContext()->getFile(),
                        (string)$class->getContext()->getLineNumberStart()
                    );
                }
                foreach ($class->getInterfaceFQSENList() as $interface) {
                    $clazz = $this->code_base->getClassByFQSEN($interface);
                    if ($clazz->isDeprecated()) {
                        $this->emitIssue(
                            Issue::DeprecatedInterface,
                            $node->lineno ?? 0,
                            (string)$clazz->getFQSEN(),
                            $clazz->getContext()->getFile(),
                            (string)$clazz->getContext()->getLineNumberStart()
                        );
                    }
                }
                foreach ($class->getTraitFQSENList() as $trait) {
                    $clazz = $this->code_base->getClassByFQSEN($trait);
                    if ($clazz->isDeprecated()) {
                        $this->emitIssue(
                            Issue::DeprecatedTrait,
                            $node->lineno ?? 0,
                            (string)$clazz->getFQSEN(),
                            $clazz->getContext()->getFile(),
                            (string)$clazz->getContext()->getLineNumberStart()
                        );
                    }
                }
            }

            $this->analyzeMethodVisibility(
                $method,
                $node
            );

            $this->analyzeCallToMethod(
                $method,
                $node
            );

            foreach ($class_list as $class) {
                // Make sure we're not instantiating an abstract
                // class
                if ($class->isAbstract()
                    && (!$this->context->isInClassScope()
                    || $class->getFQSEN() != $this->context->getClassFQSEN())
                ) {
                    $this->emitIssue(
                        Issue::TypeInstantiateAbstract,
                        $node->lineno ?? 0,
                        (string)$class->getFQSEN()
                    );
                }

                // Make sure we're not instantiating an interface
                if ($class->isInterface()) {
                    $this->emitIssue(
                        Issue::TypeInstantiateInterface,
                        $node->lineno ?? 0,
                        (string)$class->getFQSEN()
                    );
                }
            }
        } catch (IssueException $exception) {
            Issue::maybeEmitInstance(
                $this->code_base,
                $this->context,
                $exception->getIssueInstance()
            );
        } catch (\Exception $exception) {
            // If we can't figure out what kind of a call
            // this is, don't worry about it
            return $this->context;
        }

        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitInstanceof(Node $node) : Context
    {
        try {
            // Fetch the class list, and emit warnings as a side effect.
            // TODO: Unify UnionTypeVisitor, AssignmentVisitor, and PostOrderAnalysisVisitor
            (new ContextNode(
                $this->code_base,
                $this->context,
                $node->children['class']
            ))->getClassList(false, ContextNode::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME, Issue::TypeInvalidInstanceof);
        } catch (CodeBaseException $exception) {
            $this->emitIssue(
                Issue::UndeclaredClassInstanceof,
                $node->lineno ?? 0,
                (string)$exception->getFQSEN()
            );
        }

        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitStaticCall(Node $node) : Context
    {
        // Get the name of the method being called
        $method_name = $node->children['method'];

        // Give up on things like Class::$var
        if (!\is_string($method_name)) {
            return $this->context;
        }

        // Get the name of the static class being referenced
        $static_class = '';
        $class_node = $node->children['class'];
        if (!($class_node instanceof Node)) {
            $static_class = (string)$class_node;
        } elseif ($node->children['class']->kind == \ast\AST_NAME) {
            $static_class = (string)$node->children['class']->children['name'];
        }

        $method = $this->getStaticMethodOrEmitIssue($node);

        if ($method === null) {
            // Short circuit on a constructor being called statically
            // on something other than 'parent'
            if ($method_name === '__construct' && $static_class !== 'parent') {
                $this->emitConstructorWarning($node, $static_class, $method_name);
            }
            return $this->context;
        }

        try {
            if ($method_name === '__construct') {
                $this->checkNonAncestorConstructCall($node, $static_class, $method_name);
                // Even if it exists, continue on and type check the arguments passed.
            }
            // Get the method that's calling the static method
            $calling_method = null;
            if ($this->context->isInMethodScope()) {
                $calling_function_like =
                    $this->context->getFunctionLikeInScope($this->code_base);

                if ($calling_function_like instanceof Method) {
                    $calling_method = $calling_function_like;
                }
            }

            // If the method being called isn't actually static and it's
            // not a call to parent::f from f, we may be in trouble.
            if (!$method->isStatic()

                // Allow static calls to parent if we're not in a static
                // method or if it's to the overridden method
                && !(
                    (
                        'parent' === $static_class
                        || 'self' === $static_class
                        || 'static' === $static_class
                    )
                    && $this->context->isInMethodScope()
                    && (
                        $this->context->getFunctionLikeFQSEN()->getName() == $method->getFQSEN()->getName()
                        || ($calling_method && !$calling_method->isStatic())
                    )

                // Allow static calls to methods from non-static class methods
                ) && !(
                    $this->context->isInClassScope()
                    && $this->context->isInFunctionLikeScope()
                    && ($calling_method && !$calling_method->isStatic())
                // Allow static calls parent methods from closure
                ) && !(
                    $this->context->isInClassScope()
                    && $this->context->isInFunctionLikeScope()
                    && $this->context->getFunctionLikeFQSEN()->isClosure()
                )
            ) {
                $class_list = (new ContextNode(
                    $this->code_base,
                    $this->context,
                    $node->children['class']
                ))->getClassList();

                if (!empty($class_list)) {
                    $class = \array_values($class_list)[0];

                    $this->emitIssue(
                        Issue::StaticCallToNonStatic,
                        $node->lineno ?? 0,
                        "{$class->getFQSEN()}::{$method_name}()",
                        $method->getFileRef()->getFile(),
                        (string)$method->getFileRef()->getLineNumberStart()
                    );
                }
            }

            $this->analyzeMethodVisibility(
                $method,
                $node
            );

            // Make sure the parameters look good
            $this->analyzeCallToMethod(
                $method,
                $node
            );
        } catch (IssueException $exception) {
            Issue::maybeEmitInstance(
                $this->code_base,
                $this->context,
                $exception->getIssueInstance()
            );
        } catch (\Exception $exception) {
            // If we can't figure out the class for this method
            // call, cry YOLO and mark every method with that
            // name with a reference.
            if (Config::get_track_references()
                && Config::getValue('dead_code_detection_prefer_false_negative')
            ) {
                foreach ($this->code_base->getMethodSetByName(
                    $method_name
                ) as $method) {
                    $method->addReference($this->context);
                }
            }

            // If we can't figure out what kind of a call
            // this is, don't worry about it
            return $this->context;
        }
        return $this->context;
    }

    /**
     * Check calling A::__construct (where A is not parent)
     * @return void
     */
    private function checkNonAncestorConstructCall(
        Node $node,
        string $static_class,
        string $method_name
    ) {
        // TODO: what about unanalyzable?
        if ($node->children['class']->kind !== \ast\AST_NAME) {
            return;
        }
        $class_context_node = (new ContextNode(
            $this->code_base,
            $this->context,
            $node->children['class']
        ));
        // TODO: check for self/static/<class name of self> and warn about recursion?
        // TODO: Only allow calls to __construct from other constructors?
        $found_ancestor_constructor = false;
        if ($this->context->isInMethodScope()) {
            $possible_ancestor_type = $class_context_node->getClassUnionType();
            // If we can determine the ancestor type, and it's an parent/ancestor class, allow the call without warning.
            // (other code should check visibility and existence and args of __construct)

            if (!$possible_ancestor_type->isEmpty()) {
                // but forbid 'self::__construct', 'static::__construct'
                $type = $this->context->getClassFQSEN()->asUnionType();
                if ($possible_ancestor_type->hasStaticType()) {
                    $this->emitIssue(
                        Issue::AccessOwnConstructor,
                        $node->lineno ?? 0,
                        $static_class
                    );
                    $found_ancestor_constructor = true;
                } elseif ($type->asExpandedTypes($this->code_base)->canCastToUnionType($possible_ancestor_type)) {
                    if ($type->canCastToUnionType($possible_ancestor_type)) {
                        $this->emitIssue(
                            Issue::AccessOwnConstructor,
                            $node->lineno ?? 0,
                            $static_class
                        );
                    }
                    $found_ancestor_constructor = true;
                }
            }
        }

        if (!$found_ancestor_constructor) {
            // TODO: new issue type?
            $this->emitConstructorWarning($node, $static_class, $method_name);
        }
    }

    /**
     * TODO: change to a different issue type in a future phan release?
     * @return void
     */
    private function emitConstructorWarning(Node $node, string $static_class, string $method_name)
    {
        $this->emitIssue(
            Issue::UndeclaredStaticMethod,
            $node->lineno ?? 0,
            "{$static_class}::{$method_name}()"
        );
    }

    /**
     * gets the static method, or emits an issue.
     * @return Method|null
     */
    private function getStaticMethodOrEmitIssue(Node $node)
    {
        $method_name = $node->children['method'];

        try {
            // Get a reference to the method being called
            return (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ))->getMethod($method_name, true, true);
        } catch (IssueException $exception) {
            Issue::maybeEmitInstance(
                $this->code_base,
                $this->context,
                $exception->getIssueInstance()
            );
        } catch (\Exception $exception) {
            // If we can't figure out the class for this method
            // call, cry YOLO and mark every method with that
            // name with a reference.
            if (Config::get_track_references()
                && Config::getValue('dead_code_detection_prefer_false_negative')
            ) {
                foreach ($this->code_base->getMethodSetByName(
                    $method_name
                ) as $method) {
                    $method->addReference($this->context);
                }
            }

            // If we can't figure out what kind of a call
            // this is, don't worry about it
        }
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitMethod(Node $node) : Context
    {
        \assert(
            $this->context->isInFunctionLikeScope(),
            "Must be in function-like scope to get method"
        );

        $method = $this->context->getFunctionLikeInScope($this->code_base);

        $return_type = $method->getUnionType();

        \assert(
            $method instanceof Method,
            "Function found where method expected"
        );

        $has_interface_class = false;
        if ($method instanceof Method) {
            try {
                $class = $method->getClass($this->code_base);
                $has_interface_class = $class->isInterface();
            } catch (\Exception $exception) {
            }

            if (!$method->isAbstract()
                && !$method->isFromPHPDoc()
                && !$has_interface_class
                && !$return_type->isEmpty()
                && !$method->getHasReturn()
                && !$this->declOnlyThrows($node)
                && !$return_type->hasType(VoidType::instance(false))
                && !$return_type->hasType(NullType::instance(false))
            ) {
                $this->emitIssue(
                    Issue::TypeMissingReturn,
                    $node->lineno ?? 0,
                    (string)$method->getFQSEN(),
                    (string)$return_type
                );
            }

            if ($method->isStatic()
                && $method->getUnionType()->hasTemplateType()
            ) {
                $this->emitIssue(
                    Issue::TemplateTypeStaticMethod,
                    $node->lineno ?? 0,
                    (string)$method->getFQSEN()
                );
            }
        }

        if ($method->getHasReturn() && $method->getIsMagicAndVoid()) {
            $this->emitIssue(
                Issue::TypeMagicVoidWithReturn,
                $node->lineno ?? 0,
                (string)$method->getFQSEN()
            );
        }

        $parameters_seen = [];
        foreach ($method->getParameterList() as $i => $parameter) {
            if (isset($parameters_seen[$parameter->getName()])) {
                $this->emitIssue(
                    Issue::ParamRedefined,
                    $node->lineno ?? 0,
                    '$' . $parameter->getName()
                );
            } else {
                $parameters_seen[$parameter->getName()] = $i;
            }
        }


        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_FUNC_DECL`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitFuncDecl(Node $node) : Context
    {
        $method =
            $this->context->getFunctionLikeInScope($this->code_base);

        $return_type = $method->getUnionType();

        if (!$return_type->isEmpty()
            && !$method->getHasReturn()
            && !$this->declOnlyThrows($node)
            && !$return_type->hasType(VoidType::instance(false))
            && !$return_type->hasType(NullType::instance(false))
        ) {
            $this->emitIssue(
                Issue::TypeMissingReturn,
                $node->lineno ?? 0,
                (string)$method->getFQSEN(),
                (string)$return_type
            );
        }

        $parameters_seen = [];
        foreach ($method->getParameterList() as $i => $parameter) {
            if (isset($parameters_seen[$parameter->getName()])) {
                $this->emitIssue(
                    Issue::ParamRedefined,
                    $node->lineno ?? 0,
                    '$' . $parameter->getName()
                );
            } else {
                $parameters_seen[$parameter->getName()] = $i;
            }
        }

        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitMethodCall(Node $node) : Context
    {
        $method_name = $node->children['method'];

        if (!\is_string($method_name)) {
            return $this->context;
        }

        try {
            $method = (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ))->getMethod($method_name, false);
        } catch (IssueException $exception) {
            Issue::maybeEmitInstance(
                $this->code_base,
                $this->context,
                $exception->getIssueInstance()
            );
            return $this->context;
        } catch (NodeException $exception) {
            // If we can't figure out the class for this method
            // call, cry YOLO and mark every method with that
            // name with a reference.
            if (Config::get_track_references()
                && Config::getValue('dead_code_detection_prefer_false_negative')
            ) {
                foreach ($this->code_base->getMethodSetByName(
                    $method_name
                ) as $method) {
                    $method->addReference($this->context);
                }
            }

            // Swallow it
            return $this->context;
        }

        $this->analyzeMethodVisibility(
            $method,
            $node
        );

        // Check the call for parameter and argument types
        $this->analyzeCallToMethod(
            $method,
            $node
        );

        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_DIM`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitDim(Node $node) : Context
    {
        $code_base = $this->code_base;
        $context = $this->context;
        // Check the dimension type to trigger PhanUndeclaredVariable, etc.
        /* $dim_type = */
        UnionTypeVisitor::unionTypeFromNode(
            $code_base,
            $context,
            $node->children['dim'],
            true
        );

        if ($node->flags & PhanAnnotationAdder::FLAG_IGNORE_NULLABLE_AND_UNDEF) {
            return $context;
        }
        // Check the array type to trigger TypeArraySuspicious
        try {
            /* $array_type = */
            UnionTypeVisitor::unionTypeFromNode(
                $code_base,
                $context,
                $node,
                false
            );
            // TODO: check if array_type has array but not ArrayAccess.
            // If that is true, then assert that $dim_type can cast to `int|string`
        } catch (IssueException $exception) {
            // Detect this elsewhere, e.g. want to detect PhanUndeclaredVariableDim but not PhanUndeclaredVariable
        }
        return $context;
    }

    /**
     * @return bool true if the union type should skip analysis due to being the left hand side expression of an assignment
     * We skip checks for $x['key'] being valid in expressions such as `$x['key']['key2']['key3'] = 'value';`
     * because those expressions will create $x['key'] as a side effect.
     *
     * Precondition: $parent_node->kind === \ast\AST_DIM && $parent_node->children['expr'] is $node
     */
    private function shouldSkipNestedAssignDim() : bool
    {
        $parent_node_list = $this->parent_node_list;
        $cur_parent_node = \end($parent_node_list);
        for (;; $cur_parent_node = $prev_parent_node) {
            $prev_parent_node = \prev($parent_node_list);
            switch ($prev_parent_node->kind) {
                case \ast\AST_DIM:
                    if ($prev_parent_node->children['expr'] !== $cur_parent_node) {
                        return false;
                    }
                    break;
                case \ast\AST_ASSIGN:
                case \ast\AST_ASSIGN_REF:
                    return $prev_parent_node->children['var'] === $cur_parent_node;
                case \ast\AST_ARRAY_ELEM:
                    $prev_parent_node = \prev($parent_node_list);  // this becomes AST_ARRAY
                    break;
                case \ast\AST_ARRAY:
                    break;
                default:
                    return false;
            }
        }
    }

    public function visitStaticProp(Node $node) : Context
    {
        return $this->analyzeProp($node, true);
    }

    public function visitProp(Node $node) : Context
    {
        return $this->analyzeProp($node, false);
    }

    /**
     * Analyze a node with kind `\ast\AST_PROP` or `\ast\AST_STATIC_PROP`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @param bool $is_static
     * True if fetching a static property.
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function analyzeProp(Node $node, bool $is_static) : Context
    {
        $exception_or_null = null;

        try {
            $property = (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ))->getProperty($node->children['prop'], $is_static);

            // Mark that this property has been referenced from
            // this context
            if (Config::get_track_references()) {
                $this->trackPropertyReference($property, $node);
            }
        } catch (IssueException $exception) {
            // We'll check out some reasons it might not exist
            // before logging the issue
            $exception_or_null = $exception;
        } catch (\Exception $exception) {
            // Swallow any exceptions. We'll catch it later.
        }

        if (isset($property)) {
            $this->analyzeNoOp($node, Issue::NoopProperty);
        } else {
            \assert(
                isset($node->children['expr'])
                || isset($node->children['class']),
                "Property nodes must either have an expression or class"
            );

            $class_list = [];
            try {
                // Get the set of classes that are being referenced
                $class_list = (new ContextNode(
                    $this->code_base,
                    $this->context,
                    $node->children['expr'] ?? $node->children['class']
                ))->getClassList(true);
            } catch (IssueException $exception) {
                Issue::maybeEmitInstance(
                    $this->code_base,
                    $this->context,
                    $exception->getIssueInstance()
                );
            }

            if (!$is_static) {
                // Find out of any of them have a __get magic method
                // (Only check if looking for instance properties)
                $has_getter =
                    \array_reduce($class_list, function ($carry, $class) {
                        return (
                            $carry ||
                            $class->hasGetMethod($this->code_base)
                        );
                    }, false);

                // If they don't, then analyze for Noops.
                if (!$has_getter) {
                    $this->analyzeNoOp($node, Issue::NoopProperty);

                    if ($exception_or_null instanceof IssueException) {
                        Issue::maybeEmitInstance(
                            $this->code_base,
                            $this->context,
                            $exception_or_null->getIssueInstance()
                        );
                    }
                }
            }
        }

        return $this->context;
    }

    /**
     * @return void
     */
    private function trackPropertyReference(Property $property, Node $node)
    {
        $property->addReference($this->context);
        if (!$property->hasReadReference() && !$this->isAssignmentOrNestedAssignment($node)) {
            $property->setHasReadReference();
        }
    }

    private function isAssignmentOrNestedAssignment(Node $node) : bool
    {
        $parent_node = \end($this->parent_node_list);
        $parent_kind = $parent_node->kind;
        if ($parent_kind === \ast\AST_DIM) {
            return $parent_node->children['expr'] === $node && $this->shouldSkipNestedAssignDim();
        } elseif ($parent_kind === \ast\AST_ASSIGN || $parent_kind === \ast\AST_ASSIGN_REF) {
            return $parent_node->children['var'] === $node;
        }
        return false;
    }

    /**
     * Analyze whether a method is callable
     *
     * @param Method $method
     * @param Node $node
     *
     * @return void
     */
    private function analyzeMethodVisibility(
        Method $method,
        Node $node
    ) {
        if ($method->isPrivate()
            && (
                !$this->context->isInClassScope()
                || $this->context->getClassFQSEN() != $method->getDefiningClassFQSEN()
            )
        ) {
            $has_call_magic_method = !$method->isStatic()
                && $method->getDefiningClass($this->code_base)->hasMethodWithName($this->code_base, '__call');

            $this->emitIssue(
                $has_call_magic_method ?
                    Issue::AccessMethodPrivateWithCallMagicMethod : Issue::AccessMethodPrivate,
                $node->lineno ?? 0,
                (string)$method->getFQSEN(),
                $method->getFileRef()->getFile(),
                (string)$method->getFileRef()->getLineNumberStart()
            );
        } elseif ($method->isProtected() && !$this->canAccessProtectedMethodFromContext($method)) {
            $has_call_magic_method = !$method->isStatic()
                && $method->getDefiningClass($this->code_base)->hasMethodWithName($this->code_base, '__call');

            $this->emitIssue(
                $has_call_magic_method ?
                    Issue::AccessMethodProtectedWithCallMagicMethod : Issue::AccessMethodProtected,
                $node->lineno ?? 0,
                (string)$method->getFQSEN(),
                $method->getFileRef()->getFile(),
                (string)$method->getFileRef()->getLineNumberStart()
            );
        }
    }

    private function canAccessProtectedMethodFromContext(Method $method) : bool
    {
        $context = $this->context;
        if (!$context->isInClassScope()) {
            return false;
        }
        $class_fqsen = $context->getClassFQSEN();
        $class_fqsen_type = $class_fqsen->asType();
        $method_class_fqsen_type = $method->getClassFQSEN()->asType();
        if ($class_fqsen_type->canCastToType($method_class_fqsen_type)) {
            return true;
        }
        $method_defining_class_fqsen = $method->getDefiningClassFQSEN();
        if ($class_fqsen === $method_defining_class_fqsen) {
            return true;
        }
        $method_defining_class_fqsen_type = $method_defining_class_fqsen->asType();
        if ($class_fqsen_type->isSubclassOf($this->code_base, $method_defining_class_fqsen_type)) {
            return true;
        }
        if ($method_defining_class_fqsen_type->isSubclassOf($this->code_base, $class_fqsen_type)) {
            return true;
        }
        return false;
    }

    /**
     * Analyze the parameters and arguments for a call
     * to the given method or function
     *
     * @param FunctionInterface $method
     * @param Node $node
     *
     * @return void
     */
    private function analyzeCallToMethod(
        FunctionInterface $method,
        Node $node
    ) {
        $code_base = $this->code_base;
        $context = $this->context;

        $method->addReference($context);

        // Create variables for any pass-by-reference
        // parameters
        $argument_list = $node->children['args']->children;
        foreach ($argument_list as $i => $argument) {
            if (!$argument instanceof \ast\Node) {
                continue;
            }

            $parameter = $method->getParameterForCaller($i);
            if (!$parameter) {
                continue;
            }

            // If pass-by-reference, make sure the variable exists
            // or create it if it doesn't.
            if ($parameter->isPassByReference()) {
                if ($argument->kind == \ast\AST_VAR) {
                    try {
                        // We don't do anything with the new variable; just create it
                        // if it doesn't exist
                        (new ContextNode(
                            $code_base,
                            $context,
                            $argument
                        ))->getOrCreateVariable();
                    } catch (NodeException $e) {
                        // E.g. `function_accepting_reference(${$varName})` - Phan can't analyze outer type of ${$varName}
                        continue;
                    }
                } elseif ($argument->kind == \ast\AST_STATIC_PROP
                    || $argument->kind == \ast\AST_PROP
                ) {
                    $property_name = $argument->children['prop'];

                    if (\is_string($property_name)) {
                        // We don't do anything with it; just create it
                        // if it doesn't exist
                        try {
                            (new ContextNode(
                                $code_base,
                                $context,
                                $argument
                            ))->getOrCreateProperty($argument->children['prop'], $argument->kind == \ast\AST_STATIC_PROP);
                        } catch (IssueException $exception) {
                            Issue::maybeEmitInstance(
                                $code_base,
                                $context,
                                $exception->getIssueInstance()
                            );
                        } catch (\Exception $exception) {
                            // If we can't figure out what kind of a call
                            // this is, don't worry about it
                        }
                    } else {
                        // This is stuff like `Class->$foo`. I'm ignoring
                        // it.
                    }
                }
            }
        }

        // Confirm the argument types are clean
        ArgumentType::analyze(
            $method,
            $node,
            $context,
            $code_base
        );

        // Take another pass over pass-by-reference parameters
        // and assign types to passed in variables
        foreach ($argument_list as $i => $argument) {
            if (!$argument instanceof \ast\Node) {
                continue;
            }
            $parameter = $method->getParameterForCaller($i);

            if (!$parameter) {
                continue;
            }

            $kind = $argument->kind;
            if ($kind === \ast\AST_CLOSURE) {
                if (Config::get_track_references()) {
                    $this->trackReferenceToClosure($argument);
                }
            }

            // If the parameter is pass-by-reference and we're
            // passing a variable in, see if we should pass
            // the parameter and variable types to eachother
            if ($parameter->isPassByReference()) {
                $this->analyzePassByReferenceArgument(
                    $code_base,
                    $context,
                    $argument,
                    $argument_list,
                    $method,
                    $parameter
                );
            }
        }

        // If we're in quick mode, don't retest methods based on
        // parameter types passed in
        if (Config::get_quick_mode()) {
            return;
        }

        if (!$method->needsRecursiveAnalysis()) {
            return;
        }

        // Re-analyze the method with the types of the arguments
        // being passed in.
        $this->analyzeMethodWithArgumentTypes(
            $node->children['args'],
            $method
        );
    }

    /**
     * @return void
     */
    private function analyzePassByReferenceArgument(
        CodeBase $code_base,
        Context $context,
        Node $argument,
        array $argument_list,
        FunctionInterface $method,
        Parameter $parameter
    ) {
        $variable = null;
        $kind = $argument->kind;
        if ($kind === \ast\AST_VAR) {
            try {
                $variable = (new ContextNode(
                    $code_base,
                    $context,
                    $argument
                ))->getOrCreateVariable();
            } catch (NodeException $e) {
                // E.g. `function_accepting_reference(${$varName})` - Phan can't analyze outer type of ${$varName}
                return;
            }
        } elseif ($kind === \ast\AST_STATIC_PROP
            || $kind === \ast\AST_PROP
        ) {
            $property_name = $argument->children['prop'];

            if (\is_string($property_name)) {
                // We don't do anything with it; just create it
                // if it doesn't exist
                try {
                    $variable = (new ContextNode(
                        $code_base,
                        $context,
                        $argument
                    ))->getOrCreateProperty($argument->children['prop'], $argument->kind == \ast\AST_STATIC_PROP);
                    $variable->addReference($context);
                } catch (IssueException $exception) {
                    Issue::maybeEmitInstance(
                        $code_base,
                        $context,
                        $exception->getIssueInstance()
                    );
                } catch (\Exception $exception) {
                    // If we can't figure out what kind of a call
                    // this is, don't worry about it
                }
            } else {
                // This is stuff like `Class->$foo`. I'm ignoring
                // it.
            }
        }

        if ($variable) {
            $reference_parameter_type = $parameter->getNonVariadicUnionType();
            switch ($parameter->getReferenceType()) {
                case Parameter::REFERENCE_WRITE_ONLY:
                    static $preg_match_fqsen = null;
                    if ($preg_match_fqsen === null) {
                        $preg_match_fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString('preg_match');
                    }
                    // TODO: Make this configurable with a plugin
                    if ($method->getFQSEN() === $preg_match_fqsen) {
                        $this->analyzePregMatch($argument_list, $variable);
                    } else {
                        // The previous value is being ignored, and being replaced.
                        $variable->setUnionType(
                            $reference_parameter_type
                        );
                    }
                    break;
                case Parameter::REFERENCE_READ_WRITE:
                    $variable_type = $variable->getUnionType();
                    if ($variable_type->isEmpty()) {
                        // if Phan doesn't know the variable type,
                        // then guess that the variable is the type of the reference
                        // when analyzing the following statements.
                        $variable->setUnionType(
                            $reference_parameter_type
                        );
                    } elseif (!$variable_type->canCastToUnionType($reference_parameter_type)) {
                        // Phan already warned about incompatible types.
                        // But analyze the following statements as if it could have been the type expected,
                        // to reduce false positives.
                        $variable->setUnionType($variable->getUnionType()->withUnionType(
                            $reference_parameter_type
                        ));
                    }
                    // don't modify - assume the function takes the same type in that it returns,
                    // and we want to preserve generic array types for sorting functions (May change later on)
                    // TODO: Check type compatibility earlier, and don't modify?
                    break;
                case Parameter::REFERENCE_DEFAULT:
                default:
                    // We have no idea what type of reference this is.
                    // Probably user defined code.
                    $variable->setUnionType($variable->getUnionType()->withUnionType(
                        $reference_parameter_type
                    ));
                    break;
            }
        }
    }

    /**
     * @param Variable|Property $variable
     */
    private function analyzePregMatch(array $argument_list, $variable)
    {
        $string_array_type = null;
        $array_type = null;
        $shape_array_type = null;
        if ($string_array_type === null) {
            // Note: Patterns **can** have named subpatterns
            $string_array_type = UnionType::fromFullyQualifiedString('string[]');
            $array_type        = UnionType::fromFullyQualifiedString('array');
            $shape_array_type  = UnionType::fromFullyQualifiedString('array{0:string,1:int}[]');
        }
        if (\count($argument_list) <= 3) {
            $variable->setUnionType($string_array_type);
            return;
        }
        $offset_flags_node = $argument_list[3];
        $bit = (new ContextNode($this->code_base, $this->context, $offset_flags_node))->getEquivalentPHPScalarValue();
        if (!\is_int($bit)) {
            return $array_type;
        }
        if ($bit & PREG_OFFSET_CAPTURE) {
            return $shape_array_type;
        }
        return $string_array_type;
    }

    /**
     * @return void
     */
    private function trackReferenceToClosure(Node $argument)
    {
        try {
            $inner_context = $this->context->withLineNumberStart($argument->lineno ?? 0);
            $method = (new ContextNode(
                $this->code_base,
                $inner_context,
                $argument
            ))->getClosure();

            $method->addReference($inner_context);
        } catch (\Exception $exception) {
            // Swallow it
        }
    }

    /**
     * Replace the method's parameter types with the argument
     * types and re-analyze the method.
     *
     * This is used when analyzing callbacks and closures, e.g. in array_map.
     *
     * @param array<int,UnionType> $argument_types
     * An AST node listing the arguments
     *
     * @param FunctionInterface $method
     * The method or function being called
     *
     * @return void
     *
     * @see analyzeMethodWithArgumentTypes (Which takes AST nodes)
     */
    public function analyzeCallableWithArgumentTypes(
        array $argument_types,
        FunctionInterface $method
    ) {
        if (!$method->needsRecursiveAnalysis()) {
            return;
        }

        // Don't re-analyze recursive methods. That doesn't go well.
        if ($this->context->isInFunctionLikeScope()
            && $method->getFQSEN() === $this->context->getFunctionLikeFQSEN()
        ) {
            return;
        }

        $original_method_scope = $method->getInternalScope();
        $method->setInternalScope(clone($original_method_scope));
        try {
            // Even though we don't modify the parameter list, we still need to know the types
            // -- as an optimization, we don't run quick mode again if the types didn't change?
            $parameter_list = \array_map(function (Parameter $parameter) {
                return clone($parameter);
            }, $method->getParameterList());

            foreach ($parameter_list as $i => $parameter_clone) {
                if (!isset($argument_types[$i]) && $parameter_clone->hasDefaultValue()) {
                    $parameter_type = $parameter_clone->getDefaultValueType();
                    if ($parameter_type->isType(NullType::instance(false))) {
                        // Treat a parameter default of null the same way as passing null to that parameter
                        // (Add null to the list of possibilities)
                        $parameter_clone->addUnionType($parameter_type);
                    } else {
                        // For other types (E.g. string), just replace the union type.
                        $parameter_clone->setUnionType($parameter_type);
                    }
                }

                // Add the parameter to the scope
                $method->getInternalScope()->addVariable(
                    $parameter_clone->asNonVariadic()
                );

                // If there's no parameter at that offset, we may be in
                // a ParamTooMany situation. That is caught elsewhere.
                if (!isset($argument_types[$i])
                    || !$parameter_clone->getNonVariadicUnionType()->isEmpty()
                ) {
                    continue;
                }

                $this->updateParameterTypeByArgument(
                    $method,
                    $parameter_clone,
                    null,  // TODO: Can array_map/array_filter accept closures with references? Consider warning?
                    $argument_types[$i],
                    $parameter_list,
                    $i
                );
            }
            foreach ($parameter_list as $parameter_clone) {
                if ($parameter_clone->isVariadic()) {
                    // We're using this parameter clone to analyze the **inside** of the method, it's never seen on the outside.
                    // Convert it immediately.
                    // TODO: Add tests of variadic references, fix those if necessary.
                    $method->getInternalScope()->addVariable(
                        $parameter_clone->cloneAsNonVariadic()
                    );
                }
            }

            // Now that we know something about the parameters used
            // to call the method, we can reanalyze the method with
            // the types of the parameter
            $method->analyzeWithNewParams($method->getContext(), $this->code_base, $parameter_list);
        } finally {
            $method->setInternalScope($original_method_scope);
        }
    }

    /**
     * Replace the method's parameter types with the argument
     * types and re-analyze the method.
     *
     * @param Node $argument_list_node
     * An AST node listing the arguments
     *
     * @param FunctionInterface $method
     * The method or function being called
     * Precondition: $method->needsRecursiveAnalysis() === false
     *
     * @return void
     *
     * TODO: deduplicate code.
     */
    private function analyzeMethodWithArgumentTypes(
        Node $argument_list_node,
        FunctionInterface $method
    ) {
        // Don't re-analyze recursive methods. That doesn't go
        // well.
        if ($this->context->isInFunctionLikeScope()
            && $method->getFQSEN() === $this->context->getFunctionLikeFQSEN()
        ) {
            return;
        }


        $original_method_scope = $method->getInternalScope();
        $method->setInternalScope(clone($original_method_scope));

        try {
            // Even though we don't modify the parameter list, we still need to know the types
            // -- as an optimization, we don't run quick mode again if the types didn't change?
            $parameter_list = \array_map(function (Parameter $parameter) : Parameter {
                return $parameter->cloneAsNonVariadic();
            }, $method->getParameterList());

            // always resolve all arguments outside of quick mode to detect undefined variables, other problems in call arguments.
            // Fixes https://github.com/phan/phan/issues/583
            $argument_types = [];
            foreach ($argument_list_node->children as $i => $argument) {
                if (!$argument) {
                    continue;
                }
                // Determine the type of the argument at position $i
                $argument_types[$i] = UnionTypeVisitor::unionTypeFromNode(
                    $this->code_base,
                    $this->context,
                    $argument,
                    true
                );
            }

            foreach ($parameter_list as $i => $parameter_clone) {
                assert($parameter_clone instanceof Parameter);
                $argument = $argument_list_node->children[$i] ?? null;

                if (!$argument
                    && $parameter_clone->hasDefaultValue()
                ) {
                    $parameter_type = $parameter_clone->getDefaultValueType();
                    if ($parameter_type->isType(NullType::instance(false))) {
                        // Treat a parameter default of null the same way as passing null to that parameter
                        // (Add null to the list of possibilities)
                        $parameter_clone->addUnionType($parameter_type);
                    } else {
                        // For other types (E.g. string), just replace the union type.
                        $parameter_clone->setUnionType($parameter_type);
                    }
                }

                // Add the parameter to the scope
                $method->getInternalScope()->addVariable(
                    $parameter_clone
                );

                // If there's no parameter at that offset, we may be in
                // a ParamTooMany situation. That is caught elsewhere.
                if (!$argument
                    || !$parameter_clone->getUnionType()->isEmpty()
                ) {
                    continue;
                }

                $this->updateParameterTypeByArgument(
                    $method,
                    $parameter_clone,
                    $argument,
                    $argument_types[$i],
                    $parameter_list,
                    $i
                );
            }
            foreach ($parameter_list as $parameter_clone) {
                if ($parameter_clone->isVariadic()) {
                    // We're using this parameter clone to analyze the **inside** of the method, it's never seen on the outside.
                    // Convert it immediately.
                    // TODO: Add tests of variadic references, fix those if necessary.
                    $method->getInternalScope()->addVariable(
                        $parameter_clone->cloneAsNonVariadic()
                    );
                }
            }

            // Now that we know something about the parameters used
            // to call the method, we can reanalyze the method with
            // the types of the parameter
            $method->analyzeWithNewParams($method->getContext(), $this->code_base, $parameter_list);
        } finally {
            $method->setInternalScope($original_method_scope);
        }
    }

    /**
     * @param FunctionInterface $method
     * The method that we're updating parameter types for
     *
     * @param Parameter $parameter
     * The parameter that we're updating
     *
     * @param Node|mixed $argument
     * The argument whose type we'd like to replace the
     * parameter type with.
     *
     * @param UnionType $argument_type
     * The type of $argument
     *
     * @param int $parameter_offset
     * The offset of the parameter on the method's
     * signature.
     *
     * @return void
     */
    private function updateParameterTypeByArgument(
        FunctionInterface $method,
        Parameter $parameter,
        $argument,
        UnionType $argument_type,
        array &$parameter_list,
        int $parameter_offset
    ) {
        // Then set the new type on that parameter based
        // on the argument's type. We'll use this to
        // retest the method with the passed in types
        // TODO: if $argument_type is non-empty and !isType(NullType), instead use setUnionType?

        // For https://github.com/phan/phan/issues/1525 : Collapse array shapes into generic arrays before recursively analyzing a method.
        if (!$parameter->isCloneOfVariadic()) {
            $parameter->addUnionType(
                $argument_type->withFlattenedArrayShapeTypeInstances()
            );
        } else {
            $parameter->addUnionType(
                $argument_type->withFlattenedArrayShapeTypeInstances()->asGenericArrayTypes(GenericArrayType::KEY_INT)
            );
        }

        // If we're passing by reference, get the variable
        // we're dealing with wrapped up and shoved into
        // the scope of the method
        if (!$parameter->isPassByReference()) {
            // Overwrite the method's variable representation
            // of the parameter with the parameter with the
            // new type
            $method->getInternalScope()->addVariable(
                $parameter
            );

            return;
        }

        // At this point we're dealing with a pass-by-reference
        // parameter.

        // For now, give up and work on it later.
        //
        // TODO (Issue #376): It's possible to have a
        // parameter `&...$args`. Analysing that is going to
        // be a problem. Is it possible to create
        // `PassByReferenceVariableCollection extends Variable`
        // or something similar?
        if ($parameter->isVariadic()) {
            return;
        }

        if (!$argument instanceof \ast\Node) {
            return;
        }

        $variable = null;
        if ($argument->kind == \ast\AST_VAR) {
            $variable = (new ContextNode(
                $this->code_base,
                $this->context,
                $argument
            ))->getOrCreateVariable();
        } elseif ($argument->kind == \ast\AST_STATIC_PROP) {
            try {
                // TODO: shouldn't call getOrCreateProperty for a static property. You can't create a static property.
                $variable = (new ContextNode(
                    $this->code_base,
                    $this->context,
                    $argument
                ))->getOrCreateProperty(
                    $argument->children['prop'] ?? '',
                    true
                );
            } catch (UnanalyzableException $exception) {
                // Ignore it. There's nothing we can do. (E.g. the class name for the static property fetch couldn't be determined.
            }
        }

        // If we couldn't find a variable, give up
        if (!$variable) {
            return;
        }

        $pass_by_reference_variable =
            new PassByReferenceVariable(
                $parameter,
                $variable
            );

        // Add it to the (cloned) scope of the function wrapped
        // in a way that makes it addressable as the
        // parameter its mimicking
        $method->getInternalScope()->addVariable(
            $pass_by_reference_variable
        );
        $parameter_list[$parameter_offset] = $pass_by_reference_variable;
    }

    /**
     * @param Node $node
     * A node to check to see if it's a no-op
     *
     * @param string $issue_type
     * A message to emit if it's a no-op
     *
     * @return void
     */
    private function analyzeNoOp(Node $node, string $issue_type)
    {
        if ((\end($this->parent_node_list)->kind ?? null) === \ast\AST_STMT_LIST) {
            $this->emitIssue(
                $issue_type,
                $node->lineno
            );
        }
    }

    /**
     * @param Node $node
     * A decl to check to see if it's only effect
     * is the throw an exception
     *
     * @return bool
     * True when the decl can only throw an exception or return or exit()
     */
    private function declOnlyThrows(Node $node) : bool
    {
        return BlockExitStatusChecker::willUnconditionallyThrowOrReturn($node->children['stmts']);
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\AnalysisVisitor;
use Phan\Language\Context;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedGlobalConstantName;
use Phan\Language\FQSEN\FullyQualifiedGlobalStructuralElement;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use ast\Node;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 * @phan-file-suppress PhanPartialTypeMismatchArgumentInternal
 */
abstract class ScopeVisitor extends AnalysisVisitor
{

    /**
     * @param CodeBase $code_base
     * The global code base holding all state
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     */
    /*
    public function __construct(
        CodeBase $code_base,
        Context $context
    ) {
        parent::__construct($code_base, $context);
    }
     */

    /**
     * Default visitor for node kinds that do not have
     * an overriding method
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function visit(Node $node) : Context
    {
        // Many nodes don't change the context and we
        // don't need to read them.
        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_DECLARE`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitDeclare(Node $node) : Context
    {
        $declares = $node->children['declares'];
        $name = $declares->children[0]->children['name'];
        $value = $declares->children[0]->children['value'];
        if ('strict_types' === $name && is_int($value)) {
            return $this->context->withStrictTypes($value);
        }

        return $this->context;
    }

    /**
     * Visit a node with kind `\ast\AST_NAMESPACE`
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new context resulting from parsing the node
     */
    public function visitNamespace(Node $node) : Context
    {
        $namespace = '\\' . (string)$node->children['name'];
        return $this->context->withNamespace($namespace);
    }

    /**
     * Visit a node with kind `\ast\AST_GROUP_USE`
     * such as `use \ast\Node;`.
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitGroupUse(Node $node) : Context
    {
        $children = $node->children;

        $prefix = \array_shift($children);

        $context = $this->context;

        $alias_target_map = $this->aliasTargetMapFromUseNode(
            $children['uses'],
            $prefix,
            $node->flags ?? 0
        );
        foreach ($alias_target_map as $alias => list($flags, $target, $lineno)) {
            $context = $context->withNamespaceMap(
                $flags,
                $alias,
                $target,
                $lineno
            );
        }

        return $context;
    }

    /**
     * Visit a node with kind `\ast\AST_USE`
     * such as `use \ast\Node;`.
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitUse(Node $node) : Context
    {
        $context = $this->context;

        foreach ($this->aliasTargetMapFromUseNode($node) as $alias => list($flags, $target, $lineno)) {
            $context = $context->withNamespaceMap(
                $node->flags ?: $flags,
                $alias,
                $target,
                $lineno
            );
        }

        return $context;
    }

    /**
     * @param Node $node
     * The node with the use statement
     *
     * @param int $flags
     * An optional node flag specifying the type
     * of the use clause.
     *
     * @return array<string,array{0:int,1:FullyQualifiedGlobalStructuralElement,2:int}>
     * A map from alias to target
     *
     * @suppress PhanPartialTypeMismatchReturn TODO: investigate
     */
    private function aliasTargetMapFromUseNode(
        Node $node,
        string $prefix = '',
        int $flags = 0
    ) : array {
        \assert(
            $node->kind == \ast\AST_USE,
            'Method takes AST_USE nodes'
        );

        $map = [];
        foreach ($node->children as $child_node) {
            $target = $child_node->children['name'];

            if (empty($child_node->children['alias'])) {
                if (($pos = \strrpos($target, '\\'))!==false) {
                    $alias = \substr($target, $pos + 1);
                } else {
                    $alias = $target;
                }
            } else {
                $alias = $child_node->children['alias'];
            }
            if (!\is_string($alias)) {
                // Should be impossible
                continue;
            }

            // if AST_USE does not have any flags set, then its AST_USE_ELEM
            // children will (this will be for AST_GROUP_USE)

            // The 'use' type can be defined on the `AST_GROUP_USE` node, the
            // `AST_USE_ELEM` or on the child element.
            $use_flag = $flags ?: $node->flags ?: $child_node->flags;

            if ($use_flag === \ast\flags\USE_FUNCTION) {
                $parts = \explode('\\', $target);
                $function_name = \array_pop($parts);
                $target = FullyQualifiedFunctionName::make(
                    $prefix . '\\' . implode('\\', $parts),
                    $function_name
                );
            } elseif ($use_flag === \ast\flags\USE_CONST) {
                $parts = \explode('\\', $target);
                $name = \array_pop($parts);
                $target = FullyQualifiedGlobalConstantName::make(
                    $prefix . '\\' . implode('\\', $parts),
                    $name
                );
            } elseif ($use_flag === \ast\flags\USE_NORMAL) {
                $target = FullyQualifiedClassName::fromFullyQualifiedString(
                    $prefix . '\\' . $target
                );
            } else {
                // If we get to this spot and don't know what
                // kind of a use clause we're dealing with, its
                // likely that this is a `USE` node which is
                // a child of a `GROUP_USE` and we already
                // handled it when analyzing the parent
                // node.
                continue;
            }

            $map[$alias] = [$use_flag, $target, $child_node->lineno];
        }

        return $map;
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\ContextNode;
use Phan\AST\UnionTypeVisitor;
use Phan\AST\Visitor\KindVisitorImplementation;
use Phan\BlockAnalysisVisitor;
use Phan\CodeBase;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\Variable;
use Phan\Language\Type;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\FloatType;
use Phan\Language\Type\IntType;
use Phan\Language\Type\IterableType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\ResourceType;
use Phan\Language\Type\ScalarType;
use Phan\Language\Type\StringType;
use Phan\Language\UnionType;
use Phan\Language\UnionTypeBuilder;
use ast\Node;
use ast\flags;
use Closure;

// TODO: Make $x != null remove FalseType and NullType from $x
// TODO: Make $x > 0, $x < 0, $x >= 50, etc.  remove FalseType and NullType from $x
// TODO: if (a || b || c || d) might get really slow, due to creating both ConditionVisitor and NegatedConditionVisitor
class NegatedConditionVisitor extends KindVisitorImplementation
{
    use ConditionVisitorUtil;

    /**
     * @var Context
     * The context in which the node we're going to be looking
     * at exits.
     */
    protected $context;

    /**
     * @param CodeBase $code_base
     * A code base needs to be passed in because we require
     * it to be initialized before any classes or files are
     * loaded.
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     */
    public function __construct(
        CodeBase $code_base,
        Context $context
    ) {
        $this->code_base = $code_base;
        $this->context = $context;
    }

    /**
     * Default visitor for node kinds that do not have
     * an overriding method
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visit(Node $node) : Context
    {
        $this->checkVariablesDefined($node);
        return $this->context;
    }

    /**
     * Check if variables from within a generic condition are defined.
     * @param Node $node
     * A node to parse
     * @return void
     */
    private function checkVariablesDefined(Node $node)
    {
        while ($node->kind === \ast\AST_UNARY_OP) {
            $node = $node->children['expr'];
            if (!($node instanceof Node)) {
                return;
            }
        }
        // Get the type just to make sure everything
        // is defined.
        UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node,
            true
        );
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitBinaryOp(Node $node) : Context
    {
        $flags = ($node->flags ?? 0);
        if ($flags === flags\BINARY_BOOL_OR) {
            return $this->analyzeShortCircuitingOr($node->children['left'], $node->children['right']);
        } elseif ($flags === flags\BINARY_BOOL_AND) {
            return $this->analyzeShortCircuitingAnd($node->children['left'], $node->children['right']);
        } elseif ($flags === flags\BINARY_IS_IDENTICAL) {
            $this->checkVariablesDefined($node);
            return $this->analyzeAndUpdateToBeNotIdentical($node->children['left'], $node->children['right']);
        } elseif ($flags === flags\BINARY_IS_EQUAL) {
            $this->checkVariablesDefined($node);
            return $this->analyzeAndUpdateToBeNotEqual($node->children['left'], $node->children['right']);
        } elseif ($flags === flags\BINARY_IS_NOT_IDENTICAL || $flags === flags\BINARY_IS_NOT_EQUAL) {
            $this->checkVariablesDefined($node);
            // TODO: Add a different function for IS_NOT_EQUAL, e.g. analysis of != null should be different from !== null (First would remove FalseType)
            return $this->analyzeAndUpdateToBeIdentical($node->children['left'], $node->children['right']);
        }
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitOr(Node $node) : Context
    {
        return $this->analyzeShortCircuitingOr($node->children['left'], $node->children['right']);
    }

    /**
     * Helper method
     * @param Node|mixed $left
     * a Node or non-node to parse (possibly an AST literal)
     *
     * @param Node|mixed $right
     * a Node or non-node to parse (possibly an AST literal)
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    private function analyzeShortCircuitingAnd($left, $right) : Context
    {
        // Analyze expressions such as if (!(is_string($x) || is_int($x)))
        // which would be equivalent to if (!is_string($x)) { if (!is_int($x)) { ... }}

        // Aside: If left/right is not a node, left/right is a literal such as a number/string, and is either always truthy or always falsey.
        // Inside of this conditional may be dead or redundant code.

        // Aside: If left/right is not a node, left/right is a literal such as a number/string, and is either always truthy or always falsey.
        // Inside of this conditional may be dead or redundant code.
        if (!($left instanceof Node)) {
            if (!$left) {
                return $this->context;
            }
            return $this($right);
        }
        if (!($right instanceof Node)) {
            if (!$right) {
                return $this->context;
            }
            return $this($left);
        }
        $code_base = $this->code_base;
        $context = $this->context;
        $left_false_context = (new NegatedConditionVisitor($code_base, $context))($left);
        $left_true_context = (new ConditionVisitor($code_base, $context))($left);
        // We analyze the right hand side of `cond($x) && cond2($x)` as if `cond($x)` was true.
        $right_false_context = (new NegatedConditionVisitor($code_base, $left_true_context))($right);
        // When the NegatedConditionVisitor is false, at least one of the left or right contexts must be false.
        // (NegatedConditionVisitor returns a context for when the input Node's value was falsey)
        return (new ContextMergeVisitor($context, [$left_false_context, $right_false_context]))->combineChildContextList();
    }

    /**
     * Helper method
     * @param Node|mixed $left
     * a Node or non-node to parse (possibly an AST literal)
     *
     * @param Node|mixed $right
     * a Node or non-node to parse (possibly an AST literal)
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    private function analyzeShortCircuitingOr($left, $right) : Context
    {
        // Analyze expressions such as if (!(is_string($x) || is_int($x)))
        // which would be equivalent to if (!is_string($x)) { if (!is_int($x)) { ... }}

        // Aside: If left/right is not a node, left/right is a literal such as a number/string, and is either always truthy or always falsey.
        // Inside of this conditional may be dead or redundant code.
        if ($left instanceof Node) {
            $this->context = $this($left);
        }
        if ($right instanceof Node) {
            return $this($right);
        }
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitUnaryOp(Node $node) : Context
    {
        $expr_node = $node->children['expr'];
        $flags = $node->flags;
        if ($flags !== flags\UNARY_BOOL_NOT) {
            if ($expr_node instanceof Node) {
                if ($flags === flags\UNARY_SILENCE) {
                    return $this->__invoke($expr_node);
                }
                $this->checkVariablesDefined($expr_node);
            }
            return $this->context;
        }
        // TODO: Emit dead code issue for non-nodes
        if ($expr_node instanceof Node) {
            // The negated version of a NegatedConditionVisitor is a ConditionVisitor.
            return (new ConditionVisitor($this->code_base, $this->context))($expr_node);
        }
        return $this->context;
    }

    /**
     * Look at elements of the form `is_array($v)` and modify
     * the type of the variable to negate that check.
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitCall(Node $node)
    {
        $raw_function_name = self::getFunctionName($node);
        if (!\is_string($raw_function_name)) {
            return $this->context;
        }
        $args = $node->children['args']->children;
        foreach ($args as $arg) {
            if ($arg instanceof Node) {
                $this->checkVariablesDefined($arg);
            }
        }

        $context = $this->context;
        $function_name = \strtolower(\ltrim($raw_function_name, '\\'));
        if (self::isArgumentListWithVarAsFirstArgument($args)) {
            if (\count($args) !== 1) {
                /*if (\strcasecmp($function_name, 'is_a') === 0) {
                    return $this->analyzeNegationOfVariableIsA($args, $context);
                }*/
                return $context;
            }
            static $map;
            if ($map === null) {
                $map = self::createNegationCallbackMap();
            }
            // TODO: Make this generic to all type assertions? E.g. if (!is_string($x)) removes 'string' from type, makes '?string' (nullable) into 'null'.
            // This may be redundant in some places if AST canonicalization is used, but still useful in some places
            // TODO: Make this generic so that it can be used in the 'else' branches?
            $callback = $map[$function_name] ?? null;
            if ($callback === null) {
                return $context;
            }
            return $callback($this, $args[0], $context);
        }
        if ($function_name === 'array_key_exists') {
            return $this->analyzeArrayKeyExistsNegation($args);
        }
        return $context;
    }

    public function visitVar(Node $node)
    {
        $this->checkVariablesDefined($node);
        return $this->removeTruthyFromVariable($node, $this->context, false);
    }

    private function analyzeArrayKeyExistsNegation(array $args) : Context
    {
        $context = $this->context;
        if (\count($args) !== 2) {
            return $context;
        }
        $var_node = $args[1];
        if (($var_node->kind ?? null) !== \ast\AST_VAR) {
            return $context;
        }
        $var_name = $var_node->children['name'];
        if (!\is_string($var_name)) {
            return $context;
        }
        if (!$context->getScope()->hasVariableWithName($var_name)) {
            return $context;
        }
        $variable = $context->getScope()->getVariableByName($var_name);

        if ($variable->getUnionType()->hasTopLevelArrayShapeTypeInstances()) {
            $context = $this->withNullOrUnsetArrayShapeTypes($variable, $args[0], $context, true);
            $this->context = $context;
        }
        return $context;
    }

    // TODO: empty, isset

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitInstanceof(Node $node) : Context
    {
        //$this->checkVariablesDefined($node);
        // Only look at things of the form
        // `$variable instanceof ClassName`
        $expr_node = $node->children['expr'];
        $context = $this->context;
        if (!($expr_node instanceof Node) || $expr_node->kind !== \ast\AST_VAR) {
            return $context;
        }

        $code_base = $this->code_base;

        try {
            // Get the variable we're operating on
            $variable = $this->getVariableFromScope($expr_node, $context);
            if (\is_null($variable)) {
                return $context;
            }

            // Get the type that we're checking it against
            $class_node = $node->children['class'];
            $right_hand_union_type = UnionTypeVisitor::unionTypeFromNode(
                $code_base,
                $context,
                $class_node
            )->objectTypes();

            if ($right_hand_union_type->typeCount() !== 1) {
                return $context;
            }
            $right_hand_type = $right_hand_union_type->getTypeSet()[0];

            // TODO: Assert that instanceof right hand type is valid in NegatedConditionVisitor as well

            // Make a copy of the variable
            $variable = clone($variable);
            $new_variable_type = $variable->getUnionType()->withoutSubclassesOf($code_base, $right_hand_type);
            // See https://secure.php.net/instanceof -
            $variable->setUnionType($new_variable_type);

            // Overwrite the variable with its new type
            $context = $context->withScopeVariable(
                $variable
            );
        } catch (IssueException $exception) {
            Issue::maybeEmitInstance($code_base, $context, $exception->getIssueInstance());
        } catch (\Exception $exception) {
            // Swallow it
        }

        return $context;
    }

    /*
    private function analyzeNegationOfVariableIsA(array $args, Context $context) : Context
    {
        // TODO: implement
        return $context;
    }
     */

    /**
     * @return array<string,Closure> (NegatedConditionVisitor $cv, Node $var_node, Context $context) -> Context
     * @phan-return array<string,Closure(NegatedConditionVisitor,Node,Context):Context>
     */
    private static function createNegationCallbackMap() : array
    {
        $remove_null_cb = function (NegatedConditionVisitor $cv, Node $var_node, Context $context) : Context {
            return $cv->removeNullFromVariable($var_node, $context, false);
        };

        // Remove any Types from UnionType that are subclasses of $base_class_name
        $make_basic_negated_assertion_callback = static function (string $base_class_name) : Closure {
            return static function (NegatedConditionVisitor $cv, Node $var_node, Context $context) use ($base_class_name) : Context {
                return $cv->updateVariableWithConditionalFilter(
                    $var_node,
                    $context,
                    function (UnionType $union_type) use ($base_class_name) : bool {
                        return $union_type->hasTypeMatchingCallback(function (Type $type) use ($base_class_name) : bool {
                            return $type instanceof $base_class_name;
                        });
                    },
                    function (UnionType $union_type) use ($base_class_name) : UnionType {
                        $new_type_builder = new UnionTypeBuilder();
                        $has_null = false;
                        $has_other_nullable_types = false;
                        // Add types which are not instances of $base_class_name
                        foreach ($union_type->getTypeSet() as $type) {
                            if ($type instanceof $base_class_name) {
                                $has_null = $has_null || $type->getIsNullable();
                                continue;
                            }
                            assert($type instanceof Type);
                            $has_other_nullable_types = $has_other_nullable_types || $type->getIsNullable();
                            $new_type_builder->addType($type);
                        }
                        // Add Null if some of the rejected types were were nullable, and none of the accepted types were nullable
                        if ($has_null && !$has_other_nullable_types) {
                            $new_type_builder->addType(NullType::instance(false));
                        }
                        return $new_type_builder->getUnionType();
                    },
                    false
                );
            };
        };
        $remove_float_callback = $make_basic_negated_assertion_callback(FloatType::class);
        $remove_int_callback = $make_basic_negated_assertion_callback(IntType::class);
        $remove_scalar_callback = static function (NegatedConditionVisitor $cv, Node $var_node, Context $context) : Context {
            return $cv->updateVariableWithConditionalFilter(
                $var_node,
                $context,
                // if (!is_scalar($x)) removes scalar types from $x, but $x can still be null.
                function (UnionType $union_type) : bool {
                    return $union_type->hasTypeMatchingCallback(function (Type $type) : bool {
                        return ($type instanceof ScalarType) && !($type instanceof NullType);
                    });
                },
                function (UnionType $union_type) : UnionType {
                    $new_type_builder = new UnionTypeBuilder();
                    $has_null = false;
                    $has_other_nullable_types = false;
                    // Add types which are not scalars
                    foreach ($union_type->getTypeSet() as $type) {
                        if ($type instanceof ScalarType && !($type instanceof NullType)) {
                            $has_null = $has_null || $type->getIsNullable();
                            continue;
                        }
                        assert($type instanceof Type);
                        $has_other_nullable_types = $has_other_nullable_types || $type->getIsNullable();
                        $new_type_builder->addType($type);
                    }
                    // Add Null if some of the rejected types were were nullable, and none of the accepted types were nullable
                    if ($has_null && !$has_other_nullable_types) {
                        $new_type_builder->addType(NullType::instance(false));
                    }
                    return $new_type_builder->getUnionType();
                },
                false
            );
        };
        $remove_callable_callback = static function (NegatedConditionVisitor $cv, Node $var_node, Context $context) : Context {
            return $cv->updateVariableWithConditionalFilter(
                $var_node,
                $context,
                // if (!is_callable($x)) removes non-callable/closure types from $x.
                // TODO: Could check for __invoke()
                function (UnionType $union_type) : bool {
                    return $union_type->hasTypeMatchingCallback(function (Type $type) : bool {
                        return $type->isCallable();
                    });
                },
                function (UnionType $union_type) : UnionType {
                    $new_type_builder = new UnionTypeBuilder();
                    $has_null = false;
                    $has_other_nullable_types = false;
                    // Add types which are not callable
                    foreach ($union_type->getTypeSet() as $type) {
                        if ($type->isCallable()) {
                            $has_null = $has_null || $type->getIsNullable();
                            continue;
                        }
                        assert($type instanceof Type);
                        $has_other_nullable_types = $has_other_nullable_types || $type->getIsNullable();
                        $new_type_builder->addType($type);
                    }
                    // Add Null if some of the rejected types were were nullable, and none of the accepted types were nullable
                    if ($has_null && !$has_other_nullable_types) {
                        $new_type_builder->addType(NullType::instance(false));
                    }
                    return $new_type_builder->getUnionType();
                },
                false
            );
        };
        // The implementation of Traversable may change in the future (e.g. to support generics).
        // So use fromFullyQualifiedString()
        $traversable_type = Type::fromFullyQualifiedString('\Traversable');
        $remove_array_callback = static function (NegatedConditionVisitor $cv, Node $var_node, Context $context) use ($traversable_type) : Context {
            return $cv->updateVariableWithConditionalFilter(
                $var_node,
                $context,
                // if (!is_callable($x)) removes non-callable/closure types from $x.
                // TODO: Could check for __invoke()
                function (UnionType $union_type) : bool {
                    return $union_type->hasIterable();
                },
                function (UnionType $union_type) use ($traversable_type) : UnionType {
                    $new_type_builder = new UnionTypeBuilder();
                    $has_null = false;
                    $has_other_nullable_types = false;
                    // Add types which are not callable
                    foreach ($union_type->getTypeSet() as $type) {
                        if ($type instanceof ArrayType) {
                            $has_null = $has_null || $type->getIsNullable();
                            continue;
                        }

                        assert($type instanceof Type);
                        $has_other_nullable_types = $has_other_nullable_types || $type->getIsNullable();

                        if (\get_class($type) === IterableType::class) {
                            // An iterable that is not an object must be an array
                            $new_type_builder->addType($traversable_type->withIsNullable($type->getIsNullable()));
                            continue;
                        }
                        $new_type_builder->addType($type);
                    }
                    // Add Null if some of the rejected types were were nullable, and none of the accepted types were nullable
                    if ($has_null && !$has_other_nullable_types) {
                        $new_type_builder->addType(NullType::instance(false));
                    }
                    return $new_type_builder->getUnionType();
                },
                false
            );
        };
        $remove_object_callback = static function (NegatedConditionVisitor $cv, Node $var_node, Context $context) : Context {
            return $cv->updateVariableWithConditionalFilter(
                $var_node,
                $context,
                // if (!is_callable($x)) removes non-callable/closure types from $x.
                // TODO: Could check for __invoke()
                function (UnionType $union_type) : bool {
                    return $union_type->hasPossiblyObjectTypes();
                },
                function (UnionType $union_type) : UnionType {
                    $new_type_builder = new UnionTypeBuilder();
                    $has_null = false;
                    $has_other_nullable_types = false;
                    // Add types which are not callable
                    foreach ($union_type->getTypeSet() as $type) {
                        if ($type->isObject()) {
                            $has_null = $has_null || $type->getIsNullable();
                            continue;
                        }
                        assert($type instanceof Type);
                        $has_other_nullable_types = $has_other_nullable_types || $type->getIsNullable();

                        if (\get_class($type) === IterableType::class) {
                            // An iterable that is not an array must be a Traversable
                            $new_type_builder->addType(ArrayType::instance($type->getIsNullable()));
                            continue;
                        }
                        $new_type_builder->addType($type);
                    }
                    // Add Null if some of the rejected types were were nullable, and none of the accepted types were nullable
                    if ($has_null && !$has_other_nullable_types) {
                        $new_type_builder->addType(NullType::instance(false));
                    }
                    return $new_type_builder->getUnionType();
                },
                false
            );
        };

        return [
            'is_null' => $remove_null_cb,
            'is_array' => $remove_array_callback,
            // 'is_bool' => $make_basic_assertion_callback(BoolType::class),
            'is_callable' => $remove_callable_callback,
            'is_double' => $remove_float_callback,
            'is_float' => $remove_float_callback,
            'is_int' => $remove_int_callback,
            'is_integer' => $remove_int_callback,
            'is_iterable' => $make_basic_negated_assertion_callback(IterableType::class),  // TODO: Could keep basic array types and classes extending iterable
            'is_long' => $remove_int_callback,
            // 'is_numeric' => $make_basic_assertion_callback('string|int|float'),
            'is_object' => $remove_object_callback,
            'is_real' => $remove_float_callback,
            'is_resource' => $make_basic_negated_assertion_callback(ResourceType::class),
            'is_scalar' => $remove_scalar_callback,
            'is_string' => $make_basic_negated_assertion_callback(StringType::class),
        ];
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitCoalesce(Node $node) : Context
    {
        $this->checkVariablesDefined($node);
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitIsset(Node $node) : Context
    {
        $var_node = $node->children['var'];
        if (!($var_node instanceof Node)) {
            return $this->context;
        }
        if (($var_node->kind ?? null) !== \ast\AST_VAR) {
            return $this->checkComplexIsset($var_node);
        }
        // if (!isset($x))
        return $this->updateVariableWithNewType($var_node, $this->context, NullType::instance(false)->asUnionType(), true);
    }

    /**
     * Analyze expressions such as $x['offset'] inside of a negated isset type check
     */
    public function checkComplexIsset(Node $var_node)
    {
        $context = $this->context;
        if ($var_node->kind === \ast\AST_DIM) {
            $expr_node = $var_node;
            do {
                $parent_node = $expr_node;
                $expr_node = $expr_node->children['expr'];
                if (!($expr_node instanceof Node)) {
                    return $context;
                }
            } while ($expr_node->kind === \ast\AST_DIM);

            if ($expr_node->kind === \ast\AST_VAR) {
                $var_name = $expr_node->children['name'];
                if (!\is_string($var_name)) {
                    return $context;
                }
                if (!$context->getScope()->hasVariableWithName($var_name)) {
                    // e.g. assert(!isset($x['key'])) - $x may still be undefined.
                    return $context;
                }
                $variable = $context->getScope()->getVariableByName($var_name);
                $var_node_union_type = $variable->getUnionType();

                if ($var_node_union_type->hasTopLevelArrayShapeTypeInstances()) {
                    $context = $this->withNullOrUnsetArrayShapeTypes($variable, $parent_node->children['dim'], $context, false);
                    $this->context = $context;
                }
            }
        }
        return $context;
    }

    /**
     * @param Variable $variable the variable being modified by inferences from isset or array_key_exists
     * @param Node|string|float|int|bool $dim_node represents the dimension being accessed. (E.g. can be a literal or an AST_CONST, etc.
     * @param Context $context the context with inferences made prior to this condition
     * @suppress PhanPartialTypeMismatchArgument
     */
    private function withNullOrUnsetArrayShapeTypes(Variable $variable, $dim_node, Context $context, bool $remove_offset) : Context
    {
        $dim_value = $dim_node instanceof Node ? (new ContextNode($this->code_base, $this->context, $dim_node))->getEquivalentPHPScalarValue() : $dim_node;
        // TODO: detect and warn about null
        if (!\is_scalar($dim_value)) {
            return $context;
        }

        $union_type = $variable->getUnionType();
        $dim_union_type = UnionTypeVisitor::resolveArrayShapeElementTypesForOffset($union_type, $dim_value);
        if (!$dim_union_type) {
            // There are other types, this dimension does not exist yet.
            // Whether or not the union type already has array shape types, don't change the type
            return $context;
        } else {
            $variable = clone($variable);

            static $null_and_possibly_undefined = null;
            if ($null_and_possibly_undefined === null) {
                $null_and_possibly_undefined = NullType::instance(false)->asUnionType()->withIsPossiblyUndefined(true);
            }

            if ($remove_offset) {
                $new_union_type = $union_type->withoutArrayShapeField($dim_value);
            } else {
                $new_union_type = ArrayType::combineArrayShapeTypesWithField($union_type, $dim_value, $null_and_possibly_undefined);
            }
            $variable->setUnionType($new_union_type);

            // Overwrite the variable with its new type in this
            // scope without overwriting other scopes
            return $context->withScopeVariable(
                $variable
            );
            // TODO finish
        }
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitEmpty(Node $node) : Context
    {
        $context = $this->context;
        $var_node = $node->children['expr'];
        // if (!empty($x))
        if ($var_node instanceof Node && $var_node->kind === \ast\AST_VAR) {
            // Don't check if variables are defined - don't emit notices for if (!empty($x)) {}, etc.
            $var_name = $var_node->children['name'];
            if (is_string($var_name)) {
                if (!$context->getScope()->hasVariableWithName($var_name)) {
                    // Support analyzing cases such as `if (!empty($x)) { use($x); }`, or `assert(!empty($x))`
                    // (In the PHP language, empty($x) is equivalent to (!isset($x) || !$x))
                    $context->setScope($context->getScope()->withVariable(new Variable(
                        $context->withLineNumberStart($var_node->lineno ?? 0),
                        $var_name,
                        UnionType::empty(),
                        $var_node->flags ?? 0
                    )));
                }
                $context->setScope($context->getScope()->withVariable(new Variable(
                    $context->withLineNumberStart($var_node->lineno ?? 0),
                    $var_name,
                    UnionType::empty(),
                    $var_node->flags ?? 0
                )));
                return $this->removeFalseyFromVariable($var_node, $context, true);
            }
        }
        $this->checkVariablesDefined($node);
        return $context;
    }

    /**
     * @param Node $node
     * A node to parse
     * (Should be useful when analyzing for loops with no breaks (`for (; !is_string($x); ){...}, in the future))
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitExprList(Node $node) : Context
    {
        $children = $node->children;
        $count = \count($children);
        if ($count > 1) {
            foreach ($children as $sub_node) {
                --$count;
                if ($count > 0 && $sub_node instanceof Node) {
                    $this->checkVariablesDefined($sub_node);
                }
            }
        }
        // Only analyze the last expression in the expression list for (negation of) conditions.
        $last_expression = \end($node->children);
        if ($last_expression instanceof Node) {
            return $this($last_expression);
        } else {
            // TODO: emit no-op warning
            return $this->context;
        }
    }

    /**
     * Useful for analyzing `if ($x = foo() && $x->method())`
     *
     * TODO: Convert $x to empty/false/null types
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitAssign(Node $node) : Context
    {
        return (new BlockAnalysisVisitor($this->code_base, $this->context))->visitAssign($node);
    }

    /**
     * Useful for analyzing `if ($x =& foo() && $x->method())`
     * TODO: Convert $x to empty/false/null types
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitAssignRef(Node $node) : Context
    {
        return (new BlockAnalysisVisitor($this->code_base, $this->context))->visitAssignRef($node);
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\AnalysisVisitor;
use Phan\AST\ContextNode;
use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Config;
use Phan\Debug;
use Phan\Exception\CodeBaseException;
use Phan\Exception\IssueException;
use Phan\Exception\NodeException;
use Phan\Exception\UnanalyzableException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\PassByReferenceVariable;
use Phan\Language\Element\Parameter;
use Phan\Language\Element\Property;
use Phan\Language\Element\Variable;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\Type;
use Phan\Language\Type\ArrayShapeType;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\FalseType;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\IntType;
use Phan\Language\Type\MixedType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\StringType;
use Phan\Language\UnionType;
use ast\Node;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class AssignmentVisitor extends AnalysisVisitor
{
    /**
     * @var Node
     */
    private $assignment_node;

    /**
     * @var UnionType
     */
    private $right_type;

    /**
     * @var int
     * Depth of array parameters in this assignment, e.g. this is 1 for
     * for `$foo[3] = 42`, 0 for `$x = 2;`, etc.
     * We need to know this in order to decide
     * if we're replacing the union type
     * or if we're adding a type to the union type.
     */
    private $dim_depth;

    /**
     * @var ?UnionType
     * Non-null if this this assignment is to an array parameter such as
     * in `$foo[3] = 42` (type would be int). We need to know this in order to decide
     * to type check the assignment (e.g. array keys are int|string, string offsets are int)
     * type to the union type.
     *
     * Null for `$foo[] = 42` or when dim_depth is 0
     */
    private $dim_type;

    /**
     * @param CodeBase $code_base
     * The global code base we're operating within
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     *
     * @param Node $assignment_node
     * The AST node containing the assignment
     *
     * @param UnionType $right_type
     * The type of the element on the right side of the assignment
     *
     * @param int $dim_depth
     * Positive if this assignment is to an array parameter such as
     * in `$foo[3] = 42`. We need to know this in order to decide
     * if we're replacing the union type or if we're adding a
     * type to the union type.
     *
     * @param ?UnionType $dim_type
     * The type of the dimension.
     */
    public function __construct(
        CodeBase $code_base,
        Context $context,
        Node $assignment_node,
        UnionType $right_type,
        int $dim_depth = 0,
        UnionType $dim_type = null
    ) {
        parent::__construct($code_base, $context);

        $this->assignment_node = $assignment_node;
        $this->right_type = $right_type;
        $this->dim_depth = $dim_depth;
        $this->dim_type = $dim_type;  // null for `$x[] =` or when dim_depth is 0.
    }

    /**
     * Default visitor for node kinds that do not have
     * an overriding method
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visit(Node $node) : Context
    {
        throw new \AssertionError(
            "Unknown left side of assignment in {$this->context} with node type "
            . Debug::nodeName($node)
        );
    }

    /**
     * The following is an example of how this'd happen.
     * (TODO: Check if the right hand side is an object with offsetSet() or a reference?
     *
     * ```php
     * class C {
     *     function f() {
     *         return [ 24 ];
     *     }
     * }
     * (new C)->f()[1] = 42;
     * ```
     *
     * @param Node $unused_node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitMethodCall(Node $unused_node) : Context
    {
        return $this->context;
    }

    /**
     * The following is an example of how this'd happen.
     * TODO: Check that the left hand side is a reference or defines offsetSet()?
     *
     * ```php
     * function &f() {
     *     $x = [ 24 ]; return $x;
     * }
     * f()[1] = 42;
     * ```
     *
     * @param Node $unused_node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitCall(Node $unused_node) : Context
    {
        return $this->context;
    }

    /**
     * The following is an example of how this'd happen.
     *
     * ```php
     * class A{
     *     function &f() {
     *         $x = [ 24 ]; return $x;
     *     }
     * }
     * A::f()[1] = 42;
     * ```
     *
     * @param Node $unused_node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitStaticCall(Node $unused_node) : Context
    {
        return $this->context;
    }

    /**
     * This happens for code like the following
     * ```
     * list($a) = [1, 2, 3];
     * ```
     *
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitArray(Node $node) : Context
    {
        if ($this->right_type->hasTopLevelArrayShapeTypeInstances()) {
            $this->analyzeShapedArrayAssignment($node);
        } else {
            // common case
            $this->analyzeGenericArrayAssignment($node);
        }
        return $this->context;
    }

    /**
     * Analyzes code such as list($a) = [1, 2, 3];
     * @return void
     * @see $this->visitArray
     * @suppress PhanTypeMismatchDimAssignment deliberate for $key_set
     */
    private function analyzeShapedArrayAssignment(Node $node)
    {
        // Figure out the type of elements in the list
        $fallback_element_type = null;
        $get_fallback_element_type = function () use (&$fallback_element_type) : UnionType {
            return $fallback_element_type ?? ($fallback_element_type = $this->right_type->genericArrayElementTypes());
        };

        $expect_string_keys_lineno = false;
        $expect_int_keys_lineno = false;

        $key_set = [];

        foreach ($node->children ?? [] as $child_node) {
            // Some times folks like to pass a null to
            // a list to throw the element away. I'm not
            // here to judge.
            if (!($child_node instanceof Node)) {
                // Track the element that was thrown away.
                $key_set[] = true;
                continue;
            }

            // Get the key and value nodes for each
            // array element we're assigning to
            // TODO: Check key types are valid?
            $key_node = $child_node->children['key'];
            $key_value = null;

            if ($key_node === null) {
                $key_set[] = true;
                \end($key_set);
                $key_value = \key($key_set);

                $expect_int_keys_lineno = $child_node->lineno;  // list($x, $y) = ... is equivalent to list(0 => $x, 1 => $y) = ...
            } else {
                if ($key_node instanceof Node) {
                    $key_value = (new ContextNode($this->code_base, $this->context, $key_node))->getEquivalentPHPScalarValue();
                } else {
                    $key_value = $key_node;
                }
                if (\is_scalar($key_value)) {
                    $key_set[$key_value] = true;
                    if (\is_int($key_value)) {
                        $expect_int_keys_lineno = $child_node->lineno;
                    } elseif (\is_string($key_value)) {
                        $expect_string_keys_lineno = $child_node->lineno;
                    }
                } else {
                    $key_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $key_node);
                    $key_type_enum = GenericArrayType::keyTypeFromUnionTypeValues($key_type);
                    // TODO: Warn about types that can't cast to int|string
                    if ($key_type_enum === GenericArrayType::KEY_INT) {
                        $expect_int_keys_lineno = $child_node->lineno;
                    } elseif ($key_type_enum === GenericArrayType::KEY_STRING) {
                        $expect_string_keys_lineno = $child_node->lineno;
                    }
                }
            }

            if (\is_scalar($key_value)) {
                $element_type = UnionTypeVisitor::resolveArrayShapeElementTypesForOffset($this->right_type, $key_value);
                if ($element_type === null) {
                    $element_type = $get_fallback_element_type();
                } elseif ($element_type === false) {
                    $this->emitIssue(
                        Issue::TypeInvalidDimOffsetArrayDestructuring,
                        $child_node->lineno,
                        json_encode($key_value),
                        (string)$this->right_type
                    );
                    $element_type = $get_fallback_element_type();
                }
            } else {
                $element_type = $get_fallback_element_type();
            }


            $value_node = $child_node->children['value'];

            if ($value_node->kind == \ast\AST_VAR) {
                $variable = Variable::fromNodeInContext(
                    $value_node,
                    $this->context,
                    $this->code_base,
                    false
                );

                // Set the element type on each element of
                // the list
                $variable->setUnionType($element_type);

                // Note that we're not creating a new scope, just
                // adding variables to the existing scope
                $this->context->addScopeVariable($variable);
            } elseif ($value_node->kind == \ast\AST_PROP) {
                try {
                    $property = (new ContextNode(
                        $this->code_base,
                        $this->context,
                        $value_node
                    ))->getProperty($value_node->children['prop'], false);

                    // Set the element type on each element of
                    // the list
                    $property->setUnionType($element_type);
                } catch (UnanalyzableException $exception) {
                    // Ignore it. There's nothing we can do.
                } catch (NodeException $exception) {
                    // Ignore it. There's nothing we can do.
                } catch (IssueException $exception) {
                    Issue::maybeEmitInstance(
                        $this->code_base,
                        $this->context,
                        $exception->getIssueInstance()
                    );
                    continue;
                }
            } else {
                $this->context = (new AssignmentVisitor(
                    $this->code_base,
                    $this->context,
                    $node,
                    $element_type,
                    0
                ))->__invoke($value_node);
            }
        }

        if (!Config::getValue('scalar_array_key_cast')) {
            $this->checkMismatchArrayDestructuringKey($expect_int_keys_lineno, $expect_string_keys_lineno);
        }
    }

    /**
     * Analyzes code such as list($a) = function_returning_array();
     * @return void
     * @see $this->visitArray
     */
    private function analyzeGenericArrayAssignment(Node $node)
    {
        // Figure out the type of elements in the list
        $element_type =
            $this->right_type->genericArrayElementTypes();

        $expect_string_keys_lineno = false;
        $expect_int_keys_lineno = false;

        $scalar_array_key_cast = Config::getValue('scalar_array_key_cast');

        foreach ($node->children ?? [] as $child_node) {
            // Some times folks like to pass a null to
            // a list to throw the element away. I'm not
            // here to judge.
            if (!($child_node instanceof Node)) {
                continue;
            }

            // Get the key and value nodes for each
            // array element we're assigning to
            // TODO: Check key types are valid?
            $key_node = $child_node->children['key'];
            if (!$scalar_array_key_cast) {
                if ($key_node === null) {
                    $expect_int_keys_lineno = $child_node->lineno;  // list($x, $y) = ... is equivalent to list(0 => $x, 1 => $y) = ...
                } else {
                    $key_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $key_node);
                    $key_type_enum = GenericArrayType::keyTypeFromUnionTypeValues($key_type);
                    // TODO: Warn about types that can't cast to int|string
                    if ($key_type_enum === GenericArrayType::KEY_INT) {
                        $expect_int_keys_lineno = $child_node->lineno;
                    } elseif ($key_type_enum === GenericArrayType::KEY_STRING) {
                        $expect_string_keys_lineno = $child_node->lineno;
                    }
                }
            }

            $value_node = $child_node->children['value'];

            if ($value_node->kind == \ast\AST_VAR) {
                $variable = Variable::fromNodeInContext(
                    $value_node,
                    $this->context,
                    $this->code_base,
                    false
                );

                // Set the element type on each element of
                // the list
                $variable->setUnionType($element_type);

                // Note that we're not creating a new scope, just
                // adding variables to the existing scope
                $this->context->addScopeVariable($variable);
            } elseif ($value_node->kind == \ast\AST_PROP) {
                try {
                    $property = (new ContextNode(
                        $this->code_base,
                        $this->context,
                        $value_node
                    ))->getProperty($value_node->children['prop'], false);

                    // Set the element type on each element of
                    // the list
                    $property->setUnionType($element_type);
                } catch (UnanalyzableException $exception) {
                    // Ignore it. There's nothing we can do.
                } catch (NodeException $exception) {
                    // Ignore it. There's nothing we can do.
                } catch (IssueException $exception) {
                    Issue::maybeEmitInstance(
                        $this->code_base,
                        $this->context,
                        $exception->getIssueInstance()
                    );
                    continue;
                }
            } else {
                $this->context = (new AssignmentVisitor(
                    $this->code_base,
                    $this->context,
                    $node,
                    $element_type,
                    0
                ))->__invoke($value_node);
            }
        }

        $this->checkMismatchArrayDestructuringKey($expect_int_keys_lineno, $expect_string_keys_lineno);
    }

    /**
     * @param int|false $expect_int_keys_lineno
     * @param int|false $expect_string_keys_lineno
     * @return void
     */
    private function checkMismatchArrayDestructuringKey($expect_int_keys_lineno, $expect_string_keys_lineno)
    {
        if ($expect_int_keys_lineno !== false || $expect_string_keys_lineno !== false) {
            $right_hand_key_type = GenericArrayType::keyTypeFromUnionTypeKeys($this->right_type);
            if ($expect_int_keys_lineno !== false && ($right_hand_key_type & GenericArrayType::KEY_INT) === 0) {
                Issue::maybeEmit(
                    $this->code_base,
                    $this->context,
                    Issue::TypeMismatchArrayDestructuringKey,
                    $expect_int_keys_lineno,
                    'int',
                    'string'
                );
            } elseif ($expect_string_keys_lineno !== false && ($right_hand_key_type & GenericArrayType::KEY_STRING) === 0) {
                Issue::maybeEmit(
                    $this->code_base,
                    $this->context,
                    Issue::TypeMismatchArrayDestructuringKey,
                    $expect_string_keys_lineno,
                    'string',
                    'int'
                );
            }
        }
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitDim(Node $node) : Context
    {
        $expr_node = $node->children['expr'];
        if ($expr_node->kind == \ast\AST_VAR) {
            $variable_name = (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ))->getVariableName();

            if (Variable::isHardcodedVariableInScopeWithName($variable_name, $this->context->isInGlobalScope())) {
                return $this->analyzeSuperglobalDim($node, $variable_name);
            }
        }

        // TODO: Check if the unionType is valid for the []
        // For most types, it should be int|string, but SplObjectStorage and a few user-defined types will be exceptions.
        // Infer it from offsetSet?
        $dim_node = $node->children['dim'];
        $dim_value = null;
        if ($dim_node instanceof Node) {
            // TODO: Use ContextNode to infer dim_value
            $dim_type = UnionTypeVisitor::unionTypeFromNode(
                $this->code_base,
                $this->context,
                $dim_node
            );
        } elseif (\is_scalar($dim_node) && $dim_node !== null) {
            $dim_value = $dim_node;
            $dim_type = Type::fromObject($dim_node)->asUnionType();
        } else {
            // TODO: If the array shape has only one set of keys, then appending should add to that shape? Possibly not a common use case.
            $dim_type = null;
        }

        if ($dim_value !== null) {
            $right_type = ArrayShapeType::fromFieldTypes([
                $dim_value => $this->right_type,
            ], false)->asUnionType();
        } else {
            // Make the right type a generic (i.e. int -> int[])
            if ($dim_type !== null) {
                $key_type_enum = GenericArrayType::keyTypeFromUnionTypeValues($dim_type);
            } elseif ($dim_node !== null) {
                $key_type_enum = GenericArrayType::KEY_MIXED;
            } else {
                $key_type_enum = GenericArrayType::KEY_INT;
            }
            $right_inner_type = $this->right_type;
            if ($right_inner_type->isEmpty()) {
                if ($key_type_enum === GenericArrayType::KEY_MIXED) {
                    $right_type = ArrayType::instance(false)->asUnionType();
                } else {
                    $right_type = GenericArrayType::fromElementType(MixedType::instance(false), false, $key_type_enum)->asUnionType();
                }
            } else {
                $right_type = $right_inner_type->asGenericArrayTypes($key_type_enum);
            }
        }

        // Recurse into whatever we're []'ing
        $context = (new AssignmentVisitor(
            $this->code_base,
            $this->context,
            $node,
            $right_type,
            $this->dim_depth + 1,
            $dim_type
        ))->__invoke($expr_node);

        return $context;
    }

    /**
     * Analyze an assignment where $variable_name is a superglobal, and return the new context.
     * May create a new variable in $this->context.
     * TODO: Emit issues if the assignment is incompatible with the pre-existing type?
     */
    private function analyzeSuperglobalDim(Node $node, string $variable_name) : Context
    {
        $dim = $node->children['dim'];
        if ('GLOBALS' === $variable_name) {
            if (!\is_string($dim)) {
                // You're not going to believe this, but I just
                // found a piece of code like $GLOBALS[mt_rand()].
                // Super weird, right?
                return $this->context;
            }

            if (Variable::isHardcodedVariableInScopeWithName($dim, $this->context->isInGlobalScope())) {
                // Don't override types of superglobals such as $_POST, $argv through $_GLOBALS['_POST'] = expr either. TODO: Warn.
                return $this->context;
            }

            $variable = new Variable(
                $this->context,
                $dim,
                $this->right_type,
                $node->flags ?? 0
            );

            $this->context->addGlobalScopeVariable(
                $variable
            );
        }
        // TODO: Assignment sanity checks.
        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse, for an instance property.
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitProp(Node $node) : Context
    {
        // Get class list first, warn if the class list is invalid.
        try {
            $class_list = (new ContextNode(
                $this->code_base,
                $this->context,
                $node->children['expr']
            ))->getClassList(false, ContextNode::CLASS_LIST_ACCEPT_OBJECT, Issue::TypeExpectedObjectPropAccess);
        } catch (CodeBaseException $exception) {
            // This really shouldn't happen since the code
            // parsed cleanly. This should fatal.
            // throw $exception;
            return $this->context;
        } catch (\Exception $exception) {
            // If we can't figure out what kind of a class
            // this is, don't worry about it
            return $this->context;
        }

        $property_name = $node->children['prop'];

        // Things like $foo->$bar
        if (!\is_string($property_name)) {
            return $this->context;
        }

        foreach ($class_list as $clazz) {
            // Check to see if this class has the property or
            // a setter
            if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
                if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
                    continue;
                }
            }

            try {
                $property = $clazz->getPropertyByNameInContext(
                    $this->code_base,
                    $property_name,
                    $this->context,
                    false
                );
            } catch (IssueException $exception) {
                Issue::maybeEmitInstance(
                    $this->code_base,
                    $this->context,
                    $exception->getIssueInstance()
                );
                return $this->context;
            }
            return $this->analyzePropAssignment($clazz, $property, $node);
        }

        // Check if it is a built in class with dynamic properties but (possibly) no __set, such as SimpleXMLElement or stdClass or V8Js
        $is_class_with_arbitrary_types = isset($class_list[0]) ? $class_list[0]->getHasDynamicProperties($this->code_base) : false;

        if ($is_class_with_arbitrary_types || Config::getValue('allow_missing_properties')) {
            try {
                // Create the property
                $property = (new ContextNode(
                    $this->code_base,
                    $this->context,
                    $node
                ))->getOrCreateProperty($property_name, false);

                $this->addTypesToProperty($property, $node);
            } catch (\Exception $exception) {
                // swallow it
            }
        } elseif (!empty($class_list)) {
            $this->emitIssue(
                Issue::UndeclaredProperty,
                $node->lineno ?? 0,
                "{$class_list[0]->getFQSEN()}->$property_name"
            );
        } else {
            // If we hit this part, we couldn't figure out
            // the class, so we ignore the issue
        }

        return $this->context;
    }

    /**
     * This analyzes an assignment to an instance or static property.
     */
    private function analyzePropAssignment(Clazz $clazz, Property $property, Node $node) : Context
    {
        // TODO: Iterate over individual types, don't look at the whole type at once?

        // If we're assigning to an array element then we don't
        // know what the constitutation of the parameter is
        // outside of the scope of this assignment, so we add to
        // its union type rather than replace it.
        $property_union_type = $property->getUnionType();
        if ($this->dim_depth > 0) {
            if ($this->right_type->canCastToExpandedUnionType(
                $property_union_type,
                $this->code_base
            )) {
                $this->addTypesToProperty($property, $node);
                if (Config::get_strict_property_checking() && $this->right_type->typeCount() > 1) {
                    $this->analyzePropertyAssignmentStrict($clazz, $property, $this->right_type, $node);
                }
            } elseif ($property_union_type->asExpandedTypes($this->code_base)->hasArrayAccess()) {
                // Add any type if this is a subclass with array access.
                $this->addTypesToProperty($property, $node);
            } else {
                // Convert array shape types to generic arrays to reduce false positive PhanTypeMismatchProperty instances.

                // TODO: If the codebase explicitly sets a phpdoc array shape type on a property assignment,
                // then preserve the array shape type.
                $new_types = $this->typeCheckDimAssignment($property_union_type, $node)->withFlattenedArrayShapeTypeInstances();

                // TODO: More precise than canCastToExpandedUnionType
                if (!$new_types->canCastToExpandedUnionType(
                    $property_union_type,
                    $this->code_base
                )) {
                    // TODO: Don't emit if array shape type is compatible with the original value of $property_union_type
                    $this->emitIssue(
                        Issue::TypeMismatchProperty,
                        $node->lineno ?? 0,
                        (string)$new_types,
                        "{$clazz->getFQSEN()}::{$property->getName()}",
                        (string)$property_union_type
                    );
                } else {
                    if (Config::get_strict_property_checking() && $this->right_type->typeCount() > 1) {
                        $this->analyzePropertyAssignmentStrict($clazz, $property, $this->right_type, $node);
                    }
                    $this->right_type = $new_types;
                    $this->addTypesToProperty($property, $node);
                }
            }
            return $this->context;
        } elseif ($clazz->isPHPInternal() && $clazz->getFQSEN() !== FullyQualifiedClassName::getStdClassFQSEN()) {
            // We don't want to modify the types of internal classes such as \ast\Node even if they are compatible
            // This would result in unpredictable results, and types which are more specific than they really are.
            // stdClass is an exception to this, for issues such as https://github.com/phan/phan/pull/700
            return $this->context;
        } else {
            // This is a regular assignment, not an assignment to an offset
            if (!$this->right_type->canCastToExpandedUnionType(
                $property_union_type,
                $this->code_base
            )
                && !($this->right_type->hasTypeInBoolFamily() && $property_union_type->hasTypeInBoolFamily())
                && !$clazz->getHasDynamicProperties($this->code_base)
            ) {
                // TODO: optionally, change the message from "::" to "->"?
                $this->emitIssue(
                    Issue::TypeMismatchProperty,
                    $node->lineno ?? 0,
                    (string)$this->right_type,
                    "{$clazz->getFQSEN()}::{$property->getName()}",
                    (string)$property_union_type
                );
                return $this->context;
            }

            if (Config::get_strict_property_checking() && $this->right_type->typeCount() > 1) {
                $this->analyzePropertyAssignmentStrict($clazz, $property, $this->right_type, $node);
            }
        }

        // After having checked it, add this type to it
        $this->addTypesToProperty($property, $node);

        return $this->context;
    }

    private function analyzePropertyAssignmentStrict(Clazz $clazz, Property $property, UnionType $assignment_type, Node $node)
    {
        $type_set = $assignment_type->getTypeSet();
        \assert(\count($type_set) >= 2);

        $property_union_type = $property->getUnionType();
        if ($property_union_type->hasTemplateType()) {
            $property_union_type = $property_union_type->asExpandedTypes($this->code_base);
        }

        $mismatch_type_set = UnionType::empty();
        $mismatch_expanded_types = null;

        // For the strict
        foreach ($type_set as $type) {
            // Expand it to include all parent types up the chain
            $individual_type_expanded = $type->asExpandedTypes($this->code_base);

            // See if the argument can be cast to the
            // parameter
            if (!$individual_type_expanded->canCastToUnionType(
                $property_union_type
            )) {
                $mismatch_type_set = $mismatch_type_set->withType($type);
                if ($mismatch_expanded_types === null) {
                    // Warn about the first type
                    $mismatch_expanded_types = $individual_type_expanded;
                }
            }
        }


        if ($mismatch_expanded_types === null) {
            // No mismatches
            return;
        }

        $this->emitIssue(
            self::getStrictIssueType($mismatch_type_set),
            $node->lineno ?? 0,
            (string)$this->right_type,
            "{$clazz->getFQSEN()}::{$property->getName()}",
            (string)$property_union_type,
            (string)$mismatch_expanded_types
        );
    }

    private static function getStrictIssueType(UnionType $union_type) : string
    {
        if ($union_type->typeCount() === 1) {
            $type = $union_type->getTypeSet()[0];
            if ($type instanceof NullType) {
                return Issue::PossiblyNullTypeMismatchProperty;
            }
            if ($type instanceof FalseType) {
                return Issue::PossiblyFalseTypeMismatchProperty;
            }
        }
        return Issue::PartialTypeMismatchProperty;
    }

    /**
     * @param Property $property - The property which should have types added to it
     *
     * @return void
     */
    private function addTypesToProperty(Property $property, Node $node)
    {
        $property_types = $property->getUnionType();
        if ($property_types->isEmpty()) {
            // TODO: Be more precise?
            $property->setUnionType($this->right_type->withFlattenedArrayShapeTypeInstances());
            return;
        }
        if ($this->dim_depth > 0) {
            $new_types = $this->typeCheckDimAssignment($property_types, $node);
        } else {
            $new_types = $this->right_type;
        }
        // Don't add MixedType to a non-empty property - It makes inferences on that property useless.
        if ($new_types->hasType(MixedType::instance(false))) {
            $new_types = $new_types->withoutType(MixedType::instance(false));
        }
        $new_types = $new_types->withFlattenedArrayShapeTypeInstances();

        // TODO: Add an option to check individual types, not just the whole union type?
        //       If that is implemented, verify that generic arrays will properly cast to regular arrays (public $x = [];)
        $property->setUnionType($property_types->withUnionType($new_types));
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     *
     * @see $this->visitProp
     */
    public function visitStaticProp(Node $node) : Context
    {
        $property_name = $node->children['prop'];

        // Things like self::${$x}
        if (!\is_string($property_name)) {
            return $this->context;
        }

        try {
            $class_list = (new ContextNode(
                $this->code_base,
                $this->context,
                $node->children['class']
            ))->getClassList(false, ContextNode::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME, Issue::TypeExpectedObjectStaticPropAccess);
        } catch (CodeBaseException $exception) {
            // This really shouldn't happen since the code
            // parsed cleanly. This should fatal.
            // throw $exception;
            return $this->context;
        } catch (\Exception $exception) {
            // If we can't figure out what kind of a class
            // this is, don't worry about it
            return $this->context;
        }

        foreach ($class_list as $clazz) {
            // Check to see if this class has the property
            if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
                continue;
            }

            try {
                // Look for static properties with that $property_name
                $property = $clazz->getPropertyByNameInContext(
                    $this->code_base,
                    $property_name,
                    $this->context,
                    true
                );
            } catch (IssueException $exception) {
                Issue::maybeEmitInstance(
                    $this->code_base,
                    $this->context,
                    $exception->getIssueInstance()
                );
                return $this->context;
            }

            return $this->analyzePropAssignment($clazz, $property, $node);
        }

        if (!empty($class_list)) {
            $this->emitIssue(
                Issue::UndeclaredStaticProperty,
                $node->lineno ?? 0,
                $property_name,
                (string)$class_list[0]->getFQSEN()
            );
        } else {
            // If we hit this part, we couldn't figure out
            // the class, so we ignore the issue
        }

        return $this->context;
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitVar(Node $node) : Context
    {
        try {
            $variable_name = (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ))->getVariableName();
        } catch (IssueException $exception) {
            Issue::maybeEmitInstance(
                $this->code_base,
                $this->context,
                $exception->getIssueInstance()
            );
            return $this->context;
        }
        // Don't analyze variables when we can't determine their names.
        if ($variable_name === '') {
            return $this->context;
        }

        // Check to see if the variable already exists
        if ($this->context->getScope()->hasVariableWithName(
            $variable_name
        )) {
            $variable =
                $this->context->getScope()->getVariableByName(
                    $variable_name
                );

            // If we're assigning to an array element then we don't
            // know what the constitutation of the parameter is
            // outside of the scope of this assignment, so we add to
            // its union type rather than replace it.
            if ($this->dim_depth > 0) {
                $old_variable_union_type = $variable->getUnionType();
                $right_type = $this->typeCheckDimAssignment($old_variable_union_type, $node);
                if ($old_variable_union_type->isEmpty()) {
                    $old_variable_union_type = ArrayType::instance(false)->asUnionType();
                }
                // TODO: Make the behavior more precise for $x['a']['b'] = ...; when $x is an array shape.
                if ($this->dim_depth > 1 || ($old_variable_union_type->hasTopLevelNonArrayShapeTypeInstances() || $right_type->hasTopLevelNonArrayShapeTypeInstances() || $right_type->isEmpty())) {
                    $variable->setUnionType($old_variable_union_type->withUnionType(
                        $right_type
                    ));
                } else {
                    $variable->setUnionType(ArrayType::combineArrayTypesOverriding(
                        $right_type,
                        $old_variable_union_type
                    ));
                }
            } else {
                // If the variable isn't a pass-by-reference parameter
                // we clone it so as to not disturb its previous types
                // as we replace it.
                if ($variable instanceof Parameter) {
                    if (!$variable->isPassByReference()) {
                        $variable = clone($variable);
                    }
                } elseif (!($variable instanceof PassByReferenceVariable)) {
                    $variable = clone($variable);
                }

                $variable->setUnionType($this->right_type);
            }

            $this->context->addScopeVariable(
                $variable
            );

            return $this->context;
        } else {
            // no such variable exists, check for invalid array Dim access
            if ($this->dim_depth > 0) {
                $this->emitIssue(
                    Issue::UndeclaredVariableDim,
                    $node->lineno ?? 0,
                    $variable_name
                );
            }
        }

        $variable = Variable::fromNodeInContext(
            $this->assignment_node,
            $this->context,
            $this->code_base,
            false
        );
        if ($this->dim_depth > 0) {
            // Reduce false positives: If $variable did not already exist, assume it may already have other array fields
            // (e.g. in a loop, or in the global scope)
            $variable->setUnionType($this->right_type->withType(ArrayType::instance(false)));
        } else {
            // Set that type on the variable
            $variable->setUnionType(
                $this->right_type
            );
        }

        // Note that we're not creating a new scope, just
        // adding variables to the existing scope
        $this->context->addScopeVariable($variable);

        return $this->context;
    }

    /**
     * @param UnionType $assign_type - The type which is being added to
     * @return UnionType - Usually the unmodified UnionType. Sometimes, the adjusted type, e.g. for string modification.
     */
    public function typeCheckDimAssignment(UnionType $assign_type, Node $node) : UnionType
    {
        static $int_or_string_type = null;
        static $int_type = null;
        static $string_type = null;
        static $mixed_type = null;
        static $string_array_type = null;
        if ($int_or_string_type === null) {
            $int_or_string_type = UnionType::fromFullyQualifiedString('int|string');
            $int_type = IntType::instance(false);
            $string_type = StringType::instance(false);
            $mixed_type = MixedType::instance(false);
            $string_array_type = UnionType::fromFullyQualifiedString('string[]');
        }
        $dim_type = $this->dim_type;
        $right_type = $this->right_type;
        if ($assign_type->isEmpty() || ($assign_type->hasGenericArray() && !$assign_type->asExpandedTypes($this->code_base)->hasArrayAccess())) {
            // For empty union types or 'array', expect the provided dimension to be able to cast to int|string
            if ($dim_type && !$dim_type->isEmpty() && !$dim_type->canCastToUnionType($int_or_string_type)) {
                $this->emitIssue(
                    Issue::TypeMismatchDimAssignment,
                    $node->lineno ?? 0,
                    (string)$assign_type,
                    (string)$dim_type,
                    (string)$int_or_string_type
                );
            }
            return $right_type;
        }
        if (!$assign_type->asExpandedTypes($this->code_base)->hasArrayLike()) {
            if ($assign_type->hasType($string_type)) {
                // Are we assigning to a variable/property of type 'string' (with no ArrayAccess or array types)?
                if (\is_null($dim_type)) {
                    $this->emitIssue(
                        Issue::TypeMismatchDimEmpty,
                        $node->lineno ?? 0,
                        (string)$assign_type,
                        (string)$int_type
                    );
                } elseif (!$dim_type->isEmpty() && !$dim_type->hasType($int_type)) {
                    $this->emitIssue(
                        Issue::TypeMismatchDimAssignment,
                        $node->lineno,
                        (string)$assign_type,
                        (string)$dim_type,
                        (string)$int_type
                    );
                } else {
                    if ($right_type->canCastToUnionType($string_array_type)) {
                        // e.g. $a = 'aaa'; $a[0] = 'x';
                        // (Currently special casing this, not handling deeper dimensions)
                        return StringType::instance(false)->asUnionType();
                    }
                }
            } elseif (!$assign_type->hasType($mixed_type)) {
                // Imitate the check in UnionTypeVisitor, don't warn for mixed, etc.
                $this->emitIssue(
                    Issue::TypeArraySuspicious,
                    $node->lineno,
                    (string)$assign_type
                );
            }
        }
        return $right_type;
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\IssueException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Type\IntType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\StringType;
use Phan\Language\Element\Variable;
use Phan\Language\UnionType;
use ast\Node;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgumentInternal
 */
trait ConditionVisitorUtil
{
    /** @var CodeBase */
    protected $code_base;

    /**
     * @var Context
     * The context in which the node we're going to be looking
     * at exits.
     */
    protected $context;

    /**
     * Remove any types which are definitely truthy from that variable (objects, TrueType, ResourceType, etc.)
     * E.g. if (empty($x)) {} would result in this.
     * Note that Phan can't know some scalars are not an int/string/float, since 0/""/"0"/0.0/[] are empty.
     * (Remove arrays anyway)
     */
    protected final function removeTruthyFromVariable(Node $var_node, Context $context, bool $suppress_issues) : Context
    {
        return $this->updateVariableWithConditionalFilter(
            $var_node,
            $context,
            function (UnionType $type) : bool {
                return $type->containsTruthy();
            },
            function (UnionType $type) : UnionType {
                return $type->nonTruthyClone();
            },
            $suppress_issues
        );
    }

    // Remove any types which are definitely falsey from that variable (NullType, FalseType)
    protected final function removeFalseyFromVariable(Node $var_node, Context $context, bool $suppress_issues) : Context
    {
        return $this->updateVariableWithConditionalFilter(
            $var_node,
            $context,
            function (UnionType $type) : bool {
                return $type->containsFalsey();
            },
            function (UnionType $type) : UnionType {
                return $type->nonFalseyClone();
            },
            $suppress_issues
        );
    }


    protected final function removeNullFromVariable(Node $var_node, Context $context, bool $suppress_issues) : Context
    {
        return $this->updateVariableWithConditionalFilter(
            $var_node,
            $context,
            function (UnionType $type) : bool {
                return $type->containsNullable();
            },
            function (UnionType $type) : UnionType {
                return $type->nonNullableClone();
            },
            $suppress_issues
        );
    }

    protected final function removeFalseFromVariable(Node $var_node, Context $context) : Context
    {
        return $this->updateVariableWithConditionalFilter(
            $var_node,
            $context,
            function (UnionType $type) : bool {
                return $type->containsFalse();
            },
            function (UnionType $type) : UnionType {
                return $type->nonFalseClone();
            },
            false
        );
    }

    protected final function removeTrueFromVariable(Node $var_node, Context $context) : Context
    {
        return $this->updateVariableWithConditionalFilter(
            $var_node,
            $context,
            function (UnionType $type) : bool {
                return $type->containsTrue();
            },
            function (UnionType $type) : UnionType {
                return $type->nonTrueClone();
            },
            false
        );
    }

    /**
     * If the inferred UnionType makes $should_filter_cb return true
     * (indicating there are Types to be removed from the UnionType or altered),
     * then replace the UnionType with the modified UnionType which $filter_union_type_cb returns,
     * and update the context.
     *
     * Note: It's expected that $should_filter_cb returns false on the new UnionType of that variable.
     */
    protected final function updateVariableWithConditionalFilter(
        Node $var_node,
        Context $context,
        \Closure $should_filter_cb,
        \Closure $filter_union_type_cb,
        bool $suppress_issues
    ) : Context {
        try {
            // Get the variable we're operating on
            $variable = $this->getVariableFromScope($var_node, $context);
            if (\is_null($variable)) {
                return $context;
            }

            $union_type = $variable->getUnionType();
            if (!$should_filter_cb($union_type)) {
                return $context;
            }

            // Make a copy of the variable
            $variable = clone($variable);

            $variable->setUnionType(
                $filter_union_type_cb($union_type)
            );

            // Overwrite the variable with its new type in this
            // scope without overwriting other scopes
            $context = $context->withScopeVariable(
                $variable
            );
        } catch (IssueException $exception) {
            if (!$suppress_issues) {
                Issue::maybeEmitInstance($this->code_base, $context, $exception->getIssueInstance());
            }
        } catch (\Exception $exception) {
            // Swallow it
        }
        return $context;
    }

    protected final function updateVariableWithNewType(
        Node $var_node,
        Context $context,
        UnionType $new_union_type,
        bool $suppress_issues
    ) : Context {
        try {
            // Get the variable we're operating on
            $variable = $this->getVariableFromScope($var_node, $context);
            if (\is_null($variable)) {
                return $context;
            }

            // Make a copy of the variable
            $variable = clone($variable);

            $variable->setUnionType(
                $new_union_type
            );

            // Overwrite the variable with its new type in this
            // scope without overwriting other scopes
            $context = $context->withScopeVariable(
                $variable
            );
        } catch (IssueException $exception) {
            if (!$suppress_issues) {
                Issue::maybeEmitInstance($this->code_base, $context, $exception->getIssueInstance());
            }
        } catch (\Exception $exception) {
            // Swallow it
        }
        return $context;
    }

    /**
     * @param Node $var_node
     * @param Node|int|float|string $expr
     * @return Context - Constant after inferring type from an expression such as `if ($x === 'literal')`
     */
    protected final function updateVariableToBeIdentical(
        Node $var_node,
        $expr,
        Context $context
    ) : Context {
        $var_name = $var_node->children['name'] ?? null;
        // Don't analyze variables such as $$a
        if (\is_string($var_name) && $var_name) {
            try {
                $exprType = UnionTypeVisitor::unionTypeFromLiteralOrConstant($this->code_base, $context, $expr);
                if ($exprType) {
                    // Get the variable we're operating on
                    $variable = $this->getVariableFromScope($var_node, $context);
                    if (\is_null($variable)) {
                        return $context;
                    }

                    // Make a copy of the variable
                    $variable = clone($variable);

                    $variable->setUnionType($exprType);

                    // Overwrite the variable with its new type in this
                    // scope without overwriting other scopes
                    $context = $context->withScopeVariable(
                        $variable
                    );
                    return $context;
                }
            } catch (\Exception $e) {
                // Swallow it (E.g. IssueException for undefined variable)
            }
        }
        return $context;
    }

    /**
     * @param Node $var_node
     * @param Node|int|float|string $expr
     * @return Context - Constant after inferring type from an expression such as `if ($x !== 'literal')`
     */
    protected final function updateVariableToBeNotIdentical(
        Node $var_node,
        $expr,
        Context $context
    ) : Context {
        $var_name = $var_node->children['name'] ?? null;
        if (\is_string($var_name)) {
            try {
                if ($expr instanceof Node && $expr->kind === \ast\AST_CONST) {
                    $exprNameNode = $expr->children['name'];
                    if ($exprNameNode->kind === \ast\AST_NAME) {
                        // Currently, only add this inference when we're absolutely sure this is a check rejecting null/false/true
                        $exprName = $exprNameNode->children['name'];
                        switch (\strtolower($exprName)) {
                            case 'null':
                                return $this->removeNullFromVariable($var_node, $context, false);
                            case 'false':
                                return $this->removeFalseFromVariable($var_node, $context);
                            case 'true':
                                return $this->removeTrueFromVariable($var_node, $context);
                        }
                    }
                }
            } catch (\Exception $e) {
                // Swallow it (E.g. IssueException for undefined variable)
            }
        }
        return $context;
    }

    /**
     * @param Node $var_node
     * @param Node|int|float|string $expr
     * @return Context - Constant after inferring type from an expression such as `if ($x !== 'literal')`
     */
    protected final function updateVariableToBeNotEqual(
        Node $var_node,
        $expr,
        Context $context
    ) : Context {
        $var_name = $var_node->children['name'] ?? null;
        // http://php.net/manual/en/types.comparisons.php#types.comparisions-loose
        if (\is_string($var_name)) {
            try {
                if ($expr instanceof Node) {
                    if ($expr->kind === \ast\AST_CONST) {
                        $exprNameNode = $expr->children['name'];
                        if ($exprNameNode->kind === \ast\AST_NAME) {
                            // Currently, only add this inference when we're absolutely sure this is a check rejecting null/false/true
                            $exprName = $exprNameNode->children['name'];
                            switch (\strtolower($exprName)) {
                                case 'null':
                                case 'false':
                                    return $this->removeFalseyFromVariable($var_node, $context, false);
                                case 'true':
                                    return $this->removeTrueFromVariable($var_node, $context);
                            }
                        }
                    }
                } elseif ($expr == false) {
                    if ($expr == null) {
                        return $this->removeFalseyFromVariable($var_node, $context, false);
                    }
                    return $this->removeFalseFromVariable($var_node, $context);
                    ;
                } elseif ($expr == null) {
                    return $this->removeNullFromVariable($var_node, $context, false);
                } elseif ($expr == true) {  // e.g. 1, "1", -1
                    return $this->removeTrueFromVariable($var_node, $context);
                }
            } catch (\Exception $e) {
                // Swallow it (E.g. IssueException for undefined variable)
            }
        }
        return $context;
    }

    /**
     * @param Node|int|float|string $left
     * @param Node|int|float|string $right
     * @return Context - Constant after inferring type from an expression such as `if ($x !== false)`
     */
    protected function analyzeAndUpdateToBeIdentical($left, $right) : Context
    {
        if (($left instanceof Node) && $left->kind === \ast\AST_VAR) {
            // e.g. `if (!($x !== null))` or `if ($x === null)`
            return $this->updateVariableToBeIdentical($left, $right, $this->context);
        } elseif (($right instanceof Node) && $right->kind === \ast\AST_VAR) {
            // e.g. if (!(null !== $x))
            return $this->updateVariableToBeIdentical($right, $left, $this->context);
        }
        return $this->context;
    }

    /**
     * @param Node|int|float|string $left
     * @param Node|int|float|string $right
     * @return Context - Constant after inferring type from an expression such as `if ($x !== false)`
     */
    protected function analyzeAndUpdateToBeNotIdentical($left, $right) : Context
    {
        if (($left instanceof Node) && $left->kind === \ast\AST_VAR) {
            // e.g. if ($x !== null)
            return $this->updateVariableToBeNotIdentical($left, $right, $this->context);
        } elseif (($right instanceof Node) && $right->kind === \ast\AST_VAR) {
            // e.g. if (null !== $x)
            return $this->updateVariableToBeNotIdentical($right, $left, $this->context);
        }
        return $this->context;
    }

    /**
     * @param Node|int|float|string $left
     * @param Node|int|float|string $right
     * @return Context - Constant after inferring type from an expression such as `if ($x == 'literal')`
     */
    protected function analyzeAndUpdateToBeNotEqual($left, $right) : Context
    {
        if (($left instanceof Node) && $left->kind === \ast\AST_VAR) {
            // e.g. if (!($x == null))
            return $this->updateVariableToBeNotEqual($left, $right, $this->context);
        } elseif (($right instanceof Node) && $right->kind === \ast\AST_VAR) {
            // e.g. if (!(null == $x))
            return $this->updateVariableToBeNotEqual($right, $left, $this->context);
        }
        return $this->context;
    }

    /**
     * @return ?Variable - Returns null if the variable is undeclared and ignore_undeclared_variables_in_global_scope applies.
     *                     or if assertions won't be applied?
     * @throws IssueException if variable is undeclared and not ignored.
     * @see UnionTypeVisitor->visitVar
     *
     * TODO: support assertions on superglobals, within the current file scope?
     */
    protected final function getVariableFromScope(Node $var_node, Context $context)
    {
        if ($var_node->kind !== \ast\AST_VAR) {
            return null;
        }
        $var_name_node = $var_node->children['name'] ?? null;

        if ($var_name_node instanceof Node) {
            // This is nonsense. Give up, but check if it's a type other than int/string.
            // (e.g. to catch typos such as $$this->foo = bar;)
            $name_node_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $context, $var_name_node, true);
            static $int_or_string_type;
            if ($int_or_string_type === null) {
                $int_or_string_type = new UnionType([
                    StringType::instance(false),
                    IntType::instance(false),
                    NullType::instance(false),
                ]);
            }
            if (!$name_node_type->canCastToUnionType($int_or_string_type)) {
                Issue::maybeEmit($this->code_base, $context, Issue::TypeSuspiciousIndirectVariable, $var_name_node->lineno ?? 0, (string)$name_node_type);
            }

            return null;
        }

        $var_name = (string)$var_name_node;

        if (!$context->getScope()->hasVariableWithName($var_name)) {
            if (Variable::isHardcodedVariableInScopeWithName($var_name, $context->isInGlobalScope())) {
                return null;
            }
            if (!($context->isInGlobalScope() && Config::getValue('ignore_undeclared_variables_in_global_scope'))) {
                throw new IssueException(
                    Issue::fromType(Issue::UndeclaredVariable)(
                        $context->getFile(),
                        $var_node->lineno ?? 0,
                        [$var_name]
                    )
                );
            }
            $variable = new Variable(
                $context,
                $var_name,
                UnionType::empty(),
                $var_node->flags
            );
            $context->addScopeVariable($variable);
            return $variable;
        }
        return $context->getScope()->getVariableByName(
            $var_name
        );
    }

    protected static final function isArgumentListWithVarAsFirstArgument(array $args) : bool
    {
        if (\count($args) >= 1) {
            $arg = $args[0];
            return ($arg instanceof Node) && ($arg->kind === \ast\AST_VAR);
        }
        return false;
    }

    /**
     * Fetches the function name. Does not check for function uses or namespaces.
     * @return ?string (null if function name could not be found)
     */
    protected static final function getFunctionName(Node $node)
    {
        $expr = $node->children['expr'];
        if (!($expr instanceof Node)) {
            return null;
        }
        $raw_function_name = $expr->children['name'] ?? null;
        if (!(\is_string($raw_function_name) && $raw_function_name)) {
            return null;
        }
        return $raw_function_name;
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CodeBase;
use Phan\Issue;
use Phan\Language\Element\Clazz;

/**
 * This verifies that the inherited abstract methods are all implemented on non-abstract clases.
 * NOTE: This step must be run after adding methods from this class and each of its ancestors.
 */
class AbstractMethodAnalyzer
{

    /**
     * Check to see if signatures match
     *
     * @return void
     */
    public static function analyzeAbstractMethodsAreImplemented(
        CodeBase $code_base,
        Clazz $class
    ) {
        // Don't worry about internal classes
        if ($class->isPHPInternal()) {
            return;
        }
        // Don't worry about traits or abstract classes, those can have abstract methods
        if ($class->isAbstract() || $class->isTrait() || $class->isInterface()) {
            return;
        }
        foreach ($class->getMethodMap($code_base) as $method) {
            if ($method->isAbstract()) {
                if ($method->isPHPInternal()) {
                    Issue::maybeEmit(
                        $code_base,
                        $class->getContext(),
                        Issue::ClassContainsAbstractMethodInternal,
                        $class->getFileRef()->getLineNumberStart(),
                        (string)$class->getFQSEN(),
                        (string)$method->getDefiningFQSEN()
                    );
                } else {
                    Issue::maybeEmit(
                        $code_base,
                        $class->getContext(),
                        Issue::ClassContainsAbstractMethod,
                        $class->getFileRef()->getLineNumberStart(),
                        (string)$class->getFQSEN(),
                        (string)$method->getDefiningFQSEN(),
                        $method->getFileRef()->getFile(),
                        $method->getFileRef()->getLineNumberStart()
                    );
                }
            }
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CodeBase;
use Phan\Issue;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;

class DuplicateFunctionAnalyzer
{

    /**
     * Check to see if the given FunctionInterface is a duplicate
     *
     * @return void
     */
    public static function analyzeDuplicateFunction(
        CodeBase $code_base,
        FunctionInterface $method
    ) {
        $fqsen = $method->getFQSEN();

        if (!$fqsen->isAlternate()) {
            return;
        }

        $original_fqsen = $fqsen->getCanonicalFQSEN();

        if ($original_fqsen instanceof FullyQualifiedFunctionName) {
            if (!$code_base->hasFunctionWithFQSEN($original_fqsen)) {
                return;
            }

            $original_method = $code_base->getFunctionByFQSEN(
                $original_fqsen
            );
        } else {
            if (!$code_base->hasMethodWithFQSEN($original_fqsen)) {
                return;
            }

            $original_method = $code_base->getMethodByFQSEN(
                $original_fqsen
            );
        }

        $method_name = $method->getName();

        if ($original_method->isPHPInternal()) {
            if (!$method->hasSuppressIssue(Issue::RedefineFunctionInternal)) {
                Issue::maybeEmit(
                    $code_base,
                    $method->getContext(),
                    Issue::RedefineFunctionInternal,
                    $method->getFileRef()->getLineNumberStart(),
                    $method_name,
                    $method->getFileRef()->getFile(),
                    $method->getFileRef()->getLineNumberStart()
                );
            }
        } else {
            if (!$method->hasSuppressIssue(Issue::RedefineFunction)) {
                Issue::maybeEmit(
                    $code_base,
                    $method->getContext(),
                    Issue::RedefineFunction,
                    $method->getFileRef()->getLineNumberStart(),
                    $method_name,
                    $method->getFileRef()->getFile(),
                    $method->getFileRef()->getLineNumberStart(),
                    $original_method->getFileRef()->getFile(),
                    $original_method->getFileRef()->getLineNumberStart()
                );
            }
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\AST\Visitor\KindVisitorImplementation;
use Phan\Language\Context;
use Phan\Language\Scope;
use Phan\Language\Type\NullType;
use Phan\Language\UnionType;
use ast\Node;

/**
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class ContextMergeVisitor extends KindVisitorImplementation
{
    /**
     * @var Context
     * The context in which the node we're going to be looking
     * at exits.
     */
    private $context;

    /**
     * @var array<int,Context>
     * A list of the contexts returned after depth-first
     * parsing of all first-level children of this node
     */
    private $child_context_list;

    /**
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     *
     * @param array<int,Context> $child_context_list
     * A list of the contexts returned after depth-first
     * parsing of all first-level children of this node
     */
    public function __construct(
        Context $context,
        array $child_context_list
    ) {
        $this->context = $context;
        $this->child_context_list = $child_context_list;
    }

    /**
     * Default visitor for node kinds that do not have
     * an overriding method
     *
     * @param Node $unused_node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visit(Node $unused_node) : Context
    {
        // TODO: if ($this->context->isInGlobalScope()) {
        //            copy local to global
        //       }

        return \end($this->child_context_list) ?: $this->context;
    }

    /**
     * Merges the only try block of a try/catch node into the parent context.
     * This acts as though the entire block succeeds or throws on the first statement, which isn't necessarily the case.
     *
     * visitTry() was split out into multiple functions for the following reasons:
     *
     * 1. The try{} block affects the Context of catch blocks (and finally block), if any
     * 2. The catch blocks affect the Context of the finally block, if any
     *
     * TODO: Look at ways to improve accuracy based on inferences of the exit status of the node?
     */
    public function mergeTryContext(Node $node) : Context
    {
        \assert(\count($this->child_context_list) === 1);

        // Get the list of scopes for each branch of the
        // conditional
        $context = $this->context;
        $try_context = $this->child_context_list[0];

        if ($this->willRemainingStatementsBeAnalyzedAsIfTryMightFail($node)) {
            return $this->combineScopeList([
                $context->getScope(),
                $try_context->getScope()
            ]);
        }
        return $try_context;
    }

    private function willRemainingStatementsBeAnalyzedAsIfTryMightFail(Node $node) : bool
    {
        if ($node->children['finally'] !== null) {
            // We want to analyze finally as if the try block (and one or more of the catch blocks) was or wasn't executed.
            // ... This isn't optimal.
            // A better solution would be to analyze finally{} twice,
            // 1. As if try could fail
            // 2. As if try did not fail, using the latter to analyze statements after the finally{}.
            return true;
        }
        // E.g. after analyzing the following code:
        //      try { $x = expr(); } catch (Exception $e) { echo "Caught"; return; } catch (OtherException $e) { continue; }
        // Phan should infer that $x is guaranteed to be defined.
        foreach ($node->children['catches']->children ?? [] as $catch_node) {
            if (BlockExitStatusChecker::willUnconditionallySkipRemainingStatements($catch_node->children['stmts'])) {
                return true;
            }
        }
        return false;
    }

    public function mergeCatchContext(Node $node) : Context
    {
        \assert(\count($this->child_context_list) >= 2);  // first context is the try
        // Get the list of scopes for each branch of the
        // conditional
        $scope_list = \array_map(function (Context $context) {
            return $context->getScope();
        }, $this->child_context_list);

        $catch_scope_list = [];
        $catch_nodes = $node->children['catches']->children;
        foreach ($catch_nodes as $i => $catch_node) {
            if (!BlockExitStatusChecker::willUnconditionallySkipRemainingStatements($catch_node->children['stmts'])) {
                $catch_scope_list[] = $scope_list[$i + 1];
            }
        }
        // TODO: Check if try node unconditionally returns.

        // Merge in the types for any variables found in a catch.
        // if ($node->children['finally'] !== null) {
            // If we have to analyze a finally statement later,
            // then be conservative and assume the try statement may or may not have failed.
            // E.g. the below example must have a inferred type of string|false
            //      $x = new stdClass(); try {...; $x = (string)fn(); } catch(Exception $e) { $x = false; }
            // $try_scope = $this->context->getScope();
        // } else {
            // If we don't have to worry about analyzing the finally statement, then assume that the entire try statement succeeded or the a catch statement succeeded.
            $try_scope = \reset($this->child_context_list)->getScope();
        // }

        if (\count($catch_scope_list) === 0) {
            // All of the catch statements will unconditionally rethrow or return.
            // So, after the try and catch blocks (finally is analyzed separately),
            // the context is the same as if the try block finished successfully.
            return $this->context->withScope($try_scope);
        }

        foreach ($try_scope->getVariableMap() as $variable_name => $variable) {
            $variable_name = (string)$variable_name;  // e.g. ${42}
            foreach ($catch_scope_list as $catch_scope) {
                // Merge types if try and catch have a variable in common
                if ($catch_scope->hasVariableWithName($variable_name)) {
                    $catch_variable = $catch_scope->getVariableByName(
                        $variable_name
                    );

                    $variable->setUnionType($variable->getUnionType()->withUnionType(
                        $catch_variable->getUnionType()
                    ));
                }
            }
        }

        // Look for variables that exist in catch, but not try
        foreach ($catch_scope_list as $catch_scope) {
            foreach ($catch_scope->getVariableMap() as $variable_name => $variable) {
                $variable_name = (string)$variable_name;
                if (!$try_scope->hasVariableWithName($variable_name)) {
                    // Note that it can be null
                    $variable->setUnionType($variable->getUnionType()->withType(
                        NullType::instance(false)
                    ));

                    // Add it to the try scope
                    $try_scope->addVariable($variable);
                }
            }
        }

        // Set the new scope with only the variables and types
        // that are common to all branches
        return $this->context->withScope($try_scope);
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     *
     * @suppress PhanPossiblyFalseTypeReturn child_context_list is not empty
     */
    public function visitIf(Node $node) : Context
    {
        // Get the list of scopes for each branch of the
        // conditional
        $scope_list = \array_map(function (Context $context) {
            return $context->getScope();
        }, $this->child_context_list);

        $has_else = \array_reduce(
            $node->children,
            function (bool $carry, $child_node) {
                return $carry || (
                    $child_node instanceof Node
                    && \is_null($child_node->children['cond'])
                );
            },
            false
        );

        // If we're not guaranteed to hit at least one
        // branch, mark the incoming scope as a possibility
        if (!$has_else) {
            $scope_list[] = $this->context->getScope();
        }

        // If there weren't multiple branches, continue on
        // as if the conditional never happened
        if (\count($scope_list) < 2) {
            return \reset($this->child_context_list);
        }

        return $this->combineScopeList($scope_list);
    }

    /**
     * A generic helper method to merge multiple Contexts. (e.g. for use outside of BlockAnalysisVisitor)
     * If you wish to include the base context, add it to $child_context_list in the constructor of ContextMergeVisitor.
     */
    public function combineChildContextList() : Context
    {
        $child_context_list = $this->child_context_list;
        \assert(\count($child_context_list) >= 2);
        $scope_list = \array_map(function (Context $context) {
            return $context->getScope();
        }, $child_context_list);
        return $this->combineScopeList($scope_list);
    }

    /**
     * @param array<int,Scope> $scope_list
     */
    public function combineScopeList(array $scope_list) : Context
    {
        \assert(\count($scope_list) >= 2);
        // Get a list of all variables in all scopes
        $variable_map = [];
        foreach ($scope_list as $scope) {
            foreach ($scope->getVariableMap() as $name => $variable) {
                $variable_map[$name] = $variable;
            }
        }

        // A function that determins if a variable is defined on
        // every branch
        $is_defined_on_all_branches =
            function (string $variable_name) use ($scope_list) {
                foreach ($scope_list as $scope) {
                    if (!$scope->hasVariableWithName($variable_name)) {
                        return false;
                    }
                }
                return true;
            };

        // Get the intersection of all types for all versions of
        // the variable from every side of the branch
        /** @suppress PhanPluginUnusedVariable plugin doesn't handle loops well */
        $union_type =
            function (string $variable_name) use ($scope_list) {
                $previous_type = null;
                $type_list = [];
                // Get a list of all variables with the given name from
                // each scope
                foreach ($scope_list as $scope) {
                    if (!$scope->hasVariableWithName($variable_name)) {
                        continue;
                    }

                    $type = $scope->getVariableByName($variable_name)->getUnionType();
                    // Frequently, a branch won't even modify a variable's type.
                    // The immutable UnionType might have the exact same instance
                    if ($type !== $previous_type) {
                        $type_list[] = $type;
                        $previous_type = $type;
                    }
                };

                if (\count($type_list) < 2) {
                    $result = \reset($type_list) ?: UnionType::empty();
                } else {
                    // compute the un-normalized types
                    $result = UnionType::merge($type_list);
                }

                $result_count = $result->typeCount();
                foreach ($type_list as $type) {
                    if ($type->typeCount() < $result_count) {
                        // normalize it if any of the types varied
                        // (i.e. one of the types lacks types in the type union)
                        //
                        // This is useful to avoid ending up with "bool|?false|true" (Will convert to "?bool")
                        return $result->asNormalizedTypes();
                    }
                }
                // Otherwise, don't normalize it - The different contexts didn't differ in the union types
                return $result;
            };

        // Clone the incoming scope so we can modify it
        // with the outgoing merged scope
        $scope = clone($this->context->getScope());

        foreach ($variable_map as $name => $variable) {
            // Skip variables that are only partially defined
            if (!$is_defined_on_all_branches($name)) {
                if ($this->context->getIsStrictTypes()) {
                    continue;
                } else {
                    // Limit the type of the variable to the subset
                    // of types that are common to all branches
                    // Record that it can be null, as the best available equivalent for undefined.
                    $variable = clone($variable);

                    $variable->setUnionType(
                        $union_type($name)->withType(
                            NullType::instance(false)
                        )
                    );

                    // Add the variable to the outgoing scope
                    $scope->addVariable($variable);
                    continue;
                }
            }

            // Limit the type of the variable to the subset
            // of types that are common to all branches
            $variable = clone($variable);

            $variable->setUnionType(
                $union_type($name)
            );

            // Add the variable to the outgoing scope
            $scope->addVariable($variable);
        }

        // Set the new scope with only the variables and types
        // that are common to all branches
        return $this->context->withScope($scope);
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CodeBase;
use Phan\Config;
use Phan\Issue;
use Phan\Language\Element\Clazz;

class ParentConstructorCalledAnalyzer
{

    /**
     * Check to see if the given Clazz is a duplicate
     *
     * @return void
     */
    public static function analyzeParentConstructorCalled(
        CodeBase $code_base,
        Clazz $clazz
    ) {
        // Only look at classes configured to require a call
        // to its parent constructor
        if (!\in_array(
            $clazz->getName(),
            Config::getValue('parent_constructor_required')
        )
        ) {
            return;
        }

        // Don't worry about internal classes
        if ($clazz->isPHPInternal()) {
            return;
        }

        // Don't worry if there's no parent class
        if (!$clazz->hasParentType()) {
            return;
        }

        if (!$code_base->hasClassWithFQSEN(
            $clazz->getParentClassFQSEN()
        )) {
            // This is an error, but its caught elsewhere. We'll
            // just roll through looking for other errors
            return;
        }

        $parent_clazz = $code_base->getClassByFQSEN(
            $clazz->getParentClassFQSEN()
        );

        if (!$parent_clazz->isAbstract()
            && !$clazz->getIsParentConstructorCalled()
        ) {
            Issue::maybeEmit(
                $code_base,
                $clazz->getContext(),
                Issue::TypeParentConstructorCalled,
                $clazz->getFileRef()->getLineNumberStart(),
                (string)$clazz->getFQSEN(),
                (string)$parent_clazz->getFQSEN()
            );
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\Analysis;

use Phan\CodeBase;
use Phan\Issue;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Method;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\Type;
use Phan\Language\Type\ObjectType;
use Phan\Language\Type\TemplateType;

class ThrowsTypesAnalyzer
{

    /**
     * Check method phpdoc (at)throws types to make sure they're valid
     *
     * @return void
     */
    public static function analyzeThrowsTypes(
        CodeBase $code_base,
        FunctionInterface $method
    ) {
        foreach ($method->getThrowsUnionType()->getTypeSet() as $type) {
            self::analyzeSingleThrowType($code_base, $method, $type);
        }
    }

    /**
     * Check a throw type to make sure it's valid
     *
     * @return void
     */
    private static function analyzeSingleThrowType(
        CodeBase $code_base,
        FunctionInterface $method,
        Type $type
    ) {
        if (!$type->isObject()) {
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                Issue::TypeInvalidThrowsNonObject,
                $method->getFileRef()->getLineNumberStart(),
                $method->getName(),
                (string)$type
            );
            return;
        }
        if ($type instanceof TemplateType) {
            // TODO: Add unit tests of templates for return types and checks
            if ($method instanceof Method && $method->isStatic()) {
                Issue::maybeEmit(
                    $code_base,
                    $method->getContext(),
                    Issue::TemplateTypeStaticMethod,
                    $method->getFileRef()->getLineNumberStart(),
                    (string)$method->getFQSEN()
                );
            }
            return;
        }
        if ($type instanceof ObjectType) {
            // (at)throws object is valid
            return;
        }
        static $throwable;
        if ($throwable === null) {
            $throwable = Type::fromFullyQualifiedString('\\Throwable');
        }
        if ($type === $throwable) {
            // allow (at)throws Throwable.
            return;
        }

        $type_fqsen = $type->asFQSEN();
        \assert($type_fqsen instanceof FullyQualifiedClassName, 'non-native types must be class names');
        if (!$code_base->hasClassWithFQSEN($type_fqsen)) {
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                Issue::UndeclaredTypeThrowsType,
                $method->getFileRef()->getLineNumberStart(),
                $method->getName(),
                $type
            );
            return;
        }
        $exception_class = $code_base->getClassByFQSEN($type_fqsen);
        if ($exception_class->isTrait() || $exception_class->isInterface()) {
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                $exception_class->isTrait() ? Issue::TypeInvalidThrowsIsTrait : Issue::TypeInvalidThrowsIsInterface,
                $method->getFileRef()->getLineNumberStart(),
                $method->getName(),
                $type
            );
            return;
        }

        if (!($type->asExpandedTypes($code_base)->hasType($throwable))) {
            Issue::maybeEmit(
                $code_base,
                $method->getContext(),
                Issue::TypeInvalidThrowsNonThrowable,
                $method->getFileRef()->getLineNumberStart(),
                $method->getName(),
                $type
            );
            return;
        }
    }
}
<?php declare(strict_types=1);
namespace Phan;

use Phan\Output\Colorizing;
use Phan\Language\FQSEN;
use Phan\Language\Type;
use Phan\Language\UnionType;

class IssueInstance
{
    /** @var Issue */
    private $issue;

    /** @var string */
    private $file;

    /** @var int */
    private $line;

    /** @var string */
    private $message;

    /**
     * @param Issue $issue
     * @param string $file
     * @param int $line
     * @param array<int,string|int|FQSEN|Type|UnionType> $template_parameters
     */
    public function __construct(
        Issue $issue,
        string $file,
        int $line,
        array $template_parameters
    ) {
        $this->issue = $issue;
        $this->file = $file;
        $this->line = $line;

        // color_issue_message will interfere with some formatters, such as xml.
        if (Config::getValue('color_issue_messages')) {
            $this->message = self::generateColorizedMessage($issue, $template_parameters);
        } else {
            $this->message = self::generatePlainMessage($issue, $template_parameters);
        }
    }

    /**
     * @param array<int,string|int|float|bool|object> $template_parameters
     */
    private static function generatePlainMessage(
        Issue $issue,
        array $template_parameters
    ) : string {
        $template = $issue->getTemplate();

        // markdown_issue_messages doesn't make sense with color, unless you add <span style="color:red">msg</span>
        // Not sure if codeclimate supports that.
        if (Config::getValue('markdown_issue_messages')) {
            $template = preg_replace(
                '/([^ ]*%s[^ ]*)/',
                '`\1`',
                $template
            );
        }
        return vsprintf(
            $template,
            $template_parameters
        );
    }

    /**
     * @param array<int,string|int|FQSEN|Type|UnionType> $template_parameters
     */
    private static function generateColorizedMessage(
        Issue $issue,
        array $template_parameters
    ) : string {
        $template = $issue->getTemplateRaw();

        return Colorizing::colorizeTemplate($template, $template_parameters);
    }

    /**
     * @return Issue
     */
    public function getIssue() : Issue
    {
        return $this->issue;
    }

    /**
     * @return string
     */
    public function getFile() : string
    {
        return $this->file;
    }

    /**
     * @return int
     */
    public function getLine() : int
    {
        return $this->line;
    }

    /**
     * @return string
     */
    public function getMessage()
    {
        return $this->message;
    }

    public function __toString() : string
    {
        return "{$this->getFile()}:{$this->getLine()} {$this->getMessage()}";
    }
}
<?php declare(strict_types=1);
namespace Phan;

use ast\Node;

class Prep
{

    /**
     * Scan a list of files, applying the given closure to every
     * AST node
     *
     * @param array<int,string> $file_list
     * A list of files to scan
     *
     * @param \Closure $visit_node
     * A closure that is to be applied to every AST node
     *
     * @return void
     */
    public static function scanFileList(
        array $file_list,
        \Closure $visit_node
    ) {
        foreach ($file_list as $file_path) {
            // Convert the file to an Abstract Syntax Tree
            // before passing it on to the recursive version
            // of this method
            $node = \ast\parse_file(
                $file_path,
                Config::AST_VERSION
            );

            // Skip empty files
            if (!$node) {
                continue;
            }

            self::scanNodeInFile($node, $file_path, $visit_node);
        }
    }

    /**
     * Recursively scan a node and its children applying the
     * given closure to each.
     *
     * @param string $file_path
     * The file in which the node exists
     *
     * @param \Closure $visit_node
     * A closure that is to be applied to every AST node
     *
     * @return void
     */
    public static function scanNodeInFile(
        Node $node,
        string $file_path,
        \Closure $visit_node
    ) {
        // Visit the node doing whatever the caller
        // likes
        $visit_node($node, $file_path);

        // Scan all children recursively
        foreach ($node->children as $child_node) {
            if (!($child_node instanceof Node)) {
                continue;
            }

            self::scanNodeInFile($child_node, $file_path, $visit_node);
        }
    }
}
<?php declare(strict_types=1);
namespace Phan;

use ast\Node;
use ast\flags;
use Phan\Analysis\BlockExitStatusChecker;

/**
 * Debug utilities
 *
 * Mostly utilities for printing representations of AST nodes.
 * Also see `Debug/`
 */
class Debug
{
    // option for self::astDump
    const AST_DUMP_LINENOS = 1;

    /**
     * Print a lil' something to the console to
     * see if a thing is called
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public static function mark()
    {
        print "mark\n";
    }

    /**
     * @param string|int|float|Node|null $node
     * An AST node
     *
     * Print an AST node
     *
     * @return void
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public static function printNode($node)
    {
        print self::nodeToString($node);
    }

    /**
     * Print the name of a node to the terminal
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public static function printNodeName($node, $indent = 0)
    {
        print str_repeat("\t", $indent);
        print self::nodeName($node);
        print "\n";
    }

    /**
     * Print a thing with the given indent level
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public static function print(string $message, int $indent = 0)
    {
        print str_repeat("\t", $indent);
        print $message . "\n";
    }

    /**
     * @param Node|string|null $node
     * @return string
     * The name of the node
     */
    public static function nodeName($node) : string
    {
        if (\is_string($node)) {
            return "string";
        }

        if (!$node) {
            return 'null';
        }

        $kind = $node->kind;
        if (\is_string($kind)) {
            // For placeholders created by tolerant-php-parser-to-php-ast
            return "string ($kind)";
        }
        return \ast\get_kind_name($kind);
    }

    /**
     * @param string|int|float|Node|null $node
     * An AST node
     *
     * @param int|float|string|null $name
     * The name of the node (if this node has a parent)
     *
     * @param int $indent
     * The indentation level for the string
     *
     * @return string
     * A string representation of an AST node
     *
     * @phan-suppress PhanPartialTypeMismatchArgument handle impossible arrays?
     */
    public static function nodeToString(
        $node,
        $name = null,
        int $indent = 0
    ) : string {
        $string = str_repeat("\t", $indent);

        if ($name !== null) {
            $string .= "$name => ";
        }

        if (\is_string($node)) {
            return $string . $node . "\n";
        }

        if (!$node) {
            return $string . 'null' . "\n";
        }

        if (!\is_object($node)) {
            return $string . $node . "\n";
        }
        $kind = $node->kind;

        $string .= self::nodeName($node);

        $string .= ' ['
            . (is_int($kind) ? self::astFlagDescription($node->flags ?? 0, $kind) : 'unknown')
            . ']';

        if (isset($node->lineno)) {
            $string .= ' #' . $node->lineno;
        }

        $endLineno = $node->endLineno ?? null;
        if (!\is_null($endLineno)) {
            $string .= ':' . $endLineno;
        }

        $string .= "\n";

        foreach ($node->children as $name => $child_node) {
            $string .= self::nodeToString(
                $child_node,
                $name,
                $indent + 1
            );
        }

        return $string;
    }

    /**
     * @return string
     * Get a string representation of AST node flags such as
     * 'ASSIGN_DIV|TYPE_ARRAY'
     * @see self::formatFlags for a similar function also printing the integer flag value.
     */
    public static function astFlagDescription(int $flags, int $kind) : string
    {
        list($exclusive, $combinable) = self::getFlagInfo();
        $flag_names = [];
        if (isset($exclusive[$kind])) {
            $flagInfo = $exclusive[$kind];
            if (isset($flagInfo[$flags])) {
                $flag_names[] = $flagInfo[$flags];
            }
        } elseif (isset($combinable[$kind])) {
            $flagInfo = $combinable[$kind];
            foreach ($flagInfo as $flag => $name) {
                if ($flags & $flag) {
                    $flag_names[] = $name;
                }
            }
        }

        return implode('|', $flag_names);
    }

    /**
     * @return string
     * Get a string representation of AST node flags such as
     * 'ASSIGN_DIV (26)'
     * Source: https://github.com/nikic/php-ast/blob/master/util.php
     */
    public static function formatFlags(int $kind, int $flags) : string
    {
        list($exclusive, $combinable) = self::getFlagInfo();
        if (isset($exclusive[$kind])) {
            $flagInfo = $exclusive[$kind];
            if (isset($flagInfo[$flags])) {
                return "{$flagInfo[$flags]} ($flags)";
            }
        } elseif (isset($combinable[$kind])) {
            $flagInfo = $combinable[$kind];
            $names = [];
            foreach ($flagInfo as $flag => $name) {
                if ($flags & $flag) {
                    $names[] = $name;
                }
            }
            if (!empty($names)) {
                return implode(" | ", $names) . " ($flags)";
            }
        }
        return (string) $flags;
    }


    /**
     * @return void
     * Pretty-printer for debug_backtrace
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public static function backtrace(int $levels = 0)
    {
        $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $levels+1);
        foreach ($bt as $level => $context) {
            if (!$level) {
                continue;
            }
            echo "#".($level-1)." {$context['file']}:{$context['line']} {$context['class']} ";
            if (!empty($context['type'])) {
                echo $context['class'].$context['type'];
            }
            echo $context['function'];
            echo "\n";
        }
    }

    /**
     * Dumps abstract syntax tree
     * Source: https://github.com/nikic/php-ast/blob/master/util.php
     */
    public static function astDump($ast, int $options = 0) : string
    {
        if ($ast instanceof \ast\Node) {
            $result = \ast\get_kind_name($ast->kind);

            if ($options & self::AST_DUMP_LINENOS) {
                $result .= " @ $ast->lineno";
                $endLineno = $ast->endLineno ?? null;
                if (!\is_null($endLineno)) {
                    $result .= "-$endLineno";
                }
            }

            if (\ast\kind_uses_flags($ast->kind)) {
                $flags_without_phan_additions = $ast->flags & ~BlockExitStatusChecker::STATUS_BITMASK;
                if ($flags_without_phan_additions != 0) {
                    $result .= "\n    flags: " . self::formatFlags($ast->kind, $ast->flags);
                }
            }
            foreach ($ast->children as $i => $child) {
                $result .= "\n    $i: " . str_replace("\n", "\n    ", self::astDump($child, $options));
            }
            return $result;
        } elseif ($ast === null) {
            return 'null';
        } elseif (\is_string($ast)) {
            return "\"$ast\"";
        } else {
            return (string) $ast;
        }
    }

    /**
     * Source: https://github.com/nikic/php-ast/blob/master/util.php
     * @return array<int,array<int,array<int,string>>>
     * Return value is [string[][] $exclusive, string[][] $combinable]. Maps node id to flag id to name.
     */
    private static function getFlagInfo() : array
    {
        static $exclusive, $combinable;
        if ($exclusive !== null) {
            return [$exclusive, $combinable];
        }

        $modifiers = [
            flags\MODIFIER_PUBLIC => 'MODIFIER_PUBLIC',
            flags\MODIFIER_PROTECTED => 'MODIFIER_PROTECTED',
            flags\MODIFIER_PRIVATE => 'MODIFIER_PRIVATE',
            flags\MODIFIER_STATIC => 'MODIFIER_STATIC',
            flags\MODIFIER_ABSTRACT => 'MODIFIER_ABSTRACT',
            flags\MODIFIER_FINAL => 'MODIFIER_FINAL',
            flags\FUNC_RETURNS_REF => 'FUNC_RETURNS_REF',
            flags\FUNC_GENERATOR => 'FUNC_GENERATOR',
        ];
        $types = [
            flags\TYPE_NULL => 'TYPE_NULL',
            flags\TYPE_BOOL => 'TYPE_BOOL',
            flags\TYPE_LONG => 'TYPE_LONG',
            flags\TYPE_DOUBLE => 'TYPE_DOUBLE',
            flags\TYPE_STRING => 'TYPE_STRING',
            flags\TYPE_ARRAY => 'TYPE_ARRAY',
            flags\TYPE_OBJECT => 'TYPE_OBJECT',
            flags\TYPE_CALLABLE => 'TYPE_CALLABLE',
            flags\TYPE_VOID => 'TYPE_VOID',
            flags\TYPE_ITERABLE => 'TYPE_ITERABLE',
        ];
        $useTypes = [
            flags\USE_NORMAL => 'USE_NORMAL',
            flags\USE_FUNCTION => 'USE_FUNCTION',
            flags\USE_CONST => 'USE_CONST',
        ];
        $sharedBinaryOps = [
            flags\BINARY_BITWISE_OR => 'BINARY_BITWISE_OR',
            flags\BINARY_BITWISE_AND => 'BINARY_BITWISE_AND',
            flags\BINARY_BITWISE_XOR => 'BINARY_BITWISE_XOR',
            flags\BINARY_CONCAT => 'BINARY_CONCAT',
            flags\BINARY_ADD => 'BINARY_ADD',
            flags\BINARY_SUB => 'BINARY_SUB',
            flags\BINARY_MUL => 'BINARY_MUL',
            flags\BINARY_DIV => 'BINARY_DIV',
            flags\BINARY_MOD => 'BINARY_MOD',
            flags\BINARY_POW => 'BINARY_POW',
            flags\BINARY_SHIFT_LEFT => 'BINARY_SHIFT_LEFT',
            flags\BINARY_SHIFT_RIGHT => 'BINARY_SHIFT_RIGHT',
        ];

        $exclusive = [
            \ast\AST_NAME => [
                flags\NAME_FQ => 'NAME_FQ',
                flags\NAME_NOT_FQ => 'NAME_NOT_FQ',
                flags\NAME_RELATIVE => 'NAME_RELATIVE',
            ],
            \ast\AST_CLASS => [
                flags\CLASS_ABSTRACT => 'CLASS_ABSTRACT',
                flags\CLASS_FINAL => 'CLASS_FINAL',
                flags\CLASS_TRAIT => 'CLASS_TRAIT',
                flags\CLASS_INTERFACE => 'CLASS_INTERFACE',
                flags\CLASS_ANONYMOUS => 'CLASS_ANONYMOUS',
            ],
            \ast\AST_PARAM => [
                flags\PARAM_REF => 'PARAM_REF',
                flags\PARAM_VARIADIC => 'PARAM_VARIADIC',
            ],
            \ast\AST_TYPE => $types,
            \ast\AST_CAST => $types,
            \ast\AST_UNARY_OP => [
                flags\UNARY_BOOL_NOT => 'UNARY_BOOL_NOT',
                flags\UNARY_BITWISE_NOT => 'UNARY_BITWISE_NOT',
                flags\UNARY_MINUS => 'UNARY_MINUS',
                flags\UNARY_PLUS => 'UNARY_PLUS',
                flags\UNARY_SILENCE => 'UNARY_SILENCE',
            ],
            \ast\AST_BINARY_OP => $sharedBinaryOps + [
                flags\BINARY_BOOL_AND => 'BINARY_BOOL_AND',
                flags\BINARY_BOOL_OR => 'BINARY_BOOL_OR',
                flags\BINARY_BOOL_XOR => 'BINARY_BOOL_XOR',
                flags\BINARY_IS_IDENTICAL => 'BINARY_IS_IDENTICAL',
                flags\BINARY_IS_NOT_IDENTICAL => 'BINARY_IS_NOT_IDENTICAL',
                flags\BINARY_IS_EQUAL => 'BINARY_IS_EQUAL',
                flags\BINARY_IS_NOT_EQUAL => 'BINARY_IS_NOT_EQUAL',
                flags\BINARY_IS_SMALLER => 'BINARY_IS_SMALLER',
                flags\BINARY_IS_SMALLER_OR_EQUAL => 'BINARY_IS_SMALLER_OR_EQUAL',
                flags\BINARY_IS_GREATER => 'BINARY_IS_GREATER',
                flags\BINARY_IS_GREATER_OR_EQUAL => 'BINARY_IS_GREATER_OR_EQUAL',
                flags\BINARY_SPACESHIP => 'BINARY_SPACESHIP',
                flags\BINARY_COALESCE => 'BINARY_COALESCE',
            ],
            \ast\AST_ASSIGN_OP => $sharedBinaryOps + [
                // Old version 10 flags
                flags\ASSIGN_BITWISE_OR => 'ASSIGN_BITWISE_OR',
                flags\ASSIGN_BITWISE_AND => 'ASSIGN_BITWISE_AND',
                flags\ASSIGN_BITWISE_XOR => 'ASSIGN_BITWISE_XOR',
                flags\ASSIGN_CONCAT => 'ASSIGN_CONCAT',
                flags\ASSIGN_ADD => 'ASSIGN_ADD',
                flags\ASSIGN_SUB => 'ASSIGN_SUB',
                flags\ASSIGN_MUL => 'ASSIGN_MUL',
                flags\ASSIGN_DIV => 'ASSIGN_DIV',
                flags\ASSIGN_MOD => 'ASSIGN_MOD',
                flags\ASSIGN_POW => 'ASSIGN_POW',
                flags\ASSIGN_SHIFT_LEFT => 'ASSIGN_SHIFT_LEFT',
                flags\ASSIGN_SHIFT_RIGHT => 'ASSIGN_SHIFT_RIGHT',
            ],
            \ast\AST_MAGIC_CONST => [
                flags\MAGIC_LINE => 'MAGIC_LINE',
                flags\MAGIC_FILE => 'MAGIC_FILE',
                flags\MAGIC_DIR => 'MAGIC_DIR',
                flags\MAGIC_NAMESPACE => 'MAGIC_NAMESPACE',
                flags\MAGIC_FUNCTION => 'MAGIC_FUNCTION',
                flags\MAGIC_METHOD => 'MAGIC_METHOD',
                flags\MAGIC_CLASS => 'MAGIC_CLASS',
                flags\MAGIC_TRAIT => 'MAGIC_TRAIT',
            ],
            \ast\AST_USE => $useTypes,
            \ast\AST_GROUP_USE => $useTypes,
            \ast\AST_USE_ELEM => $useTypes,
            \ast\AST_INCLUDE_OR_EVAL => [
                flags\EXEC_EVAL => 'EXEC_EVAL',
                flags\EXEC_INCLUDE => 'EXEC_INCLUDE',
                flags\EXEC_INCLUDE_ONCE => 'EXEC_INCLUDE_ONCE',
                flags\EXEC_REQUIRE => 'EXEC_REQUIRE',
                flags\EXEC_REQUIRE_ONCE => 'EXEC_REQUIRE_ONCE',
            ],
            \ast\AST_ARRAY => [
                flags\ARRAY_SYNTAX_LIST => 'ARRAY_SYNTAX_LIST',
                flags\ARRAY_SYNTAX_LONG => 'ARRAY_SYNTAX_LONG',
                flags\ARRAY_SYNTAX_SHORT => 'ARRAY_SYNTAX_SHORT',
            ],
            \ast\AST_CLOSURE_VAR => [
                flags\CLOSURE_USE_REF => 'CLOSURE_USE_REF',
            ]
        ];

        $combinable = [];
        $combinable[\ast\AST_METHOD] = $combinable[\ast\AST_FUNC_DECL] = $combinable[\ast\AST_CLOSURE]
            = $combinable[\ast\AST_PROP_DECL] = $combinable[\ast\AST_CLASS_CONST_DECL]
            = $combinable[\ast\AST_TRAIT_ALIAS] = $modifiers;

        return [$exclusive, $combinable];
    }
}
<?php declare(strict_types=1);
namespace Phan\AST;

use Phan\CodeBase;
use Phan\Config;
use Phan\Exception\CodeBaseException;
use Phan\Exception\EmptyFQSENException;
use Phan\Exception\IssueException;
use Phan\Exception\NodeException;
use Phan\Exception\TypeException;
use Phan\Exception\UnanalyzableException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\ClassConstant;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Func;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\GlobalConstant;
use Phan\Language\Element\Method;
use Phan\Language\Element\Property;
use Phan\Language\Element\TraitAdaptations;
use Phan\Language\Element\TraitAliasSource;
use Phan\Language\Element\Variable;
use Phan\Language\FQSEN;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedGlobalConstantName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\FQSEN\FullyQualifiedPropertyName;
use Phan\Language\Type;
use Phan\Language\Type\ClosureType;
use Phan\Language\Type\FunctionLikeDeclarationType;
use Phan\Language\Type\IntType;
use Phan\Language\Type\MixedType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\ObjectType;
use Phan\Language\Type\StringType;
use Phan\Language\UnionType;
use Phan\Library\FileCache;
use Phan\Library\None;
use ast\Node;
use ast;
use InvalidArgumentException;

if (!\function_exists('spl_object_id')) {
    require_once __DIR__ . '/../../spl_object_id.php';
}

/**
 * Methods for an AST node in context
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class ContextNode
{

    /** @var CodeBase */
    private $code_base;

    /** @var Context */
    private $context;

    /** @var Node|array|bool|string|float|int|bool|null */
    private $node;

    /**
     * @param CodeBase $code_base
     * @param Context $context
     * @param Node|array|string|float|int|bool|null $node
     */
    public function __construct(
        CodeBase $code_base,
        Context $context,
        $node
    ) {
        $this->code_base = $code_base;
        $this->context = $context;
        $this->node = $node;
    }

    /**
     * Get a list of fully qualified names from a node
     *
     * @return array<int,string>
     */
    public function getQualifiedNameList() : array
    {
        if (!($this->node instanceof Node)) {
            return [];
        }

        return \array_map(function ($name_node) {
            return (new ContextNode(
                $this->code_base,
                $this->context,
                $name_node
            ))->getQualifiedName();
        }, $this->node->children);
    }

    /**
     * Get a fully qualified name from a node
     *
     * @return string
     */
    public function getQualifiedName() : string
    {
        return $this->getClassUnionType()->__toString();
    }

    /**
     * Gets the FQSEN for a trait.
     * NOTE: does not validate that it is really used on a trait
     * @return array<int,FQSEN>
     */
    public function getTraitFQSENList() : array
    {
        if (!($this->node instanceof Node)) {
            return [];
        }

        return \array_map(function ($name_node) : FQSEN {
            return (new ContextNode(
                $this->code_base,
                $this->context,
                $name_node
            ))->getTraitFQSEN([]);
        }, $this->node->children);
    }

    /**
     * Gets the FQSEN for a trait.
     * NOTE: does not validate that it is really used on a trait
     * @param array<string,TraitAdaptations> $adaptations_map
     * @return ?FQSEN (If this returns null, the caller is responsible for emitting an issue or falling back)
     */
    public function getTraitFQSEN(array $adaptations_map)
    {
        // TODO: In a subsequent PR, try to make trait analysis work when $adaptations_map has multiple possible traits.
        $trait_fqsen_string = $this->getQualifiedName();
        if ($trait_fqsen_string === '') {
            if (\count($adaptations_map) === 1) {
                return \reset($adaptations_map)->getTraitFQSEN();
            } else {
                return null;
            }
        }
        return FullyQualifiedClassName::fromStringInContext(
            $trait_fqsen_string,
            $this->context
        );
    }

    /**
     * Get a list of traits adaptations from a node of kind ast\AST_TRAIT_ADAPTATIONS
     * (with fully qualified names and `as`/`instead` info)
     *
     * @param array<int,FQSEN> $trait_fqsen_list TODO: use this for sanity check
     *
     * @return array<string,TraitAdaptations> maps the lowercase trait fqsen to the corresponding adaptations.
     */
    public function getTraitAdaptationsMap(array $trait_fqsen_list) : array
    {
        $node = $this->node;
        if (!($node instanceof Node)) {
            return [];
        }
        \assert($node->kind === ast\AST_TRAIT_ADAPTATIONS);

        // NOTE: This fetches fully qualified names more than needed,
        // but this isn't optimized, since traits aren't frequently used in classes.

        $adaptations_map = [];
        foreach ($trait_fqsen_list as $trait_fqsen) {
            $adaptations_map[\strtolower($trait_fqsen->__toString())] = new TraitAdaptations($trait_fqsen);
        }

        foreach ($this->node->children as $adaptation_node) {
            \assert($adaptation_node instanceof Node);
            if ($adaptation_node->kind === ast\AST_TRAIT_ALIAS) {
                $this->handleTraitAlias($adaptations_map, $adaptation_node);
            } elseif ($adaptation_node->kind === ast\AST_TRAIT_PRECEDENCE) {
                $this->handleTraitPrecedence($adaptations_map, $adaptation_node);
            } else {
                \assert(false, ("Unknown adaptation node kind " . $adaptation_node->kind));
            }
        }
        return $adaptations_map;
    }

    /**
     * Handles a node of kind ast\AST_TRAIT_ALIAS, modifying the corresponding TraitAdaptations instance
     * @param array<string,TraitAdaptations> $adaptations_map
     * @param Node $adaptation_node
     * @return void
     */
    private function handleTraitAlias(array $adaptations_map, Node $adaptation_node)
    {
        $trait_method_node = $adaptation_node->children['method'];
        $trait_original_class_name_node = $trait_method_node->children['class'];
        $trait_original_method_name = $trait_method_node->children['method'];
        $trait_new_method_name = $adaptation_node->children['alias'] ?? $trait_original_method_name;
        \assert(\is_string($trait_original_method_name));
        \assert(\is_string($trait_new_method_name));
        $trait_fqsen = (new ContextNode(
            $this->code_base,
            $this->context,
            $trait_original_class_name_node
        ))->getTraitFQSEN($adaptations_map);
        if ($trait_fqsen === null) {
            // TODO: try to analyze this rare special case instead of giving up in a subsequent PR?
            // E.g. `use A, B{foo as bar}` is valid PHP, but hard to analyze.
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::AmbiguousTraitAliasSource,
                $trait_method_node->lineno ?? 0,
                $trait_new_method_name,
                $trait_original_method_name,
                '[' . implode(', ', \array_map(function (TraitAdaptations $t) : string {
                    return (string) $t->getTraitFQSEN();
                }, $adaptations_map)) . ']'
            );
            return;
        }

        $fqsen_key = \strtolower($trait_fqsen->__toString());

        $adaptations_info = $adaptations_map[$fqsen_key] ?? null;
        if ($adaptations_info === null) {
            // This will probably correspond to a PHP fatal error, but keep going anyway.
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::RequiredTraitNotAdded,
                $trait_original_class_name_node->lineno ?? 0,
                $trait_fqsen->__toString()
            );
            return;
        }
        // TODO: Could check for duplicate alias method occurrences, but `php -l` would do that for you in some cases
        $adaptations_info->alias_methods[$trait_new_method_name] = new TraitAliasSource($trait_original_method_name, $adaptation_node->lineno ?? 0, $adaptation_node->flags ?? 0);
        // Handle `use MyTrait { myMethod as private; }` by skipping the original method.
        // TODO: Do this a cleaner way.
        if (strcasecmp($trait_new_method_name, $trait_original_method_name) === 0) {
            $adaptations_info->hidden_methods[\strtolower($trait_original_method_name)] = true;
        }
    }

    /**
     * Handles a node of kind ast\AST_TRAIT_PRECEDENCE, modifying the corresponding TraitAdaptations instance
     * @param array<string,TraitAdaptations> $adaptations_map
     * @param Node $adaptation_node
     * @return void
     */
    private function handleTraitPrecedence(array $adaptations_map, Node $adaptation_node)
    {
        // TODO: Should also verify that the original method exists, in a future PR?
        $trait_method_node = $adaptation_node->children['method'];
        // $trait_chosen_class_name_node = $trait_method_node->children['class'];
        $trait_chosen_method_name = $trait_method_node->children['method'];
        $trait_chosen_class_name_node = $trait_method_node->children['class'];

        $trait_chosen_fqsen = (new ContextNode(
            $this->code_base,
            $this->context,
            $trait_chosen_class_name_node
        ))->getTraitFQSEN($adaptations_map);


        if (!$trait_chosen_fqsen) {
            throw new UnanalyzableException(
                $trait_chosen_class_name_node,
                "This shouldn't happen. Could not determine trait fqsen for trait with higher precedence for method $trait_chosen_method_name"
            );
        }

        if (($adaptations_map[\strtolower($trait_chosen_fqsen->__toString())] ?? null) === null) {
            // This will probably correspond to a PHP fatal error, but keep going anyway.
            Issue::maybeEmit(
                $this->code_base,
                $this->context,
                Issue::RequiredTraitNotAdded,
                $trait_chosen_class_name_node->lineno ?? 0,
                $trait_chosen_fqsen->__toString()
            );
        }

        // This is the class which will have the method hidden
        foreach ($adaptation_node->children['insteadof']->children as $trait_insteadof_class_name) {
            \assert(\is_string($trait_chosen_method_name));
            $trait_insteadof_fqsen = (new ContextNode(
                $this->code_base,
                $this->context,
                $trait_insteadof_class_name
            ))->getTraitFQSEN($adaptations_map);
            if (!$trait_insteadof_fqsen) {
                throw new UnanalyzableException(
                    $trait_insteadof_class_name,
                    "This shouldn't happen. Could not determine trait fqsen for trait with lower precedence for method $trait_chosen_method_name"
                );
            }

            $fqsen_key = \strtolower($trait_insteadof_fqsen->__toString());

            $adaptations_info = $adaptations_map[$fqsen_key] ?? null;
            if ($adaptations_info === null) {
                // TODO: Make this into an issue type
                Issue::maybeEmit(
                    $this->code_base,
                    $this->context,
                    Issue::RequiredTraitNotAdded,
                    $trait_insteadof_class_name->lineno ?? 0,
                    $trait_insteadof_fqsen->__toString()
                );
                continue;
            }
            $adaptations_info->hidden_methods[strtolower($trait_chosen_method_name)] = true;
        }
    }

    /**
     * @return string
     * A variable name associated with the given node
     */
    public function getVariableName() : string
    {
        if (!($this->node instanceof ast\Node)) {
            return (string)$this->node;
        }

        $node = $this->node;

        while (($node instanceof ast\Node)
            && ($node->kind != ast\AST_VAR)
            && ($node->kind != ast\AST_STATIC)
            && ($node->kind != ast\AST_MAGIC_CONST)
        ) {
            $node = \array_values($node->children)[0];
        }

        if (!($node instanceof ast\Node)) {
            return (string)$node;
        }

        $name_node = $node->children['name'] ?? null;
        if (empty($name_node)) {
            return '';
        }

        if ($name_node instanceof ast\Node) {
            // This is nonsense. Give up, but check if it's a type other than int/string.
            // (e.g. to catch typos such as $$this->foo = bar;)
            try {
                $name_node_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $name_node, true);
            } catch (IssueException $exception) {
                Issue::maybeEmitInstance(
                    $this->code_base,
                    $this->context,
                    $exception->getIssueInstance()
                );
                return '';
            }
            static $int_or_string_type;
            if ($int_or_string_type === null) {
                $int_or_string_type = new UnionType([StringType::instance(false), IntType::instance(false), NullType::instance(false)]);
            }
            if (!$name_node_type->canCastToUnionType($int_or_string_type)) {
                Issue::maybeEmit($this->code_base, $this->context, Issue::TypeSuspiciousIndirectVariable, $name_node->lineno ?? 0, (string)$name_node_type);
            }

            return '';
        }

        return (string)$name_node;
    }

    /**
     * @return UnionType the union type of the class for this class node. (Should have just one Type)
     */
    public function getClassUnionType() : UnionType
    {
        return UnionTypeVisitor::unionTypeFromClassNode(
            $this->code_base,
            $this->context,
            $this->node
        );
    }

    // Constants for getClassList() API
    const CLASS_LIST_ACCEPT_ANY = 0;
    const CLASS_LIST_ACCEPT_OBJECT = 1;
    const CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME = 2;

    /**
     * @return array{0:UnionType,1:Clazz[]}
     */
    public function getClassListInner(bool $ignore_missing_classes)
    {
        $node = $this->node;
        if (!($node instanceof Node)) {
            if (\is_string($node)) {
                return [StringType::instance(false)->asUnionType(), []];
            }
            return [UnionType::empty(), []];
        }
        $context = $this->context;
        $node_id = \spl_object_id($node);

        $cached_result = $context->getCachedClassListOfNode($node_id);
        if ($cached_result) {
            // About 25% of requests are cache hits
            return $cached_result;
        }
        $code_base = $this->code_base;
        try {
            $union_type = UnionTypeVisitor::unionTypeFromClassNode(
                $code_base,
                $context,
                $node
            );
        } catch (EmptyFQSENException $e) {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::EmptyFQSENInClasslike,
                $this->node->lineno ?? $context->getLineNumberStart(),
                $e->getFQSEN()
            );
            $union_type = UnionType::empty();
        }
        if ($union_type->isEmpty()) {
            $result = [$union_type, []];
            $context->setCachedClassListOfNode($node_id, $result);
            return $result;
        }

        $class_list = [];
        if ($ignore_missing_classes) {
            try {
                // TODO: Not sure why iterator_to_array would cause a test failure
                foreach ($union_type->asClassList(
                    $code_base,
                    $context
                ) as $clazz) {
                    $class_list[] = $clazz;
                }
                $result = [$union_type, $class_list];
                $context->setCachedClassListOfNode($node_id, $result);
                return $result;
            } catch (CodeBaseException $exception) {
                // swallow it
                // TODO: Is it appropriate to return class_list
                return [$union_type, $class_list];
            }
        }
        foreach ($union_type->asClassList(
            $code_base,
            $context
        ) as $clazz) {
            $class_list[] = $clazz;
        }
        $result = [$union_type, $class_list];
        $context->setCachedClassListOfNode($node_id, $result);
        return $result;
    }
    /**
     * @param bool $ignore_missing_classes
     * If set to true, missing classes will be ignored and
     * exceptions will be inhibited
     *
     * @param int $expected_type_categories
     * Does not affect the returned classes, but will cause phan to emit issues. Does not emit by default.
     * If set to CLASS_LIST_ACCEPT_ANY, this will not warn.
     * If set to CLASS_LIST_ACCEPT_OBJECT, this will warn if the inferred type is exclusively non-object types.
     * If set to CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME, this will warn if the inferred type is exclusively non-object and non-string types.
     *
     * @param ?string $custom_issue_type
     * If this exists, emit the given issue type (passing in union type as format arg) instead of the default issue type.
     *
     * @return array<int,Clazz>
     * A list of classes representing the non-native types
     * associated with the given node
     *
     * @throws CodeBaseException
     * An exception is thrown if a non-native type does not have
     * an associated class
     */
    public function getClassList(bool $ignore_missing_classes = false, int $expected_type_categories = self::CLASS_LIST_ACCEPT_ANY, string $custom_issue_type = null) : array
    {
        list($union_type, $class_list) = $this->getClassListInner($ignore_missing_classes);
        if ($union_type->isEmpty()) {
            return [];
        }

        // TODO: Should this check that count($cclass_list) > 0 instead? Or just always check?
        if (\count($class_list) === 0 && $expected_type_categories !== self::CLASS_LIST_ACCEPT_ANY) {
            if (!$union_type->hasTypeMatchingCallback(function (Type $type) use ($expected_type_categories) : bool {
                return $type->isObject() || ($type instanceof MixedType) || ($expected_type_categories === self::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME && $type instanceof StringType);
            })) {
                if ($custom_issue_type === Issue::TypeExpectedObjectPropAccess) {
                    if ($union_type->isType(NullType::instance(false))) {
                        $custom_issue_type = Issue::TypeExpectedObjectPropAccessButGotNull;
                    }
                }
                Issue::maybeEmit(
                    $this->code_base,
                    $this->context,
                    $custom_issue_type ?? ($expected_type_categories === self::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME ? Issue::TypeExpectedObjectOrClassName : Issue::TypeExpectedObject),
                    $this->node->lineno ?? 0,
                    (string)$union_type
                );
            }
        }

        return $class_list;
    }

    /**
     * @param Node|string $method_name
     * Either then name of the method or a node that
     * produces the name of the method.
     *
     * @param bool $is_static
     * Set to true if this is a static method call
     *
     * @param bool $is_direct
     * Set to true if this is directly invoking the method (guaranteed not to be special syntax)
     *
     * @param bool $is_new_expression
     * Set to true if this is (new (expr)())
     *
     * @return Method
     * A method with the given name on the class referenced
     * from the given node
     *
     * @throws NodeException
     * An exception is thrown if we can't understand the node
     *
     * @throws CodeBaseException
     * An exception is thrown if we can't find the given
     * method
     *
     * @throws TypeException
     * An exception may be thrown if the only viable candidate
     * is a non-class type.
     *
     * @throws IssueException
     */
    public function getMethod(
        $method_name,
        bool $is_static,
        bool $is_direct = false,
        bool $is_new_expression = false
    ) : Method {

        if ($method_name instanceof Node) {
            // The method_name turned out to be a variable.
            // There isn't much we can do to figure out what
            // it's referring to.
            throw new NodeException(
                $method_name,
                "Unexpected method node"
            );
        }

        \assert(
            \is_string($method_name),
            "Method name must be a string. Found non-string in context."
        );

        \assert(
            $this->node instanceof ast\Node,
            '$this->node must be a node'
        );

        try {
            // Fetch the list of valid classes, and warn about any undefined classes.
            // (We have more specific issue types such as PhanNonClassMethodCall below, don't emit PhanTypeExpected*)
            $class_list = (new ContextNode(
                $this->code_base,
                $this->context,
                $this->node->children['expr']
                    ?? $this->node->children['class']
            ))->getClassList(false, self::CLASS_LIST_ACCEPT_ANY);
        } catch (CodeBaseException $exception) {
            throw new IssueException(
                Issue::fromType(Issue::UndeclaredClassMethod)(
                    $this->context->getFile(),
                    $this->node->lineno ?? 0,
                    [ $method_name, (string)$exception->getFQSEN() ]
                )
            );
        }

        // If there were no classes on the left-type, figure
        // out what we were trying to call the method on
        // and send out an error.
        if (empty($class_list)) {
            $union_type = UnionTypeVisitor::unionTypeFromClassNode(
                $this->code_base,
                $this->context,
                $this->node->children['expr']
                    ?? $this->node->children['class']
            );

            if (!$union_type->isEmpty()
                && $union_type->isNativeType()
                && !$union_type->hasAnyType([
                    MixedType::instance(false),
                    ObjectType::instance(false),
                ])
                // reject `$stringVar->method()` but not `$stringVar::method()` and not (`new $stringVar()`
                && !(($is_static || $is_new_expression) && $union_type->hasType(StringType::instance(false)))
                && !(
                    Config::get_null_casts_as_any_type()
                    && $union_type->hasType(NullType::instance(false))
                )
            ) {
                throw new IssueException(
                    Issue::fromType(Issue::NonClassMethodCall)(
                        $this->context->getFile(),
                        $this->node->lineno ?? 0,
                        [ $method_name, (string)$union_type ]
                    )
                );
            }

            throw new NodeException(
                $this->node,
                "Can't figure out method call for $method_name"
            );
        }

        // Hunt to see if any of them have the method we're
        // looking for
        foreach ($class_list as $class) {
            if ($class->hasMethodWithName($this->code_base, $method_name, $is_direct)) {
                return $class->getMethodByName(
                    $this->code_base,
                    $method_name
                );
            } elseif (!$is_static && $class->allowsCallingUndeclaredInstanceMethod($this->code_base)) {
                return $class->getCallMethod($this->code_base);
            } elseif ($is_static && $class->allowsCallingUndeclaredStaticMethod($this->code_base)) {
                return $class->getCallStaticMethod($this->code_base);
            }
        }

        // Figure out an FQSEN for the method we couldn't find
        $method_fqsen = FullyQualifiedMethodName::make(
            $class_list[0]->getFQSEN(),
            $method_name
        );

        if ($is_static) {
            throw new IssueException(
                Issue::fromType(Issue::UndeclaredStaticMethod)(
                    $this->context->getFile(),
                    $this->node->lineno ?? 0,
                    [ (string)$method_fqsen ]
                )
            );
        }

        throw new IssueException(
            Issue::fromType(Issue::UndeclaredMethod)(
                $this->context->getFile(),
                $this->node->lineno ?? 0,
                [ (string)$method_fqsen ]
            )
        );
    }

    /**
     * Yields a list of FunctionInterface objects for the 'expr' of an AST_CALL.
     * @return \Generator
     */
    public function getFunctionFromNode()
    {
        $expression = $this->node;
        $code_base = $this->code_base;
        $context = $this->context;

        if ($expression->kind == ast\AST_VAR) {
            $variable_name = (new ContextNode(
                $code_base,
                $context,
                $expression
            ))->getVariableName();

            if (empty($variable_name)) {
                return;
            }

            // $var() - hopefully a closure, otherwise we don't know
            if ($context->getScope()->hasVariableWithName(
                $variable_name
            )) {
                $variable = $context->getScope()
                    ->getVariableByName($variable_name);

                $union_type = $variable->getUnionType();
                if ($union_type->isEmpty()) {
                    return;
                }

                foreach ($union_type->getTypeSet() as $type) {
                    // TODO: Allow CallableType to have FQSENs as well, e.g. `$x = [MyClass::class, 'myMethod']` has an FQSEN in a sense.
                    if ($type instanceof ClosureType) {
                        $closure_fqsen =
                            FullyQualifiedFunctionName::fromFullyQualifiedString(
                                (string)$type->asFQSEN()
                            );

                        if ($code_base->hasFunctionWithFQSEN(
                            $closure_fqsen
                        )) {
                            // Get the closure
                            $function = $code_base->getFunctionByFQSEN(
                                $closure_fqsen
                            );

                            yield $function;
                        }
                    } elseif ($type instanceof FunctionLikeDeclarationType) {
                        yield $type;
                    }
                }
            }
        } elseif ($expression->kind == ast\AST_NAME
            // nothing to do
        ) {
            try {
                $method = (new ContextNode(
                    $code_base,
                    $context,
                    $expression
                ))->getFunction($expression->children['name']);
            } catch (IssueException $exception) {
                Issue::maybeEmitInstance(
                    $code_base,
                    $context,
                    $exception->getIssueInstance()
                );
                return $context;
            } catch (EmptyFQSENException $exception) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::EmptyFQSENInCallable,
                    $expression->children['name']->lineno ?? $context->getLineNumberStart(),
                    $exception->getFQSEN()
                );
                return $context;
            }

            yield $method;
        } elseif ($expression->kind == ast\AST_CALL
            || $expression->kind == ast\AST_STATIC_CALL
            || $expression->kind == ast\AST_NEW
            || $expression->kind == ast\AST_METHOD_CALL
        ) {
            $class_list = (new ContextNode(
                $code_base,
                $context,
                $expression
            ))->getClassList(false, self::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME);

            foreach ($class_list as $class) {
                if (!$class->hasMethodWithName(
                    $code_base,
                    '__invoke'
                )) {
                    continue;
                }

                $method = $class->getMethodByName(
                    $code_base,
                    '__invoke'
                );

                // Check the call for parameter and argument types
                yield $method;
            }
        } elseif ($expression->kind === ast\AST_CLOSURE) {
            $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext(
                $context->withLineNumberStart($expression->lineno ?? 0),
                $expression
            );
            $method = $code_base->getFunctionByFQSEN($closure_fqsen);
            yield $method;
        }
        // TODO: AST_CLOSURE
    }

    private function throwUndeclaredFunctionIssueException(FullyQualifiedFunctionName $function_fqsen)
    {
        throw new IssueException(
            Issue::fromType(Issue::UndeclaredFunction)(
                $this->context->getFile(),
                $this->node->lineno ?? 0,
                [ "$function_fqsen()" ]
            )
        );
    }

    /**
     * @param string $function_name
     * The name of the function we'd like to look up
     *
     * @param bool $is_function_declaration
     * This must be set to true if we're getting a function
     * that is being declared and false if we're getting a
     * function being called.
     *
     * @return FunctionInterface
     * A method with the given name in the given context
     *
     * @throws IssueException
     * An exception is thrown if we can't find the given
     * function
     */
    public function getFunction(
        string $function_name,
        bool $is_function_declaration = false
    ) : FunctionInterface {

        \assert(
            $this->node instanceof ast\Node,
            '$this->node must be a node'
        );
        $code_base = $this->code_base;
        $context = $this->context;
        $namespace = $context->getNamespace();
        // TODO: support namespace aliases for functions
        if ($is_function_declaration) {
            $function_fqsen = FullyQualifiedFunctionName::make($namespace, $function_name);
            if ($code_base->hasFunctionWithFQSEN($function_fqsen)) {
                return $code_base->getFunctionByFQSEN($function_fqsen);
            }
        } elseif (($this->node->flags & ast\flags\NAME_RELATIVE) !== 0) {
            $function_fqsen = FullyQualifiedFunctionName::make($namespace, $function_name);
            if (!$code_base->hasFunctionWithFQSEN($function_fqsen)) {
                $this->throwUndeclaredFunctionIssueException($function_fqsen);
            }
            return $code_base->getFunctionByFQSEN($function_fqsen);
        } else {
            if (($this->node->flags & ast\flags\NAME_NOT_FQ) !== 0) {
                if ($context->hasNamespaceMapFor(\ast\flags\USE_FUNCTION, $function_name)) {
                    // If we already have `use function_name;`
                    $function_fqsen = $context->getNamespaceMapFor(\ast\flags\USE_FUNCTION, $function_name);
                    \assert($function_fqsen instanceof FullyQualifiedFunctionName);

                    // Make sure the method we're calling actually exists
                    if (!$code_base->hasFunctionWithFQSEN($function_fqsen)) {
                        // The FQSEN from 'use MyNS\function_name;' was the only possible fqsen for that function.
                        $this->throwUndeclaredFunctionIssueException($function_fqsen);
                    }

                    return $code_base->getFunctionByFQSEN($function_fqsen);
                }
                // For relative and non-fully qualified functions (e.g. namespace\foo(), foo())
                $function_fqsen = FullyQualifiedFunctionName::make($namespace, $function_name);

                if ($code_base->hasFunctionWithFQSEN($function_fqsen)) {
                    return $code_base->getFunctionByFQSEN($function_fqsen);
                }
                if ($namespace === '') {
                    throw new IssueException(
                        Issue::fromType(Issue::UndeclaredFunction)(
                            $this->context->getFile(),
                            $this->node->lineno ?? 0,
                            [ "$function_fqsen()" ]
                        )
                    );
                }
                // If it doesn't exist in the local namespace, try it
                // in the global namespace
            }
            $function_fqsen =
                FullyQualifiedFunctionName::fromStringInContext(
                    $function_name,
                    $this->context
                );
        }

        // Make sure the method we're calling actually exists
        if (!$code_base->hasFunctionWithFQSEN($function_fqsen)) {
            $this->throwUndeclaredFunctionIssueException($function_fqsen);
        }

        return $code_base->getFunctionByFQSEN($function_fqsen);
    }

    /**
     * @return Variable
     * A variable in scope or a new variable
     *
     * @throws NodeException
     * An exception is thrown if we can't understand the node
     *
     * @throws IssueException
     * A IssueException is thrown if the variable doesn't
     * exist
     */
    public function getVariable() : Variable
    {
        \assert(
            $this->node instanceof ast\Node,
            '$this->node must be a node'
        );

        // Get the name of the variable
        $variable_name = $this->getVariableName();

        if (empty($variable_name)) {
            throw new NodeException(
                $this->node,
                "Variable name not found"
            );
        }

        // Check to see if the variable exists in this scope
        if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
            throw new IssueException(
                Issue::fromType(Issue::UndeclaredVariable)(
                    $this->context->getFile(),
                    $this->node->lineno ?? 0,
                    [ $variable_name ]
                )
            );
        }

        return $this->context->getScope()->getVariableByName(
            $variable_name
        );
    }

    /**
     * @return Variable
     * A variable in scope or a new variable
     *
     * @throws NodeException
     * An exception is thrown if we can't understand the node
     */
    public function getOrCreateVariable() : Variable
    {
        try {
            return $this->getVariable();
        } catch (IssueException $exception) {
            // Swallow it
        }

        \assert(
            $this->node instanceof ast\Node,
            '$this->node must be a node'
        );

        // Create a new variable
        $variable = Variable::fromNodeInContext(
            $this->node,
            $this->context,
            $this->code_base,
            false
        );

        $this->context->addScopeVariable($variable);

        return $variable;
    }

    /**
     * @param string|Node $property_name
     * The name of the property we're looking up
     *
     * @param bool $is_static
     * True if we're looking for a static property,
     * false if we're looking for an instance property.
     *
     * @return Property
     * A variable in scope or a new variable
     *
     * @throws NodeException
     * An exception is thrown if we can't understand the node
     *
     * @throws IssueException
     * An exception is thrown if we can't find the given
     * class or if we don't have access to the property (its
     * private or protected)
     * or if the property is static and missing.
     *
     * @throws TypeException
     * An exception may be thrown if the only viable candidate
     * is a non-class type.
     *
     * @throws UnanalyzableException
     * An exception is thrown if we hit a construct in which
     * we can't determine if the property exists or not
     */
    public function getProperty(
        $property_name,
        bool $is_static
    ) : Property {
        $node = $this->node;

        \assert(
            $node instanceof ast\Node,
            '$this->node must be a node'
        );

        $property_name = $node->children['prop'];

        // Give up for things like C::$prop_name
        if (!\is_string($property_name)) {
            throw new NodeException(
                $node,
                "Cannot figure out non-string property name"
            );
        }

        $class_fqsen = null;

        try {
            $expected_type_categories = $is_static ? self::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME : self::CLASS_LIST_ACCEPT_OBJECT;
            $expected_issue = $is_static ? Issue::TypeExpectedObjectStaticPropAccess : Issue::TypeExpectedObjectPropAccess;
            $class_list = (new ContextNode(
                $this->code_base,
                $this->context,
                $node->children['expr'] ??
                    $node->children['class']
            ))->getClassList(true, $expected_type_categories, $expected_issue);
        } catch (CodeBaseException $exception) {
            if ($is_static) {
                throw new IssueException(
                    Issue::fromType(Issue::UndeclaredStaticProperty)(
                        $this->context->getFile(),
                        $node->lineno ?? 0,
                        [ $property_name, (string)$exception->getFQSEN() ]
                    )
                );
            } else {
                throw new IssueException(
                    Issue::fromType(Issue::UndeclaredProperty)(
                        $this->context->getFile(),
                        $node->lineno ?? 0,
                        [ "{$exception->getFQSEN()}->$property_name" ]
                    )
                );
            }
        }

        foreach ($class_list as $class) {
            $class_fqsen = $class->getFQSEN();

            // Keep hunting if this class doesn't have the given
            // property
            if (!$class->hasPropertyWithName(
                $this->code_base,
                $property_name
            )) {
                // (if fetching an instance property)
                // If there's a getter on properties then all
                // bets are off. However, @phan-forbid-undeclared-magic-properties
                // will make this method analyze the code as if all properties were declared or had @property annotations.
                if (!$is_static && $class->hasGetMethod($this->code_base) && !$class->getForbidUndeclaredMagicProperties($this->code_base)) {
                    throw new UnanalyzableException(
                        $node,
                        "Can't determine if property {$property_name} exists in class {$class->getFQSEN()} with __get defined"
                    );
                }

                continue;
            }

            $property = $class->getPropertyByNameInContext(
                $this->code_base,
                $property_name,
                $this->context,
                $is_static
            );

            if ($property->isDeprecated()) {
                throw new IssueException(
                    Issue::fromType(Issue::DeprecatedProperty)(
                        $this->context->getFile(),
                        $node->lineno ?? 0,
                        [
                            (string)$property->getFQSEN(),
                            $property->getFileRef()->getFile(),
                            $property->getFileRef()->getLineNumberStart(),
                        ]
                    )
                );
            }

            if ($property->isNSInternal($this->code_base)
                && !$property->isNSInternalAccessFromContext(
                    $this->code_base,
                    $this->context
                )
            ) {
                throw new IssueException(
                    Issue::fromType(Issue::AccessPropertyInternal)(
                        $this->context->getFile(),
                        $node->lineno ?? 0,
                        [
                            (string)$property->getFQSEN(),
                            $property->getElementNamespace(),
                            $property->getFileRef()->getFile(),
                            $property->getFileRef()->getLineNumberStart(),
                            $this->context->getNamespace(),
                        ]
                    )
                );
            }

            return $property;
        }

        // Since we didn't find the property on any of the
        // possible classes, check for classes with dynamic
        // properties
        if (!$is_static) {
            foreach ($class_list as $class) {
                if (Config::getValue('allow_missing_properties')
                    || $class->getHasDynamicProperties($this->code_base)
                ) {
                    return $class->getPropertyByNameInContext(
                        $this->code_base,
                        $property_name,
                        $this->context,
                        $is_static
                    );
                }
            }
        }

        /*
        $std_class_fqsen =
            FullyQualifiedClassName::getStdClassFQSEN();

        // If missing properties are cool, create it on
        // the first class we found
        if (!$is_static && ($class_fqsen && ($class_fqsen === $std_class_fqsen))
            || Config::getValue('allow_missing_properties')
        ) {
            if (count($class_list) > 0) {
                $class = $class_list[0];
                return $class->getPropertyByNameInContext(
                    $this->code_base,
                    $property_name,
                    $this->context,
                    $is_static
                );
            }
        }
        */

        // If the class isn't found, we'll get the message elsewhere
        if ($class_fqsen) {
            if ($is_static) {
                throw new IssueException(
                    Issue::fromType(Issue::UndeclaredStaticProperty)(
                        $this->context->getFile(),
                        $node->lineno ?? 0,
                        [ $property_name, (string)$class_fqsen ]
                    )
                );
            } else {
                throw new IssueException(
                    Issue::fromType(Issue::UndeclaredProperty)(
                        $this->context->getFile(),
                        $node->lineno ?? 0,
                        [ "$class_fqsen->$property_name" ]
                    )
                );
            }
        }

        throw new NodeException(
            $node,
            "Cannot figure out property from {$this->context}"
        );
    }

    /**
     * @return Property
     * A variable in scope or a new variable
     *
     * @throws NodeException
     * An exception is thrown if we can't understand the node
     *
     * @throws UnanalyzableException
     * An exception is thrown if we can't find the given
     * class
     *
     * @throws CodeBaseException
     * An exception is thrown if we can't find the given
     * class
     *
     * @throws TypeException
     * An exception may be thrown if the only viable candidate
     * is a non-class type.
     *
     * @throws IssueException
     * An exception is thrown if $is_static, but the property doesn't exist.
     */
    public function getOrCreateProperty(
        string $property_name,
        bool $is_static
    ) : Property {

        try {
            return $this->getProperty($property_name, $is_static);
        } catch (IssueException $exception) {
            if ($is_static) {
                throw $exception;
            }
            // TODO: log types of IssueException that aren't for undeclared properties?
            // (in another PR)

            // For instance properties, ignore it,
            // because we'll create our own property
        } catch (UnanalyzableException $exception) {
            if ($is_static) {
                throw $exception;
            }
            // For instance properties, ignore it,
            // because we'll create our own property
        }

        \assert(
            $this->node instanceof ast\Node,
            '$this->node must be a node'
        );

        try {
            $expected_type_categories = $is_static ? self::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME : self::CLASS_LIST_ACCEPT_OBJECT;
            $expected_issue = $is_static ? Issue::TypeExpectedObjectStaticPropAccess : Issue::TypeExpectedObjectPropAccess;
            $class_list = (new ContextNode(
                $this->code_base,
                $this->context,
                $this->node->children['expr'] ?? null
            ))->getClassList(false, $expected_type_categories, $expected_issue);
        } catch (CodeBaseException $exception) {
            throw new IssueException(
                Issue::fromType(Issue::UndeclaredClassReference)(
                    $this->context->getFile(),
                    $this->node->lineno ?? 0,
                    [ $exception->getFQSEN() ]
                )
            );
        }

        $class = \reset($class_list);

        if (!($class instanceof Clazz)) {
            // empty list
            throw new UnanalyzableException(
                $this->node,
                "Could not get class name from node"
            );
        }

        $flags = 0;
        if ($this->node->kind == ast\AST_STATIC_PROP) {
            $flags |= ast\flags\MODIFIER_STATIC;
        }

        $property_fqsen = FullyQualifiedPropertyName::make(
            $class->getFQSEN(),
            $property_name
        );

        // Otherwise, we'll create it
        $property = new Property(
            $this->context,
            $property_name,
            UnionType::empty(),
            $flags,
            $property_fqsen
        );

        $class->addProperty($this->code_base, $property, new None);

        return $property;
    }

    /**
     * @return GlobalConstant
     * Get the (non-class) constant associated with this node
     * in this context
     *
     * @throws NodeException
     * An exception is thrown if we can't understand the node
     *
     * @throws CodeBaseException
     * An exception is thrown if we can't find the given
     * class
     */
    public function getConst() : GlobalConstant
    {
        $node = $this->node;
        \assert(
            $node instanceof ast\Node,
            '$node must be a node'
        );

        \assert(
            $node->kind === ast\AST_CONST,
            "Node must be of type ast\AST_CONST"
        );

        $constant_name = $node->children['name']->children['name'] ?? null;
        if (!\is_string($constant_name)) {
            throw new NodeException(
                $node,
                "Can't determine constant name"
            );
        }

        $code_base = $this->code_base;

        $constant_name_lower = \strtolower($constant_name);
        if ($constant_name_lower === 'true' || $constant_name_lower === 'false' || $constant_name_lower === 'null') {
            return $code_base->getGlobalConstantByFQSEN(
                FullyQualifiedGlobalConstantName::fromFullyQualifiedString(
                    $constant_name_lower
                )
            );
        }

        $context = $this->context;

        $fqsen = FullyQualifiedGlobalConstantName::fromStringInContext(
            $constant_name,
            $context
        );

        if (!$code_base->hasGlobalConstantWithFQSEN($fqsen)) {
            // TODO: Optimize ? (checking namespace map twice)
            if ($context->hasNamespaceMapFor(\ast\flags\USE_CONST, $constant_name)) {
                throw new IssueException(
                    Issue::fromType(Issue::UndeclaredConstant)(
                        $context->getFile(),
                        $node->lineno ?? 0,
                        [ $fqsen ]
                    )
                );
            }

            $fqsen = FullyQualifiedGlobalConstantName::fromFullyQualifiedString(
                $constant_name
            );

            if (!$code_base->hasGlobalConstantWithFQSEN($fqsen)) {
                throw new IssueException(
                    Issue::fromType(Issue::UndeclaredConstant)(
                        $context->getFile(),
                        $node->lineno ?? 0,
                        [ $fqsen ]
                    )
                );
            }
        }

        $constant = $code_base->getGlobalConstantByFQSEN($fqsen);

        if ($constant->isNSInternal($code_base)
            && !$constant->isNSInternalAccessFromContext(
                $code_base,
                $context
            )
        ) {
            throw new IssueException(
                Issue::fromType(Issue::AccessConstantInternal)(
                    $context->getFile(),
                    $node->lineno ?? 0,
                    [
                        (string)$constant->getFQSEN(),
                        $constant->getElementNamespace(),
                        $constant->getFileRef()->getFile(),
                        $constant->getFileRef()->getLineNumberStart(),
                        $context->getNamespace()
                    ]
                )
            );
        }

        return $constant;
    }

    /**
     * @return ClassConstant
     * Get the (non-class) constant associated with this node
     * in this context
     *
     * @throws NodeException
     * An exception is thrown if we can't understand the node
     *
     * @throws CodeBaseException
     * An exception is thrown if we can't find the given
     * class
     *
     * @throws UnanalyzableException
     * An exception is thrown if we hit a construct in which
     * we can't determine if the property exists or not
     *
     * @throws IssueException
     * An exception is thrown if an issue is found while getting
     * the list of possible classes.
     */
    public function getClassConst() : ClassConstant
    {
        \assert(
            $this->node instanceof Node,
            '$this->node must be a node'
        );

        \assert(
            $this->node->kind === ast\AST_CLASS_CONST,
            "Node must be of type ast\AST_CLASS_CONST"
        );

        $constant_name = $this->node->children['const'];

        $class_fqsen = null;

        try {
            $class_list = (new ContextNode(
                $this->code_base,
                $this->context,
                $this->node->children['class']
            ))->getClassList(false, self::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME);
        } catch (CodeBaseException $exception) {
            throw new IssueException(
                Issue::fromType(Issue::UndeclaredClassConstant)(
                    $this->context->getFile(),
                    $this->node->lineno ?? 0,
                    [ $constant_name, $exception->getFQSEN() ]
                )
            );
        }

        foreach ($class_list as $class) {
            // Remember the last analyzed class for the next issue message
            $class_fqsen = $class->getFQSEN();

            // Check to see if the class has the constant
            if (!$class->hasConstantWithName(
                $this->code_base,
                $constant_name
            )) {
                continue;
            }

            $constant = $class->getConstantByNameInContext(
                $this->code_base,
                $constant_name,
                $this->context
            );

            if ($constant->isNSInternal($this->code_base)
                && !$constant->isNSInternalAccessFromContext(
                    $this->code_base,
                    $this->context
                )
            ) {
                throw new IssueException(
                    Issue::fromType(Issue::AccessClassConstantInternal)(
                        $this->context->getFile(),
                        $this->node->lineno ?? 0,
                        [
                            (string)$constant->getFQSEN(),
                            $constant->getFileRef()->getFile(),
                            $constant->getFileRef()->getLineNumberStart(),
                        ]
                    )
                );
            }

            return $constant;
        }

        // If no class is found, we'll emit the error elsewhere
        if ($class_fqsen) {
            throw new IssueException(
                Issue::fromType(Issue::UndeclaredConstant)(
                    $this->context->getFile(),
                    $this->node->lineno ?? 0,
                    [ "$class_fqsen::$constant_name" ]
                )
            );
        }

        throw new NodeException(
            $this->node,
            "Can't figure out constant {$constant_name} in node"
        );
    }

    /**
     * @return string
     * A unique and stable name for an anonymous class
     */
    public function getUnqualifiedNameForAnonymousClass() : string
    {
        \assert(
            $this->node instanceof ast\Node,
            '$this->node must be a node'
        );

        \assert(
            (bool)($this->node->flags & ast\flags\CLASS_ANONYMOUS),
            "Node must be an anonymous class node"
        );

        $class_name = 'anonymous_class_'
            . \substr(\md5(\implode('|', [
                $this->context->getFile(),
                $this->context->getLineNumberStart()
            ])), 0, 8);

        return $class_name;
    }

    /**
     * @return Func
     */
    public function getClosure() : Func
    {
        $closure_fqsen =
            FullyQualifiedFunctionName::fromClosureInContext(
                $this->context,
                $this->node
            );

        if (!$this->code_base->hasFunctionWithFQSEN($closure_fqsen)) {
            throw new CodeBaseException(
                $closure_fqsen,
                "Could not find closure $closure_fqsen"
            );
        }

        return $this->code_base->getFunctionByFQSEN($closure_fqsen);
    }

    /**
     * Perform some backwards compatibility checks on a node.
     * This ignores union types, and can be run in the parse phase.
     * (It often should, because outside quick mode, it may be run multiple times per node)
     *
     * @return void
     */
    public function analyzeBackwardCompatibility()
    {
        if (!Config::get_backward_compatibility_checks()) {
            return;
        }

        if (!($this->node instanceof ast\Node) || empty($this->node->children['expr'])) {
            return;
        }

        if ($this->node->kind === ast\AST_STATIC_CALL ||
           $this->node->kind === ast\AST_METHOD_CALL) {
            return;
        }

        $llnode = $this->node;

        if ($this->node->kind !== ast\AST_DIM) {
            if (!($this->node->children['expr'] instanceof Node)) {
                return;
            }

            if ($this->node->children['expr']->kind !== ast\AST_DIM) {
                (new ContextNode(
                    $this->code_base,
                    $this->context,
                    $this->node->children['expr']
                ))->analyzeBackwardCompatibility();
                return;
            }

            $temp = $this->node->children['expr']->children['expr'];
            $llnode = $this->node->children['expr'];
            $lnode = $temp;
        } else {
            $temp = $this->node->children['expr'];
            $lnode = $temp;
        }

        // Strings can have DIMs, it turns out.
        if (!($temp instanceof Node)) {
            return;
        }

        if (!($temp->kind == ast\AST_PROP
            || $temp->kind == ast\AST_STATIC_PROP
        )) {
            return;
        }

        while ($temp instanceof Node
            && ($temp->kind == ast\AST_PROP
            || $temp->kind == ast\AST_STATIC_PROP)
        ) {
            $llnode = $lnode;
            $lnode = $temp;

            // Lets just hope the 0th is the expression
            // we want
            $temp = \array_values($temp->children)[0];
        }

        if (!($temp instanceof Node)) {
            return;
        }

        // Foo::$bar['baz'](); is a problem
        // Foo::$bar['baz'] is not
        if ($lnode->kind === ast\AST_STATIC_PROP
            && $this->node->kind !== ast\AST_CALL
        ) {
            return;
        }

        // $this->$bar['baz']; is a problem
        // $this->bar['baz'] is not
        if ($lnode->kind === ast\AST_PROP
            && !($lnode->children['prop'] instanceof Node)
            && !($llnode->children['prop'] instanceof Node)
        ) {
            return;
        }

        if ((
                (
                    $lnode->children['prop'] instanceof Node
                    && $lnode->children['prop']->kind == ast\AST_VAR
                )
                ||
                (
                    !empty($lnode->children['class'])
                    && $lnode->children['class'] instanceof Node
                    && (
                        $lnode->children['class']->kind == ast\AST_VAR
                        || $lnode->children['class']->kind == ast\AST_NAME
                    )
                )
                ||
                (
                    !empty($lnode->children['expr'])
                    && $lnode->children['expr'] instanceof Node
                    && (
                        $lnode->children['expr']->kind == ast\AST_VAR
                        || $lnode->children['expr']->kind == ast\AST_NAME
                    )
                )
            )
            &&
            (
                $temp->kind == ast\AST_VAR
                || $temp->kind == ast\AST_NAME
            )
        ) {
            $cache_entry = FileCache::getOrReadEntry($this->context->getFile());
            $line = $cache_entry->getLine($this->node->lineno);
            \assert(\is_string($line));
            unset($cache_entry);
            if (strpos($line, '}[') === false
                || strpos($line, ']}') === false
                || strpos($line, '>{') === false
            ) {
                Issue::maybeEmit(
                    $this->code_base,
                    $this->context,
                    Issue::CompatiblePHP7,
                    $this->node->lineno ?? 0
                );
            }
        }
    }

    /**
     * @return ?FullyQualifiedClassName
     * @throws IssueException if the list of possible classes couldn't be determined.
     */
    public function resolveClassNameInContext()
    {
        // A function argument to resolve into an FQSEN
        $arg = $this->node;

        if (\is_string($arg)) {
            // Class_alias treats arguments as fully qualified strings.
            return FullyQualifiedClassName::fromFullyQualifiedString($arg);
        }
        if ($arg instanceof Node
            && $arg->kind === ast\AST_CLASS_CONST
            && \strcasecmp($arg->children['const'], 'class') === 0
        ) {
            $class_type = (new ContextNode(
                $this->code_base,
                $this->context,
                $arg->children['class']
            ))->getClassUnionType();

            // If we find a class definition, then return it. There should be 0 or 1.
            // (Expressions such as 'int::class' are syntactically valid, but would have 0 results).
            foreach ($class_type->asClassFQSENList($this->context) as $class_fqsen) {
                return $class_fqsen;
            }
        }

        $class_name = $this->getEquivalentPHPScalarValue();
        // TODO: Emit
        if (\is_string($class_name)) {
            if (\preg_match('/^\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\\\]*$/', $class_name)) {
                return FullyQualifiedClassName::fromFullyQualifiedString($class_name);
            }
        }

        return null;
    }

    // Flags for getEquivalentPHPValue

    // Should this attempt to resolve arrays?
    const RESOLVE_ARRAYS = (1<<0);
    // Should this attempt to resolve array keys?
    const RESOLVE_ARRAY_KEYS = (1<<1);
    // Should this attempt to resolve array values?
    const RESOLVE_ARRAY_VALUES = (1<<2);
    // Should this attempt to resolve accesses to constants?
    const RESOLVE_CONSTANTS = (1<<3);
    // If resolving array keys fails, should this use a placeholder?
    const RESOLVE_KEYS_USE_FALLBACK_PLACEHOLDER = (1<<4);
    // Skip unknown keys
    const RESOLVE_KEYS_SKIP_UNKNOWN_KEYS = (1<<5);

    const RESOLVE_DEFAULT =
        self::RESOLVE_ARRAYS |
        self::RESOLVE_ARRAY_KEYS |
        self::RESOLVE_ARRAY_VALUES |
        self::RESOLVE_CONSTANTS |
        self::RESOLVE_KEYS_USE_FALLBACK_PLACEHOLDER;

    const RESOLVE_SCALAR_DEFAULT =
        self::RESOLVE_CONSTANTS |
        self::RESOLVE_KEYS_USE_FALLBACK_PLACEHOLDER;

    /**
     * @param Node[]|string[]|float[]|int[] $children
     * @param int $flags - See self::RESOLVE_*
     * @return ?array - array if elements could be resolved.
     */
    private function getEquivalentPHPArrayElements(array $children, int $flags)
    {
        $elements = [];
        foreach ($children as $child_node) {
            $key_node = ($flags & self::RESOLVE_ARRAY_KEYS) != 0 ? $child_node->children['key'] : null;
            $value_node = $child_node->children['value'];
            if (self::RESOLVE_ARRAY_VALUES) {
                $value_node = $this->getEquivalentPHPValueForNode($value_node, $flags);
            }
            // NOTE: this has some overlap with DuplicateKeyPlugin
            if ($key_node === null) {
                $elements[] = $value_node;
            } elseif (\is_scalar($key_node)) {
                $elements[$key_node] = $value_node;  // Check for float?
            } else {
                $key = $this->getEquivalentPHPValueForNode($key_node, $flags);
                if (\is_scalar($key)) {
                    $elements[$key] = $value_node;
                } else {
                    if (($flags & self::RESOLVE_KEYS_USE_FALLBACK_PLACEHOLDER) !== 0) {
                        $elements[] = $value_node;
                    } else {
                        // TODO: Alternate strategies?
                        return null;
                    }
                }
            }
        }
        return $elements;
    }

    /**
     * This converts an AST node in context to the value it represents.
     * This is useful for plugins, etc, and will gradually improve.
     *
     * @see $this->getEquivalentPHPValue
     *
     * @param Node|float|int|string $node
     * @return Node|string[]|int[]|float[]|string|float|int|bool|null -
     *         If this could be resolved and we're certain of the value, this gets a raw PHP value for $node.
     *         Otherwise, this returns $node.
     */
    public function getEquivalentPHPValueForNode($node, int $flags)
    {
        if (!($node instanceof Node)) {
            return $node;
        }
        $kind = $node->kind;
        if ($kind === ast\AST_ARRAY) {
            if (($flags & self::RESOLVE_ARRAYS) === 0) {
                return $node;
            }
            $elements = $this->getEquivalentPHPArrayElements($node->children, $flags);
            if ($elements === null) {
                // Attempted to resolve elements but failed at one or more elements.
                return $node;
            }
            return $elements;
        } elseif ($kind === ast\AST_CONST) {
            $name = $node->children['name']->children['name'] ?? null;
            if (\is_string($name)) {
                switch (\strtolower($name)) {
                    case 'false':
                        return false;
                    case 'true':
                        return true;
                    case 'null':
                        return null;
                }
            }
            if (($flags & self::RESOLVE_CONSTANTS) === 0) {
                return $node;
            }
            try {
                $constant = (new ContextNode($this->code_base, $this->context, $node))->getConst();
            } catch (\Exception $e) {
                return $node;
            }
            // TODO: Recurse, but don't try to resolve constants again
            $new_node = $constant->getNodeForValue();
            if (is_object($new_node)) {
                // Avoid infinite recursion, only resolve once
                $new_node = $this->getEquivalentPHPValueForNode($new_node, $flags & ~self::RESOLVE_CONSTANTS);
            }
            return $new_node;
        } elseif ($kind === ast\AST_CLASS_CONST) {
            if (($flags & self::RESOLVE_CONSTANTS) === 0) {
                return $node;
            }
            try {
                $constant = (new ContextNode($this->code_base, $this->context, $node))->getClassConst();
            } catch (\Exception $e) {
                return $node;
            }
            // TODO: Recurse, but don't try to resolve constants again
            $new_node = $constant->getNodeForValue();
            if (is_object($new_node)) {
                // Avoid infinite recursion, only resolve once
                $new_node = $this->getEquivalentPHPValueForNode($new_node, $flags & ~self::RESOLVE_CONSTANTS);
            }
            return $new_node;
        } elseif ($kind === ast\AST_MAGIC_CONST) {
            return $this->getValueForMagicConstByNode($node);
        }
        return $node;
    }

    public function getValueForMagicConst()
    {
        $node = $this->node;
        \assert($node instanceof Node && $node->kind === ast\AST_MAGIC_CONST);
        return $this->getValueForMagicConstByNode($node);
    }

    public function getValueForMagicConstByNode(Node $node)
    {
        $context = $this->context;
        switch ($node->flags) {
            case ast\flags\MAGIC_CLASS:
                if ($context->isInClassScope()) {
                    return (string)$context->getClassFQSEN();
                }
                return $node;
            case ast\flags\MAGIC_FUNCTION:
                if ($context->isInFunctionLikeScope()) {
                    $fqsen = $context->getFunctionLikeFQSEN();
                    return $fqsen->isClosure() ? '{closure}' : $fqsen->getName();
                }
                return $node;
            case ast\flags\MAGIC_METHOD:
                // TODO: Is this right?
                if ($context->isInMethodScope()) {
                    return \ltrim((string)$context->getFunctionLikeFQSEN(), '\\');
                }
                return $node;
            case ast\flags\MAGIC_DIR:
                // TODO: Absolute directory?
                return \dirname($context->getFile());
            case ast\flags\MAGIC_FILE:
                return $context->getFile();
            case ast\flags\MAGIC_LINE:
                return $node->lineno ?? $context->getLineNumberStart();
            case ast\flags\MAGIC_NAMESPACE:
                return \ltrim($context->getNamespace(), '\\');
            case ast\flags\MAGIC_TRAIT:
                // TODO: Could check if in trait, low importance.
                if ($context->isInClassScope()) {
                    return (string)$context->getClassFQSEN();
                }
                return $node;
        }
        return $node;
    }

    /**
     * This converts an AST node in context to the value it represents.
     * This is useful for plugins, etc, and will gradually improve.
     *
     * This does not create new object instances.
     *
     * @return Node|string[]|int[]|float[]|string|float|int|bool|null -
     *   If this could be resolved and we're certain of the value, this gets an equivalent definition.
     *   Otherwise, this returns $node.
     * @throws InvalidArgumentException if the object could not be determined - Callers must catch this.
     */
    public function getEquivalentPHPValue(int $flags = self::RESOLVE_DEFAULT)
    {
        return $this->getEquivalentPHPValueForNode($this->node, $flags);
    }

    /**
     * This converts an AST node in context to the value it represents.
     * This is useful for plugins, etc, and will gradually improve.
     *
     * This does not create new object instances.
     *
     * @return Node|string|float|int|bool|null -
     *         If this could be resolved and we're certain of the value, this gets an equivalent definition.
     *         Otherwise, this returns $node. If this would be an array, this returns $node.
     *
     * @throws InvalidArgumentException if the object could not be determined - Callers must catch this.
     * @suppress PhanPartialTypeMismatchReturn the flags prevent this from returning an array
     */
    public function getEquivalentPHPScalarValue()
    {
        return $this->getEquivalentPHPValueForNode($this->node, self::RESOLVE_SCALAR_DEFAULT);
    }
}
<?php declare(strict_types=1);
namespace Phan\AST;

use Phan\AST\Visitor\KindVisitorImplementation;
use Phan\CodeBase;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\FQSEN;
use Phan\Language\UnionType;
use Phan\Language\Type;

// TODO: Move to AST\Visitor?
abstract class AnalysisVisitor extends KindVisitorImplementation
{
    /**
     * @var CodeBase
     * The code base within which we're operating
     */
    protected $code_base;

    /**
     * @var Context
     * The context in which the node we're going to be looking
     * at exits.
     */
    protected $context;

    /**
     * @param CodeBase $code_base
     * The code base within which we're operating
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     */
    public function __construct(
        CodeBase $code_base,
        Context $context
    ) {
        $this->context = $context;
        $this->code_base = $code_base;
    }

    /**
     * @param string $issue_type
     * The type of issue to emit such as Issue::ParentlessClass
     *
     * @param int $lineno
     * The line number where the issue was found
     *
     * @param int|string|FQSEN|UnionType|Type ...$parameters
     * Template parameters for the issue's error message
     *
     * @return void
     */
    protected function emitIssue(
        string $issue_type,
        int $lineno,
        ...$parameters
    ) {
        Issue::maybeEmitWithParameters(
            $this->code_base,
            $this->context,
            $issue_type,
            $lineno,
            $parameters
        );
    }
}
<?php declare(strict_types=1);

namespace Phan\AST;

use Phan\AST\TolerantASTConverter\TolerantASTConverter;
use Phan\AST\TolerantASTConverter\ParseException;
use Phan\CodeBase;
use Phan\Config;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Phan;

use ast\Node;
use ParseError;

class Parser
{
    /**
     * Parses the code. If $suppress_parse_errors is false, this also emits SyntaxError.
     *
     * @param CodeBase $code_base
     * @param Context $context
     * @param string $file_path file path for error reporting
     * @param string $file_contents file contents to pass to parser. May be overridden to ignore what is currently on disk.
     * @param bool $suppress_parse_errors (If true, don't emit SyntaxError)
     * @return ?Node
     * @throws ParseError
     * @throws ParseException
     */
    public static function parseCode(CodeBase $code_base, Context $context, string $file_path, string $file_contents, bool $suppress_parse_errors)
    {
        try {
            if (Config::getValue('use_polyfill_parser')) {
                // This helper method has its own exception handling.
                // It may throw a ParseException, which is unintentionally not caught here.
                return self::parseCodePolyfill($code_base, $context, $file_path, $file_contents, $suppress_parse_errors);
            }
            return \ast\parse_code(
                $file_contents,
                Config::AST_VERSION,
                $file_path
            );
        } catch (ParseError $native_parse_error) {
            if (!$suppress_parse_errors) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::SyntaxError,
                    $native_parse_error->getLine(),
                    $native_parse_error->getMessage()
                );
            }
            if (!Config::getValue('use_fallback_parser')) {
                // By default, don't try to re-parse files with syntax errors.
                throw $native_parse_error;
            }

            // If there's a parse error in a file that's excluded from analysis, give up on parsing it.
            // Users might not see the parse error, and ignoring it (e.g. acting as though a file in vendor/ or ext/
            // that can't be parsed has class and function definitions)
            // may lead to users not noticing bugs.
            if (Phan::isExcludedAnalysisFile($file_path)) {
                throw $native_parse_error;
            }
            // But if the user would see the syntax error, go ahead and retry.

            $converter = new TolerantASTConverter();
            $converter->setShouldAddPlaceholders(false);
            $converter->setPHPVersionId(Config::get_closest_target_php_version_id());
            $converter->setParseAllDocComments(Config::getValue('polyfill_parse_all_element_doc_comments'));
            $errors = [];
            try {
                $node = $converter->parseCodeAsPHPAST($file_contents, Config::AST_VERSION, $errors);
            } catch (\Exception $e) {
                // Generic fallback. TODO: log.
                throw $native_parse_error;
            }
            // TODO: loop over $errors?
            return $node;
        }
    }

    /**
     * Parses the code. If $suppress_parse_errors is false, this also emits SyntaxError.
     *
     * @param CodeBase $code_base
     * @param Context $context
     * @param string $file_path file path for error reporting
     * @param string $file_contents file contents to pass to parser. May be overridden to ignore what is currently on disk.
     * @param bool $suppress_parse_errors (If true, don't emit SyntaxError)
     * @return ?Node
     * @throws ParseException
     */
    public static function parseCodePolyfill(CodeBase $code_base, Context $context, string $file_path, string $file_contents, bool $suppress_parse_errors)
    {
        $converter = new TolerantASTConverter();
        $converter->setShouldAddPlaceholders(false);
        $converter->setPHPVersionId(Config::get_closest_target_php_version_id());
        $converter->setParseAllDocComments(Config::getValue('polyfill_parse_all_element_doc_comments'));
        $errors = [];
        try {
            $node = $converter->parseCodeAsPHPAST($file_contents, Config::AST_VERSION, $errors);
        } catch (\Exception $e) {
            // Generic fallback. TODO: log.
            throw new ParseException('Unexpected Exception of type ' . \get_class($e) . ': ' . $e->getMessage(), 0);
        }
        foreach ($errors as $diagnostic) {
            if ($diagnostic->kind === 0) {
                $diagnostic_error_start_line = 1 + \substr_count($file_contents, "\n", 0, $diagnostic->start);
                $diagnostic_error_message = 'Fallback parser diagnostic error: ' . $diagnostic->message;
                if (!$suppress_parse_errors) {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::SyntaxError,
                        $diagnostic_error_start_line,
                        $diagnostic_error_message
                    );
                }
                if (!Config::getValue('use_fallback_parser')) {
                    // By default, don't try to re-parse files with syntax errors.
                    throw new ParseException($diagnostic_error_message, $diagnostic_error_start_line);
                }

                // If there's a parse error in a file that's excluded from analysis, give up on parsing it.
                // Users might not see the parse error, and ignoring it (e.g. acting as though a file in vendor/ or ext/
                // that can't be parsed has class and function definitions)
                // may lead to users not noticing bugs.
                if (Phan::isExcludedAnalysisFile($file_path)) {
                    throw new ParseException($diagnostic_error_message, $diagnostic_error_start_line);
                }
            }
        }
        return $node;
    }
}
<?php declare(strict_types=1);
namespace Phan\AST;

use Phan\Analysis\AssignOperatorFlagVisitor;
use Phan\Analysis\BinaryOperatorFlagVisitor;
use Phan\Analysis\ConditionVisitor;
use Phan\Analysis\NegatedConditionVisitor;
use Phan\AST\Visitor\Element;
use Phan\CodeBase;
use Phan\Config;
use Phan\Debug;
use Phan\Exception\CodeBaseException;
use Phan\Exception\EmptyFQSENException;
use Phan\Exception\IssueException;
use Phan\Exception\NodeException;
use Phan\Exception\TypeException;
use Phan\Exception\UnanalyzableException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Variable;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionLikeName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedMethodName;
use Phan\Language\Scope\BranchScope;
use Phan\Language\Scope\GlobalScope;
use Phan\Language\Type;
use Phan\Language\Type\ArrayType;
use Phan\Language\Type\ArrayShapeType;
use Phan\Language\Type\BoolType;
use Phan\Language\Type\CallableType;
use Phan\Language\Type\ClosureType;
use Phan\Language\Type\FloatType;
use Phan\Language\Type\GenericArrayType;
use Phan\Language\Type\IntType;
use Phan\Language\Type\IterableType;
use Phan\Language\Type\MixedType;
use Phan\Language\Type\NullType;
use Phan\Language\Type\ObjectType;
use Phan\Language\Type\StringType;
use Phan\Language\Type\StaticType;
use Phan\Language\Type\TemplateType;
use Phan\Language\Type\VoidType;
use Phan\Language\UnionType;
use Phan\Language\UnionTypeBuilder;
use ast\Node;

/**
 * Determine the UnionType associated with a
 * given node
 * @phan-file-suppress PhanPartialTypeMismatchArgument node is complicated
 * @phan-file-suppress PhanPartialTypeMismatchArgumentInternal node is complicated
 */
class UnionTypeVisitor extends AnalysisVisitor
{
    /**
     * @var bool
     * Set to true to cause loggable issues to be thrown
     * instead of emitted as issues to the log.
     */
    private $should_catch_issue_exception = false;

    /**
     * @param CodeBase $code_base
     * The code base within which we're operating
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     *
     * @param bool $should_catch_issue_exception
     * Set to true to cause loggable issues to be thrown
     * instead of emitted as issues to the log.
     */
    public function __construct(
        CodeBase $code_base,
        Context $context,
        bool $should_catch_issue_exception = true
    ) {
        // Inlined to be more efficient.
        // parent::__construct($code_base, $context);
        $this->code_base = $code_base;
        $this->context = $context;

        $this->should_catch_issue_exception =
            $should_catch_issue_exception;
    }

    /**
     * @param CodeBase $code_base
     * The code base within which we're operating
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     *
     * @param Node|string|bool|int|float|null $node
     * The node for which we'd like to determine its type
     *
     * @param bool $should_catch_issue_exception
     * Set to true to cause loggable issues to be thrown
     * instead
     *
     * @return UnionType
     * The UnionType associated with the given node
     * in the given Context within the given CodeBase
     *
     * @throws IssueException
     * If $should_catch_issue_exception is false an IssueException may
     * be thrown for optional issues.
     */
    public static function unionTypeFromNode(
        CodeBase $code_base,
        Context $context,
        $node,
        bool $should_catch_issue_exception = true
    ) : UnionType {
        if (!($node instanceof Node)) {
            // TODO: String null shouldn't be a special case (or should be case insensitive)?
            if ($node === null || $node === 'null') {
                return UnionType::empty();
            }

            return Type::fromObject($node)->asUnionType();
        }
        $node_id = \spl_object_id($node);

        $cached_union_type = $context->getUnionTypeOfNodeIfCached($node_id, $should_catch_issue_exception);
        if ($cached_union_type !== null) {
            return $cached_union_type;
        }

        if ($should_catch_issue_exception) {
            try {
                $union_type = (new self(
                    $code_base,
                    $context,
                    $should_catch_issue_exception
                ))->{Element::VISIT_LOOKUP_TABLE[$node->kind] ?? 'visit'}($node);
                $context->setCachedUnionTypeOfNode($node_id, $union_type, true);
                return $union_type;
            } catch (IssueException $exception) {
                Issue::maybeEmitInstance(
                    $code_base,
                    $context,
                    $exception->getIssueInstance()
                );
                return UnionType::empty();
            }
        }

        $union_type = (new self(
            $code_base,
            $context,
            $should_catch_issue_exception
        ))->{Element::VISIT_LOOKUP_TABLE[$node->kind] ?? 'visit'}($node);

        $context->setCachedUnionTypeOfNode($node_id, $union_type, false);
        return $union_type;
    }

    /**
     * Default visitor for node kinds that do not have
     * an overriding method
     *
     * @param Node $node (@phan-unused-param)
     * An AST node we'd like to determine the UnionType
     * for
     *
     * @return UnionType
     * The set of types associated with the given node
     */
    public function visit(Node $node) : UnionType
    {
        /*
        throw new NodeException($node,
            'Visitor not implemented for node of type '
            . Debug::nodeName($node)
        );
        */
        return UnionType::empty();
    }

    /**
     * Visit a node with kind `\ast\AST_POST_INC`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitPostInc(Node $node) : UnionType
    {
        // TODO: Check if union type is sane (string/int)
        return self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['var']
        );
    }

    /**
     * Visit a node with kind `\ast\AST_POST_DEC`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitPostDec(Node $node) : UnionType
    {
        // TODO: Check if union type is sane (string/int)
        return self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['var']
        );
    }

    /**
     * Visit a node with kind `\ast\AST_PRE_DEC`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitPreDec(Node $node) : UnionType
    {
        // TODO: Check if union type is sane (string/int)
        return self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['var']
        );
    }

    /**
     * Visit a node with kind `\ast\AST_PRE_INC`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitPreInc(Node $node) : UnionType
    {
        // TODO: Check if union type is sane (string/int)
        return self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['var']
        );
    }

    /**
     * Visit a node with kind `\ast\AST_CLONE`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitClone(Node $node) : UnionType
    {
        // TODO: Check if union type is sane (Any object type)
        return self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr']
        );
    }

    /**
     * Visit a node with kind `\ast\AST_EMPTY`
     *
     * @param Node $node (@phan-unused-param)
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitEmpty(Node $node) : UnionType
    {
        return BoolType::instance(false)->asUnionType();
    }

    /**
     * Visit a node with kind `\ast\AST_ISSET`
     *
     * @param Node $node (@phan-unused-param)
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitIsset(Node $node) : UnionType
    {
        return BoolType::instance(false)->asUnionType();
    }

    /**
     * Visit a node with kind `\ast\AST_INCLUDE_OR_EVAL`
     *
     * @param Node $node (@phan-unused-param)
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitIncludeOrEval(Node $node) : UnionType
    {
        // require() can return arbitrary objects. Lets just
        // say that we don't know what it is and move on
        return UnionType::empty();
    }

    /**
     * Visit a node with kind `\ast\AST_MAGIC_CONST`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitMagicConst(Node $node) : UnionType
    {
        if ($node->flags === \ast\flags\MAGIC_LINE) {
            return IntType::instance(false)->asUnionType();
        }
        // This is for things like __METHOD__
        return StringType::instance(false)->asUnionType();
    }

    /**
     * Visit a node with kind `\ast\AST_ASSIGN_REF`
     * @see $this->visitAssign
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitAssignRef(Node $node) : UnionType
    {
        // TODO: Is there any way this should differ from analysis
        // (e.g. should subsequent assignments affect the right hand Node?)
        return $this->visitAssign($node);
    }

    /**
     * Visit a node with kind `\ast\AST_SHELL_EXEC`
     *
     * @param Node $node (@phan-unused-param)
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitShellExec(Node $node) : UnionType
    {
        return StringType::instance(false)->asUnionType();
    }

    /**
     * Visit a node with kind `\ast\AST_NAME`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitName(Node $node) : UnionType
    {
        if ($node->flags & \ast\flags\NAME_NOT_FQ) {
            if ('parent' === $node->children['name']) {
                if (!$this->context->isInClassScope()) {
                    throw new IssueException(
                        Issue::fromType(Issue::ContextNotObject)(
                            $this->context->getFile(),
                            $this->context->getLineNumberStart(),
                            [
                                'parent'
                            ]
                        )
                    );
                }
                $class = $this->context->getClassInScope($this->code_base);

                if ($class->hasParentType()) {
                    return Type::fromFullyQualifiedString(
                        (string)$class->getParentClassFQSEN()
                    )->asUnionType();
                } else {
                    if (!$class->isTrait()) {
                        $this->emitIssue(
                            Issue::ParentlessClass,
                            $node->lineno ?? 0,
                            (string)$class->getFQSEN()
                        );
                    }

                    return UnionType::empty();
                }
            }

            return Type::fromStringInContext(
                $node->children['name'],
                $this->context,
                Type::FROM_NODE
            )->asUnionType();
        }

        if ($node->flags & \ast\flags\NAME_RELATIVE) {  // $x = new namespace\Foo();
            $fully_qualified_name = $this->context->getNamespace() . '\\' . $node->children['name'];
            return Type::fromFullyQualifiedString(
                $fully_qualified_name
            )->asUnionType();
        }
        // Sometimes 0 for a fully qualified name?
        // \assert(($node->flags & \ast\flags\NAME_FQ) !== 0, "All flags must match");

        return Type::fromFullyQualifiedString(
            '\\' . $node->children['name']
        )->asUnionType();
    }

    /**
     * Visit a node with kind `\ast\AST_TYPE`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitType(Node $node) : UnionType
    {
        switch ($node->flags) {
            case \ast\flags\TYPE_ARRAY:
                return ArrayType::instance(false)->asUnionType();
            case \ast\flags\TYPE_BOOL:
                return BoolType::instance(false)->asUnionType();
            case \ast\flags\TYPE_CALLABLE:
                return CallableType::instance(false)->asUnionType();
            case \ast\flags\TYPE_DOUBLE:
                return FloatType::instance(false)->asUnionType();
            case \ast\flags\TYPE_ITERABLE:
                return IterableType::instance(false)->asUnionType();
            case \ast\flags\TYPE_LONG:
                return IntType::instance(false)->asUnionType();
            case \ast\flags\TYPE_NULL:
                return NullType::instance(false)->asUnionType();
            case \ast\flags\TYPE_OBJECT:
                return ObjectType::instance(false)->asUnionType();
            case \ast\flags\TYPE_STRING:
                return StringType::instance(false)->asUnionType();
            case \ast\flags\TYPE_VOID:
                return VoidType::instance(false)->asUnionType();
            default:
                throw new \AssertionError("All flags must match. Found "
                    . Debug::astFlagDescription($node->flags ?? 0, $node->kind));
        }
    }

    /**
     * Visit a node with kind `\ast\AST_TYPE` representing
     * a nullable type such as `?string`.
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitNullableType(Node $node) : UnionType
    {
        // Get the type
        $union_type = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['type'],
            $this->should_catch_issue_exception
        );

        // Make each nullable
        return $union_type->asMappedUnionType(function (Type $type) : Type {
            return $type->withIsNullable(true);
        });
    }

    /**
     * @param int|float|string|Node $node
     * @return ?UnionType
     */
    public static function unionTypeFromLiteralOrConstant(CodeBase $code_base, Context $context, $node)
    {
        if ($node instanceof Node) {
            // TODO: Could check for arrays of constants or literals, and convert those to the generic array types
            if ($node->kind === \ast\AST_CONST || $node->kind === \ast\AST_CLASS_CONST) {
                try {
                    return UnionTypeVisitor::unionTypeFromNode($code_base, $context, $node, false);
                } catch (IssueException $e) {
                    return null;
                }
            }
            return null;
        }
        // Otherwise, this is an int/float/string.
        \assert(\is_scalar($node), 'node must be Node or scalar');
        return Type::fromObject($node)->asUnionType();
    }

    /**
     * @param int|float|string|Node $cond
     * @return ?bool
     */
    public static function checkCondUnconditionalTruthiness($cond)
    {
        if ($cond instanceof Node) {
            if ($cond->kind === \ast\AST_CONST) {
                $name = $cond->children['name'];
                if ($name->kind === \ast\AST_NAME) {
                    switch (\strtolower($name->children['name'])) {
                        case 'true':
                            return true;
                        case 'false':
                            return false;
                        case 'null':
                            return false;
                        default:
                            // Could add heuristics based on internal/user-defined constant values, but that is unreliable.
                            // (E.g. feature flags for an extension may be true or false, depending on the environment)
                            // (and Phan doesn't store constant values for user-defined constants, only the types)
                            return null;
                    }
                }
            }
            return null;
        }
        // Otherwise, this is an int/float/string.
        // Use the exact same truthiness rules as PHP to check if the conditional is truthy.
        // (e.g. "0" and 0.0 and '' are false)
        \assert(\is_scalar($cond), 'cond must be Node or scalar');
        return (bool)$cond;
    }

    /**
     * Visit a node with kind `\ast\AST_CONDITIONAL`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitConditional(Node $node) : UnionType
    {
        $cond_node = $node->children['cond'];
        $cond_truthiness = self::checkCondUnconditionalTruthiness($cond_node);
        // For the shorthand $a ?: $b, the cond node will be the truthy value.
        // Note: an ast node will never be null(can be unset), it will be a const AST node with the name null.
        $true_node = $node->children['true'] ?? $cond_node;

        // Rarely, a conditional will always be true or always be false.
        if ($cond_truthiness !== null) {
            // TODO: Add no-op checks in another PR, if they don't already exist for conditional.
            if ($cond_truthiness === true) {
                // The condition is unconditionally true
                return UnionTypeVisitor::unionTypeFromNode(
                    $this->code_base,
                    $this->context,
                    $true_node
                );
            } else {
                // The condition is unconditionally false

                // Add the type for the 'false' side
                return UnionTypeVisitor::unionTypeFromNode(
                    $this->code_base,
                    $this->context,
                    $node->children['false'] ?? ''
                );
            }
        }
        if ($true_node !== $cond_node) {
            // Visit the condition to check for undefined variables.
            UnionTypeVisitor::unionTypeFromNode(
                $this->code_base,
                $this->context,
                $cond_node
            );
        }
        // TODO: emit no-op if $cond_node is a literal, such as `if (2)`
        // - Also note that some things such as `true` and `false` are \ast\AST_NAME nodes.

        if ($cond_node instanceof Node) {
            $base_context = $this->context;
            // TODO: Use different contexts and merge those, in case there were assignments or assignments by reference in both sides of the conditional?
            // Reuse the BranchScope (sort of unintuitive). The ConditionVisitor returns a clone and doesn't modify the original.
            $base_context_scope = $this->context->getScope();
            if ($base_context_scope instanceof GlobalScope) {
                $base_context = $base_context->withScope(new BranchScope($base_context_scope));
            }
            $true_context = (new ConditionVisitor(
                $this->code_base,
                isset($node->children['true']) ? $base_context : $this->context  // special case: $c = (($d = foo()) ?: 'fallback')
            ))($cond_node);
            $false_context = (new NegatedConditionVisitor(
                $this->code_base,
                $base_context
            ))($cond_node);

            if (!isset($node->children['true'])) {
                $true_type = UnionTypeVisitor::unionTypeFromNode(
                    $this->code_base,
                    $true_context,
                    $true_node
                );

                $false_type = UnionTypeVisitor::unionTypeFromNode(
                    $this->code_base,
                    $false_context,
                    $node->children['false'] ?? ''
                );
                $true_type_is_empty = $true_type->isEmpty();
                if (!$false_type->isEmpty()) {
                    // E.g. `foo() ?: 2` where foo is nullable or possibly false.
                    if ($true_type->containsFalsey()) {
                        $true_type = $true_type->nonFalseyClone();
                    }
                }

                // Add the type for the 'true' side to the 'false' side
                $union_type = $true_type->withUnionType($false_type);

                // If one side has an unknown type but the other doesn't
                // we can't let the unseen type get erased. Unfortunately,
                // we need to add 'mixed' in so that we know it could be
                // anything at all.
                //
                // See Issue #104
                if ($true_type_is_empty xor $false_type->isEmpty()) {
                    $union_type = $union_type->withType(
                        MixedType::instance(false)
                    );
                }

                return $union_type;
            }
        } else {
            $true_context = $this->context;
            $false_context = $this->context;
        }
        // Postcondition: This is (cond_expr) ? (true_expr) : (false_expr)

        $true_type = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $true_context,
            $true_node
        );

        $false_type = UnionTypeVisitor::unionTypeFromNode(
            $this->code_base,
            $false_context,
            $node->children['false'] ?? ''
        );

        // Add the type for the 'true' side to the 'false' side
        $union_type = $true_type->withUnionType($false_type);

        // If one side has an unknown type but the other doesn't
        // we can't let the unseen type get erased. Unfortunately,
        // we need to add 'mixed' in so that we know it could be
        // anything at all.
        //
        // See Issue #104
        if ($true_type->isEmpty() xor $false_type->isEmpty()) {
            $union_type = $union_type->withType(
                MixedType::instance(false)
            );
        }

        return $union_type;
    }

    /**
     * Visit a node with kind `\ast\AST_ARRAY`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitArray(Node $node) : UnionType
    {
        $children = $node->children;
        if (!empty($children)
            && $children[0] instanceof Node
            && $children[0]->kind == \ast\AST_ARRAY_ELEM
        ) {
            $value_types_builder = new UnionTypeBuilder();

            $key_set = $this->getEquivalentArraySet($children);
            if (\is_array($key_set) && \count($key_set) === \count($children)) {
                return $this->createArrayShapeType($children, $key_set)->asUnionType();
            }

            foreach ($children as $child) {
                $value = $child->children['value'];
                if ($value instanceof Node) {
                    $element_value_type = UnionTypeVisitor::unionTypeFromNode(
                        $this->code_base,
                        $this->context,
                        $value,
                        $this->should_catch_issue_exception
                    );
                    if ($element_value_type->isEmpty()) {
                        $value_types_builder->addType(MixedType::instance(false));
                    } else {
                        $value_types_builder->addUnionType($element_value_type);
                    }
                } else {
                    $value_types_builder->addType(Type::fromObject($value));
                }
            }
            // TODO: Normalize value_types, e.g. false+true=bool, array<int,T>+array<string,T>=array<mixed,T>
            $key_type_enum = GenericArrayType::getKeyTypeOfArrayNode($this->code_base, $this->context, $node, $this->should_catch_issue_exception);
            return $value_types_builder->getUnionType()->asNonEmptyGenericArrayTypes($key_type_enum);
        }

        // TODO: Also return types such as array<int, mixed>?
        // TODO: Fix or suppress false positives PhanTypeArraySuspicious caused by loops...
        return ArrayShapeType::empty(false)->asUnionType();
    }


    /**
     * @return ?array<int|string,true>
     * Caller should check if the result size is too small and handle it (for duplicate keys)
     * Returns null if one or more keys could not be resolved
     *
     * @see ContextNode->getEquivalentPHPArrayElements
     */
    private function getEquivalentArraySet(array $children)
    {
        $elements = [];
        $context_node = null;
        foreach ($children as $child_node) {
            $key_node = $child_node->children['key'];
            // NOTE: this has some overlap with DuplicateKeyPlugin
            if ($key_node === null) {
                $elements[] = true;
            } elseif (\is_scalar($key_node)) {
                $elements[$key_node] = true;  // Check for float?
            } else {
                if ($context_node === null) {
                    $context_node = new ContextNode($this->code_base, $this->context, null);
                }
                $key = $context_node->getEquivalentPHPValueForNode($key_node, ContextNode::RESOLVE_CONSTANTS);
                if (\is_scalar($key)) {
                    $elements[$key] = true;
                } else {
                    return null;
                }
            }
        }
        return $elements;
    }

    /**
     * @param array<int,Node> $children
     * @param array<int|string,true> $key_set
     */
    private function createArrayShapeType(array $children, array $key_set) : ArrayShapeType
    {
        \reset($key_set);
        $field_types = [];

        foreach ($children as $child) {
            $value = $child->children['value'];
            $key = \key($key_set);
            \next($key_set);

            if ($value instanceof Node) {
                $element_value_type = UnionTypeVisitor::unionTypeFromNode(
                    $this->code_base,
                    $this->context,
                    $value,
                    $this->should_catch_issue_exception
                );
                $field_types[$key] = $element_value_type->isEmpty() ? MixedType::instance(false)->asUnionType() : $element_value_type;
            } else {
                $field_types[$key] = Type::fromObject($value)->asUnionType();
            }
        }
        return ArrayShapeType::fromFieldTypes($field_types, false);
    }

    /**
     * Visit a node with kind `\ast\AST_BINARY_OP`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitBinaryOp(Node $node) : UnionType
    {
        return (new BinaryOperatorFlagVisitor(
            $this->code_base,
            $this->context
        ))->__invoke($node);
    }

    /**
     * Visit a node with kind `\ast\AST_ASSIGN_OP` (E.g. $x .= 'suffix')
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitAssignOp(Node $node) : UnionType
    {
        return (new AssignOperatorFlagVisitor(
            $this->code_base,
            $this->context
        ))($node);
    }

    /**
     * Visit a node with kind `\ast\AST_GREATER`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitGreater(Node $node) : UnionType
    {
        return $this->visitBinaryOp($node);
    }

    /**
     * Visit a node with kind `\ast\AST_GREATER_EQUAL`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitGreaterEqual(Node $node) : UnionType
    {
        return $this->visitBinaryOp($node);
    }

    /**
     * Visit a node with kind `\ast\AST_CAST`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitCast(Node $node) : UnionType
    {
        // TODO: Check if the cast is allowed based on the right side type
        UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $node->children['expr']);
        switch ($node->flags) {
            case \ast\flags\TYPE_NULL:
                return NullType::instance(false)->asUnionType();
            case \ast\flags\TYPE_BOOL:
                return BoolType::instance(false)->asUnionType();
            case \ast\flags\TYPE_LONG:
                return IntType::instance(false)->asUnionType();
            case \ast\flags\TYPE_DOUBLE:
                return FloatType::instance(false)->asUnionType();
            case \ast\flags\TYPE_STRING:
                return StringType::instance(false)->asUnionType();
            case \ast\flags\TYPE_ARRAY:
                return ArrayType::instance(false)->asUnionType();
            case \ast\flags\TYPE_OBJECT:
                return ObjectType::instance(false)->asUnionType();
            default:
                throw new NodeException(
                    $node,
                    'Unknown type (' . $node->flags . ') in cast'
                );
        }
    }

    /**
     * Visit a node with kind `\ast\AST_NEW`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitNew(Node $node) : UnionType
    {
        $union_type = $this->visitClassNode($node->children['class']);

        // TODO: re-use the underlying type set in the common case
        // Maybe UnionType::fromMap

        // For any types that are templates, map them to concrete
        // types based on the parameters passed in.
        return UnionType::of(\array_map(function (Type $type) use ($node) {

            // Get a fully qualified name for the type
            $fqsen = $type->asFQSEN();

            // If this isn't a class, its fine as is
            if (!($fqsen instanceof FullyQualifiedClassName)) {
                return $type;
            }

            // If we don't have the class, we'll catch that problem
            // elsewhere
            if (!$this->code_base->hasClassWithFQSEN($fqsen)) {
                return $type;
            }


            $class = $this->code_base->getClassByFQSEN($fqsen);

            // If this class doesn't have any generics on it, we're
            // fine as we are with this Type
            if (!$class->isGeneric()) {
                return $type;
            }

            // Now things are interesting. We need to map the
            // arguments to the generic types and return a special
            // kind of type.

            // Get the constructor so that we can figure out what
            // template types we're going to be mapping
            $constructor_method =
                $class->getMethodByName($this->code_base, '__construct');

            // Map each argument to its type
            $arg_type_list = \array_map(function ($arg_node) {
                return UnionTypeVisitor::unionTypeFromNode(
                    $this->code_base,
                    $this->context,
                    $arg_node
                );
            }, $node->children['args']->children);

            // Map each template type o the argument's concrete type
            $template_type_list = [];
            foreach ($constructor_method->getParameterList() as $i => $unused_parameter) {
                if (isset($arg_type_list[$i])) {
                    $template_type_list[] = $arg_type_list[$i];
                }
            }

            // Create a new type that assigns concrete
            // types to template type identifiers.
            return Type::fromType($type, $template_type_list);
        }, $union_type->getTypeSet()));
    }

    /**
     * Visit a node with kind `\ast\AST_INSTANCEOF`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitInstanceOf(Node $node) : UnionType
    {
        $code_base = $this->code_base;
        $context = $this->context;
        // Check to make sure the left side is valid
        UnionTypeVisitor::unionTypeFromNode($code_base, $context, $node->children['expr']);
        try {
            // Get the type that we're checking it against, check if it is valid.
            $class_node = $node->children['class'];
            $type = UnionTypeVisitor::unionTypeFromNode(
                $code_base,
                $context,
                $class_node
            );
            // TODO: Unify UnionTypeVisitor, AssignmentVisitor, and PostOrderAnalysisVisitor
            if (!$type->isEmpty() && !$type->hasObjectTypes()) {
                if ($class_node->kind !== \ast\AST_NAME &&
                        !$type->canCastToUnionType(StringType::instance(false)->asUnionType())) {
                    Issue::maybeEmit(
                        $code_base,
                        $context,
                        Issue::TypeInvalidInstanceof,
                        $context->getLineNumberStart(),
                        (string)$type
                    );
                }
            }
        } catch (TypeException $exception) {
            // TODO: log it?
        }

        return BoolType::instance(false)->asUnionType();
    }

    /** @internal - Duplicated for performance. Use PhanAnnotationAdder instead */
    const FLAG_IGNORE_NULLABLE = 1 << 29;

    /**
     * Visit a node with kind `\ast\AST_DIM`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitDim(Node $node) : UnionType
    {
        $union_type = self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr'],
            $this->should_catch_issue_exception
        );

        if ($union_type->isEmpty()) {
            return $union_type;
        }

        // If none of the types we found were arrays with elements,
        // then check for ArrayAccess
        static $array_access_type;
        static $simple_xml_element_type;  // SimpleXMLElement doesn't `implement` ArrayAccess, but can be accessed that way. See #542
        static $null_type;
        static $string_type;
        static $int_union_type;
        static $int_or_string_union_type;
        if ($array_access_type === null) {
            // array offsets work on strings, unfortunately
            // Double check that any classes in the type don't
            // have ArrayAccess
            $array_access_type =
                Type::fromNamespaceAndName('\\', 'ArrayAccess', false);
            $simple_xml_element_type =
                Type::fromNamespaceAndName('\\', 'SimpleXMLElement', false);
            $null_type = NullType::instance(false);
            $string_type = StringType::instance(false);
            $int_union_type = IntType::instance(false)->asUnionType();
            $int_or_string_union_type = new UnionType([IntType::instance(false), StringType::instance(false)], true);
        }
        if ($union_type->hasTopLevelArrayShapeTypeInstances()) {
            $element_type = $this->resolveArrayShapeElementTypes($node, $union_type);
            if ($element_type !== null) {
                return $element_type;
            }
        }
        $dim_type = self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['dim'],
            true
        );

        // Figure out what the types of accessed array
        // elements would be.
        $generic_types =
            $union_type->genericArrayElementTypes();

        // If we have generics, we're all set
        if (!$generic_types->isEmpty()) {
            if ($this->isSuspiciousNullable($union_type) && !($node->flags & self::FLAG_IGNORE_NULLABLE)) {
                $this->emitIssue(
                    Issue::TypeArraySuspiciousNullable,
                    $node->lineno ?? 0,
                    (string)$union_type
                );
            }

            if (!$dim_type->isEmpty()) {
                if (!$union_type->asExpandedTypes($this->code_base)->hasArrayAccess() && !$union_type->hasMixedType()) {
                    if (Config::getValue('scalar_array_key_cast')) {
                        $expected_key_type = $int_or_string_union_type;
                    } else {
                        $expected_key_type = GenericArrayType::unionTypeForKeyType(
                            GenericArrayType::keyTypeFromUnionTypeKeys($union_type),
                            GenericArrayType::CONVERT_KEY_MIXED_TO_INT_OR_STRING_UNION_TYPE
                        );
                    }
                    if (!$dim_type->canCastToUnionType($expected_key_type)) {
                        $issue_type = Issue::TypeMismatchDimFetch;

                        if ($dim_type->containsNullable()) {
                            if ($dim_type->nonNullableClone()->canCastToUnionType($expected_key_type)) {
                                $issue_type = Issue::TypeMismatchDimFetchNullable;
                            }
                        }
                        if ($this->should_catch_issue_exception) {
                            $this->emitIssue(
                                $issue_type,
                                $node->lineno ?? 0,
                                (string)$union_type,
                                (string)$dim_type,
                                (string)$expected_key_type
                            );
                            return $generic_types;
                        }
                        throw new IssueException(
                            Issue::fromType($issue_type)(
                                $this->context->getFile(),
                                $node->lineno ?? 0,
                                [(string)$union_type, (string)$dim_type, (string)$expected_key_type]
                            )
                        );
                    }
                }
            }
            return $generic_types;
        }

        // If the only type is null, we don't know what
        // accessed items will be
        if ($union_type->isType($null_type)) {
            return UnionType::empty();
        }

        $element_types = UnionType::empty();

        // You can access string characters via array index,
        // so we'll add the string type to the result if we're
        // indexing something that could be a string
        if ($union_type->isType($string_type)
            || $union_type->canCastToUnionType($string_type->asUnionType())
        ) {
            if (!$dim_type->isEmpty() && !$dim_type->canCastToUnionType($int_union_type)) {
                // TODO: Efficient implementation of asExpandedTypes()->hasArrayAccess()?
                if (!$union_type->isEmpty() && !$union_type->asExpandedTypes($this->code_base)->hasArrayLike()) {
                    $this->emitIssue(
                        Issue::TypeMismatchDimFetch,
                        $node->lineno ?? 0,
                        $union_type,
                        (string)$dim_type,
                        $int_union_type
                    );
                }
            }
            $element_types = $element_types->withType($string_type);
        }

        if ($element_types->isEmpty()) {
            // Hunt for any types that are viable class names and
            // see if they inherit from ArrayAccess
            try {
                foreach ($union_type->asClassList($this->code_base, $this->context) as $class) {
                    $expanded_types = $class->getUnionType()->asExpandedTypes($this->code_base);
                    if ($expanded_types->hasType($array_access_type) ||
                            $expanded_types->hasType($simple_xml_element_type)) {
                        return $element_types;
                    }
                }
            } catch (CodeBaseException $exception) {
            }

            if (!$union_type->hasArrayLike()) {
                $this->emitIssue(
                    Issue::TypeArraySuspicious,
                    $node->lineno ?? 0,
                    (string)$union_type
                );
            }
        }

        return $element_types;
    }

    private function isSuspiciousNullable(UnionType $union_type) : bool
    {
        foreach ($union_type->getTypeSet() as $type) {
            if ($type->getIsNullable() && ($type instanceof ArrayType || $type instanceof StringType)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return ?UnionType
     */
    private function resolveArrayShapeElementTypes(Node $node, UnionType $union_type)
    {
        $dim_node = $node->children['dim'];
        $dim_value = $dim_node instanceof Node ? (new ContextNode($this->code_base, $this->context, $dim_node))->getEquivalentPHPScalarValue() : $dim_node;
        // TODO: detect and warn about null
        if (!\is_scalar($dim_value)) {
            return null;
        }

        $resulting_element_type = self::resolveArrayShapeElementTypesForOffset($union_type, $dim_value);

        if ($resulting_element_type === null) {
            return null;
        }
        if ($resulting_element_type === false) {
            $this->emitIssue(
                Issue::TypeInvalidDimOffset,
                $dim_node->lineno ?? $node->lineno ?? 0,
                json_encode($dim_value),
                (string)$union_type
            );
            return null;
        }
        return $resulting_element_type;
    }

    /**
     * @param UnionType $union_type a union type with at least one top level array shape type
     * @param int|string|float|bool $dim_value a scalar dimension. TODO: Warn about null?
     * @return ?UnionType|?false
     *  returns false if there the offset was invalid and there are no ways to get that offset
     *  returns null if the dim_value offset could not be found, but there were other generic array types
     */
    public static function resolveArrayShapeElementTypesForOffset(UnionType $union_type, $dim_value)
    {
        $has_non_array_shape_type = false;
        $resulting_element_type = null;
        foreach ($union_type->getTypeSet() as $type) {
            if (!($type instanceof ArrayShapeType)) {
                if ($type instanceof StringType) {
                    if (\is_int($dim_value)) {
                        // If we request a string offset from a string, that's not valid. Only accept integer dimensions as valid.
                        $has_non_array_shape_type = true;
                        // in php, indices of strings can be negative
                        if ($resulting_element_type !== null) {
                            $resulting_element_type = $resulting_element_type->withType(StringType::instance(false));
                        } else {
                            $resulting_element_type = StringType::instance(false)->asUnionType();
                        }
                    } // TODO: Warn about string indices of strings?
                } elseif ($type->isArrayLike() || $type instanceof MixedType) {
                    // TODO: Be more precise for objects implementing ArrayAccess?
                    $has_non_array_shape_type = true;
                }
                continue;
            }
            $element_type = $type->getFieldTypes()[$dim_value] ?? null;
            if ($element_type !== null) {
                // $element_type may be non-null but $element_type->isEmpty() may be true.
                // So, we use null to indicate failure below
                if ($resulting_element_type !== null) {
                    $resulting_element_type = $resulting_element_type->withUnionType($element_type);
                } else {
                    $resulting_element_type = $element_type;
                }
            }
        }
        if ($resulting_element_type === null) {
            if (!$has_non_array_shape_type) {
                return false;
            }
            return null;
        }
        return $resulting_element_type;
    }

    /**
     * Visit a node with kind `\ast\AST_DIM`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitUnpack(Node $node) : UnionType
    {
        $union_type = self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr'],
            $this->should_catch_issue_exception
        );

        if ($union_type->isEmpty()) {
            return $union_type;
        }

        // Figure out what the types of accessed array
        // elements would be
        // TODO: Account for Traversable once there are generics for Traversable
        $generic_types =
            $union_type->genericArrayElementTypes();

        // If we have generics, we're all set
        if ($generic_types->isEmpty()) {
            if (!$union_type->asExpandedTypes($this->code_base)->hasIterable() && !$union_type->hasType(MixedType::instance(false))) {
                throw new IssueException(
                    Issue::fromType(Issue::TypeMismatchUnpackValue)(
                        $this->context->getFile(),
                        $node->lineno ?? 0,
                        [(string)$union_type]
                    )
                );
            }
            return $generic_types;
        }
        // TODO: Once we have generic template types for Traversable and subclasses, rewrite this check to account for `new ArrayObject([2])`, etc.
        if (GenericArrayType::KEY_STRING === GenericArrayType::keyTypeFromUnionTypeKeys($union_type)) {
            throw new IssueException(
                Issue::fromType(Issue::TypeMismatchUnpackKey)(
                    $this->context->getFile(),
                    $node->lineno ?? 0,
                    [(string)$union_type, 'string']
                )
            );
        }
        return $generic_types;
    }

    /**
     * Visit a node with kind `\ast\AST_CLOSURE`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitClosure(Node $node) : UnionType
    {
        // The type of a closure is the fqsen pointing
        // at its definition
        $closure_fqsen =
            FullyQualifiedFunctionName::fromClosureInContext(
                $this->context,
                $node
            );

        if ($this->code_base->hasFunctionWithFQSEN($closure_fqsen)) {
            $func = $this->code_base->getFunctionByFQSEN($closure_fqsen);
        } else {
            $func = null;
        }

        return ClosureType::instanceWithClosureFQSEN(
            $closure_fqsen,
            $func
        )->asUnionType();
    }

    /**
     * Visit a node with kind `\ast\AST_VAR`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitVar(Node $node) : UnionType
    {
        // $$var or ${...} (whose idea was that anyway?)
        $name_node = $node->children['name'];
        if (($name_node instanceof Node)) {
            // This is nonsense. Give up.
            $name_node_type = $this($name_node);
            static $int_or_string_type;
            if ($int_or_string_type === null) {
                $int_or_string_type = new UnionType([
                    StringType::instance(false),
                    IntType::instance(false),
                    NullType::instance(false)
                ]);
            }
            if (!$name_node_type->canCastToUnionType($int_or_string_type)) {
                Issue::maybeEmit($this->code_base, $this->context, Issue::TypeSuspiciousIndirectVariable, $name_node->lineno ?? 0, (string)$name_node_type);
            }

            return MixedType::instance(false)->asUnionType();
        }

        // foo(${42}) is technically valid PHP code, avoid TypeError
        $variable_name =
            (string)$name_node;

        if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
            if (Variable::isHardcodedVariableInScopeWithName($variable_name, $this->context->isInGlobalScope())) {
                return Variable::getUnionTypeOfHardcodedGlobalVariableWithName($variable_name);
            }
            if (!Config::getValue('ignore_undeclared_variables_in_global_scope')
                || !$this->context->isInGlobalScope()
            ) {
                throw new IssueException(
                    Issue::fromType(Issue::UndeclaredVariable)(
                        $this->context->getFile(),
                        $node->lineno ?? 0,
                        [$variable_name]
                    )
                );
            }
        } else {
            $variable = $this->context->getScope()->getVariableByName(
                $variable_name
            );

            return $variable->getUnionType();
        }

        return UnionType::empty();
    }

    /**
     * Visit a node with kind `\ast\AST_ENCAPS_LIST`
     *
     * @param Node $node (@phan-unused-param)
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitEncapsList(Node $node) : UnionType
    {
        return StringType::instance(false)->asUnionType();
    }

    /**
     * Visit a node with kind `\ast\AST_CONST`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitConst(Node $node) : UnionType
    {
        if ($node->children['name']->kind == \ast\AST_NAME) {
            $name = $node->children['name']->children['name'];
            if (defined($name)) {
                // This constant is internal to php
                $result = Type::fromReservedConstantName($name);
                if ($result->isDefined()) {
                    // And it's a reserved keyword such as false, null, E_ALL, etc.
                    return $result->get()->asUnionType();
                }
                // TODO: use the CodeBase for all internal constants.
                // defined() doesn't account for use statements in the codebase (`use ... as aliased_name`)
                // TODO: The below code will act as though some constants from Phan exist in other codebases (e.g. EXIT_STATUS).
                return Type::fromObject(
                    constant($name)
                )->asUnionType();
            }

            // Figure out the name of the constant if it's
            // a string.
            $constant_name = $name ?? '';

            // If the constant is referring to the current
            // class, return that as a type
            if (Type::isSelfTypeString($constant_name) || Type::isStaticTypeString($constant_name)) {
                return $this->visitClassNode($node);
            }

            try {
                $constant = (new ContextNode(
                    $this->code_base,
                    $this->context,
                    $node
                ))->getConst();
            } catch (IssueException $exception) {
                Issue::maybeEmitInstance(
                    $this->code_base,
                    $this->context,
                    $exception->getIssueInstance()
                );
                return UnionType::empty();
            }

            return $constant->getUnionType();
        }

        return UnionType::empty();
    }

    /**
     * Visit a node with kind `\ast\AST_CLASS_CONST`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     *
     * @throws IssueException
     * An exception is thrown if we can't find the constant
     */
    public function visitClassConst(Node $node) : UnionType
    {
        try {
            $constant = (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ))->getClassConst();

            return $constant->getUnionType();
        } catch (NodeException $exception) {
            $this->emitIssue(
                Issue::Unanalyzable,
                $node->lineno ?? 0
            );
        }

        return UnionType::empty();
    }

    /**
     * Visit a node with kind `\ast\AST_PROP`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitProp(Node $node) : UnionType
    {
        return $this->analyzeProp($node, false);
    }

    /**
     * Analyzes a node with kind `\ast\AST_PROP` or `\ast\AST_STATIC_PROP`
     *
     * @param Node $node
     * The instance/static property access node.
     *
     * @param bool $is_static
     * True if this is a static property fetch,
     * false if this is an instance property fetch.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    private function analyzeProp(Node $node, bool $is_static) : UnionType
    {
        try {
            $property = (new ContextNode(
                $this->code_base,
                $this->context,
                $node
            ))->getProperty($node->children['prop'], $is_static);

            // Map template types to concrete types
            if ($property->getUnionType()->hasTemplateType()) {
                // Get the type of the object calling the property
                $expression_type = UnionTypeVisitor::unionTypeFromNode(
                    $this->code_base,
                    $this->context,
                    $node->children['expr']
                );

                $union_type = $property->getUnionType()->withTemplateParameterTypeMap(
                    $expression_type->getTemplateParameterTypeMap($this->code_base)
                );

                return $union_type;
            }

            return $property->getUnionType();
        } catch (IssueException $exception) {
            Issue::maybeEmitInstance(
                $this->code_base,
                $this->context,
                $exception->getIssueInstance()
            );
        } catch (CodeBaseException $exception) {
            $property_name = $node->children['prop'];
            $this->emitIssue(
                Issue::UndeclaredProperty,
                $node->lineno ?? 0,
                "{$exception->getFQSEN()}->{$property_name}"
            );
        } catch (UnanalyzableException $exception) {
            // Swallow it. There are some constructs that we
            // just can't figure out.
        } catch (NodeException $exception) {
            // Swallow it. There are some constructs that we
            // just can't figure out.
        }

        return UnionType::empty();
    }

    /**
     * Visit a node with kind `\ast\AST_STATIC_PROP`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitStaticProp(Node $node) : UnionType
    {
        return $this->analyzeProp($node, true);
    }


    /**
     * Visit a node with kind `\ast\AST_CALL`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitCall(Node $node) : UnionType
    {
        $expression = $node->children['expr'];
        $function_list_generator = (new ContextNode(
            $this->code_base,
            $this->context,
            $expression
        ))->getFunctionFromNode();

        $possible_types = UnionType::empty();
        foreach ($function_list_generator as $function) {
            assert($function instanceof FunctionInterface);
            if ($function->hasDependentReturnType()) {
                $function_types = $function->getDependentReturnType($this->code_base, $this->context, $node->children['args']->children);
            } else {
                $function_types = $function->getUnionType();
            }
            $possible_types = $possible_types->withUnionType($function_types);
        }

        return $possible_types;
    }

    /**
     * Visit a node with kind `\ast\AST_STATIC_CALL`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitStaticCall(Node $node) : UnionType
    {
        return $this->visitMethodCall($node);
    }

    /**
     * Visit a node with kind `\ast\AST_METHOD_CALL`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitMethodCall(Node $node) : UnionType
    {
        $method_name = $node->children['method'] ?? '';

        // Give up on any complicated nonsense where the
        // method name is a variable such as in
        // `$variable->$function_name()`.
        if ($method_name instanceof Node) {
            return UnionType::empty();
        }

        // Method names can some times turn up being
        // other method calls.
        \assert(
            \is_string($method_name),
            "Method name must be a string. Something else given."
        );

        try {
            foreach ($this->classListFromNode(
                $node->children['class'] ?? $node->children['expr']
            ) as $class) {
                if (!$class->hasMethodWithName(
                    $this->code_base,
                    $method_name
                )) {
                    continue;
                }

                try {
                    $method = $class->getMethodByName(
                        $this->code_base,
                        $method_name
                    );

                    if ($method->hasDependentReturnType()) {
                        $union_type = $method->getDependentReturnType($this->code_base, $this->context, $node->children['args']->children);
                    } else {
                        $union_type = $method->getUnionType();
                    }

                    // Map template types to concrete types
                    if ($union_type->hasTemplateType()) {
                        // Get the type of the object calling the property
                        $expression_type = UnionTypeVisitor::unionTypeFromNode(
                            $this->code_base,
                            $this->context,
                            $node->children['expr']
                        );

                        // Map template types to concrete types
                        $union_type = $union_type->withTemplateParameterTypeMap(
                            $expression_type->getTemplateParameterTypeMap($this->code_base)
                        );
                    }

                    // Remove any references to \static or \static[]
                    // once we're talking about the method's return
                    // type outside of its class
                    if ($union_type->hasStaticType()) {
                        $union_type = $union_type->withoutType(\Phan\Language\Type\StaticType::instance(false));
                    }

                    if ($union_type->genericArrayElementTypes()->hasStaticType()) {
                        // Find the static type on the list
                        $static_type = $union_type->findTypeMatchingCallback(function (Type $type) : bool {
                            return (
                                $type->isGenericArray()
                                && $type->genericArrayElementUnionType()->hasStaticType()
                            );
                        });

                        // Remove it from the list
                        // TODO: Limit this to fields of ArrayShapeType that actually have static type
                        $union_type = $union_type->withoutType($static_type);
                    }

                    return $union_type;
                } catch (IssueException $exception) {
                    return UnionType::empty();
                }
            }
        } catch (IssueException $exception) {
            // Swallow it
        } catch (CodeBaseException $exception) {
            $this->emitIssue(
                Issue::UndeclaredClassMethod,
                $node->lineno ?? 0,
                $method_name,
                (string)$exception->getFQSEN()
            );
        }

        return UnionType::empty();
    }

    /**
     * Visit a node with kind `\ast\AST_ASSIGN`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitAssign(Node $node) : UnionType
    {
        return self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr']
        );
    }

    /**
     * Visit a node with kind `\ast\AST_UNARY_OP`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitUnaryOp(Node $node) : UnionType
    {
        // Shortcut some easy operators
        switch ($node->flags) {
            case \ast\flags\UNARY_BOOL_NOT:
                return BoolType::instance(false)->asUnionType();
        }

        return self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node->children['expr']
        );
    }

    /**
     * Visit a node with kind `\ast\AST_UNARY_MINUS`
     *
     * @param Node $node
     * A node of the type indicated by the method name that we'd
     * like to figure out the type that it produces.
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     */
    public function visitUnaryMinus(Node $node) : UnionType
    {
        return Type::fromObject($node->children['expr'])->asUnionType();
    }

    /**
     * `print($str)` always returns 1.
     * See https://secure.php.net/manual/en/function.print.php#refsect1-function.print-returnvalues
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function visitPrint(Node $node) : UnionType
    {
        return IntType::instance(false)->asUnionType();
    }

    /*
     * @param Node $node
     * A node holding a class
     *
     * @return UnionType
     * The set of types that are possibly produced by the
     * given node
     *
     * @throws IssueException
     * An exception is thrown if we can't find a class for
     * the given type
     */
    private function visitClassNode(Node $node) : UnionType
    {
        // Things of the form `new $class_name();`
        if ($node->kind == \ast\AST_VAR) {
            return UnionType::empty();
        }

        // Anonymous class of form `new class { ... }`
        if ($node->kind == \ast\AST_CLASS
            && $node->flags & \ast\flags\CLASS_ANONYMOUS
        ) {
            // Generate a stable name for the anonymous class
            $anonymous_class_name =
                (new ContextNode(
                    $this->code_base,
                    $this->context,
                    $node
                ))->getUnqualifiedNameForAnonymousClass();

            // Turn that into a fully qualified name
            $fqsen = FullyQualifiedClassName::fromStringInContext(
                $anonymous_class_name,
                $this->context
            );

            // Turn that into a union type
            return Type::fromFullyQualifiedString((string)$fqsen)->asUnionType();
        }

        // Things of the form `new $method->name()`
        if ($node->kind !== \ast\AST_NAME) {
            return UnionType::empty();
        }

        // Get the name of the class
        $class_name = $node->children['name'];

        // If this is a straight-forward class name, recurse into the
        // class node and get its type
        $is_static_type_string = Type::isStaticTypeString($class_name);
        if (!($is_static_type_string || Type::isSelfTypeString($class_name))) {
            return self::unionTypeFromClassNode(
                $this->code_base,
                $this->context,
                $node
            );
        }

        // This is a self-referential node
        if (!$this->context->isInClassScope()) {
            $this->emitIssue(
                Issue::ContextNotObject,
                $node->lineno ?? 0,
                $class_name
            );

            return UnionType::empty();
        }

        // Reference to a parent class
        if ($class_name === 'parent') {
            $class = $this->context->getClassInScope(
                $this->code_base
            );

            if (!$class->hasParentType()) {
                $this->emitIssue(
                    Issue::ParentlessClass,
                    $node->lineno ?? 0,
                    (string)$class->getFQSEN()
                );

                return UnionType::empty();
            }

            return Type::fromFullyQualifiedString(
                (string)$class->getParentClassFQSEN()
            )->asUnionType();
        }

        // TODO: UnionType::fromFullyQualifiedString()
        $result = Type::fromFullyQualifiedString(
            (string)$this->context->getClassFQSEN()
        )->asUnionType();

        if ($is_static_type_string) {
            $result = $result->withType(StaticType::instance(false));
        }
        return $result;
    }

    /**
     * @param CodeBase $code_base
     * The code base within which we're operating
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     *
     * @param Node|mixed $node
     * The node for which we'd like to determine its type
     *
     * @return UnionType
     * The UnionType associated with the given node
     * in the given Context within the given CodeBase
     *
     * @throws IssueException
     * An exception is thrown if we can't find a class for
     * the given type
     */
    public static function unionTypeFromClassNode(
        CodeBase $code_base,
        Context $context,
        $node
    ) : UnionType {

        // If this is a list, build a union type by
        // recursively visiting the child nodes
        if ($node instanceof Node
            && $node->kind == \ast\AST_NAME_LIST
        ) {
            $union_type = UnionType::empty();
            foreach ($node->children as $child_node) {
                $union_type = $union_type->withUnionType(
                    self::unionTypeFromClassNode(
                        $code_base,
                        $context,
                        $child_node
                    )
                );
            }
            return $union_type;
        }

        // For simple nodes or very complicated nodes,
        // recurse
        if (!($node instanceof \ast\Node)
            || $node->kind != \ast\AST_NAME
        ) {
            return self::unionTypeFromNode(
                $code_base,
                $context,
                $node
            );
        }

        $class_name = (string)$node->children['name'];

        if ('parent' === $class_name) {
            if (!$context->isInClassScope()) {
                throw new IssueException(
                    Issue::fromType(Issue::ContextNotObject)(
                        $context->getFile(),
                        $node->lineno ?? 0,
                        [$class_name]
                    )
                );
            }

            $class = $context->getClassInScope($code_base);

            if ($class->isTrait()) {
                throw new IssueException(
                    Issue::fromType(Issue::TraitParentReference)(
                        $context->getFile(),
                        $node->lineno ?? 0,
                        [(string)$context->getClassFQSEN() ]
                    )
                );
            }

            if (!$class->hasParentType()) {
                throw new IssueException(
                    Issue::fromType(Issue::ParentlessClass)(
                        $context->getFile(),
                        $node->lineno ?? 0,
                        [ (string)$context->getClassFQSEN() ]
                    )
                );
            }

            $parent_class_fqsen = $class->getParentClassFQSEN();

            if (!$code_base->hasClassWithFQSEN($parent_class_fqsen)) {
                throw new IssueException(
                    Issue::fromType(Issue::UndeclaredClass)(
                        $context->getFile(),
                        $node->lineno ?? 0,
                        [ (string)$parent_class_fqsen ]
                    )
                );
            } else {
                $parent_class = $code_base->getClassByFQSEN(
                    $parent_class_fqsen
                );

                return $parent_class->getUnionType();
            }
        }

        // We're going to convert the class reference to a type
        $type = null;

        // Check to see if the name is fully qualified
        if ($node->flags & \ast\flags\NAME_NOT_FQ) {
            $type = Type::fromStringInContext(
                $class_name,
                $context,
                Type::FROM_NODE
            );
        } elseif ($node->flags & \ast\flags\NAME_RELATIVE) {
            // Relative to current namespace
            if (0 !== strpos($class_name, '\\')) {
                $class_name = '\\' . $class_name;
            }

            $type = Type::fromFullyQualifiedString(
                $context->getNamespace() . $class_name
            );
        } else {
            // Fully qualified
            if (0 !== strpos($class_name, '\\')) {
                $class_name = '\\' . $class_name;
            }

            $type = Type::fromFullyQualifiedString(
                $class_name
            );
        }

        return $type->asUnionType();
    }

    /**
     * @return \Generator|Clazz[]
     */
    public static function classListFromNodeAndContext(CodeBase $code_base, Context $context, Node $node)
    {
        return (new UnionTypeVisitor($code_base, $context, true))->classListFromNode($node);
    }

    /**
     * @return \Generator|Clazz[]
     * A list of classes associated with the given node
     *
     * @throws IssueException
     * An exception is thrown if we can't find a class for
     * the given type
     */
    private function classListFromNode(Node $node)
    {
        // Get the types associated with the node
        $union_type = self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node
        );

        // Iterate over each viable class type to see if any
        // have the constant we're looking for
        foreach ($union_type->nonNativeTypes()->getTypeSet() as $class_type) {
            // Get the class FQSEN
            $class_fqsen = FullyQualifiedClassName::fromType($class_type);

            // See if the class exists
            if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
                throw new IssueException(
                    Issue::fromType(Issue::UndeclaredClassReference)(
                        $this->context->getFile(),
                        $node->lineno ?? 0,
                        [ (string)$class_fqsen ]
                    )
                );
            }

            yield $this->code_base->getClassByFQSEN($class_fqsen);
        }
    }

    /**
     * @param CodeBase $code_base
     * @param Context $context
     * @param string|Node $node the node to fetch CallableType instances for.
     * @param bool $log_error whether or not to log errors while searching
     * @return array<int,FunctionInterface>
     * @suppress PhanPluginUnusedPublicMethodArgument (TODO: Implement $log_error)
     */
    public static function functionLikeListFromNodeAndContext(CodeBase $code_base, Context $context, $node, bool $log_error) : array
    {
        try {
            $function_fqsens = (new UnionTypeVisitor($code_base, $context, true))->functionLikeFQSENListFromNode($node);
        } catch (EmptyFQSENException $e) {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::EmptyFQSENInCallable,
                $context->getLineNumberStart(),
                $e->getFQSEN()
            );
            return [];
        }
        $functions = [];
        foreach ($function_fqsens as $fqsen) {
            if ($fqsen instanceof FullyQualifiedMethodName) {
                if (!$code_base->hasMethodWithFQSEN($fqsen)) {
                    // TODO: error PhanArrayMapClosure
                    continue;
                }
                $functions[] = $code_base->getMethodByFQSEN($fqsen);
            } else {
                assert($fqsen instanceof FullyQualifiedFunctionName);
                if (!$code_base->hasFunctionWithFQSEN($fqsen)) {
                    // TODO: error PhanArrayMapClosure
                    continue;
                }
                $functions[] = $code_base->getFunctionByFQSEN($fqsen);
            }
        }
        return $functions;
    }

    /**
     * @param CodeBase $code_base
     * @param Context $context
     * @param string|Node $node the node to fetch CallableType instances for.
     * @return array<int,FullyQualifiedFunctionLikeName>
     * @suppress PhanUnreferencedPublicMethod may be used in the future.
     */
    public static function functionLikeFQSENListFromNodeAndContext(CodeBase $code_base, Context $context, $node) : array
    {
        return (new UnionTypeVisitor($code_base, $context, true))->functionLikeFQSENListFromNode($node);
    }

    /**
     * @param string|Node $class_or_expr
     * @param string $method_name
     *
     * @return array<int,FullyQualifiedMethodName>
     * A list of CallableTypes associated with the given node
     */
    private function methodFQSENListFromObjectAndMethodName($class_or_expr, $method_name) : array
    {
        $code_base = $this->code_base;
        $context = $this->context;

        $union_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $class_or_expr);
        if ($union_type->isEmpty()) {
            return [];
        }
        $object_types = $union_type->objectTypes();
        if ($object_types->isEmpty()) {
            if (!$union_type->canCastToUnionType(StringType::instance(false)->asUnionType())) {
                $this->emitIssue(
                    Issue::TypeInvalidCallableObjectOfMethod,
                    $context->getLineNumberStart(),
                    (string)$union_type,
                    $method_name
                );
            }
            return [];
        }
        $result_types = [];
        $class = null;
        foreach ($object_types->getTypeSet() as $object_type) {
            // TODO: support templates here.
            if ($object_type instanceof ObjectType || $object_type instanceof TemplateType) {
                continue;
            }
            $class_fqsen = $object_type->asFQSEN();
            if (!($class_fqsen instanceof FullyQualifiedClassName)) {
                continue;
            }
            if ($object_type->isStaticType()) {
                if (!$context->isInClassScope()) {
                    $this->emitIssue(
                        Issue::ContextNotObjectInCallable,
                        $context->getLineNumberStart(),
                        (string)$class_fqsen,
                        "$class_fqsen::$method_name"
                    );
                    continue;
                }
                $class_fqsen = $context->getClassFQSEN();
            }
            if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
                $this->emitIssue(
                    Issue::UndeclaredClassInCallable,
                    $context->getLineNumberStart(),
                    (string)$class_fqsen,
                    "$class_fqsen::$method_name"
                );
                continue;
            }
            $class = $code_base->getClassByFQSEN($class_fqsen);
            if (!$class->hasMethodWithName($code_base, $method_name)) {
                // emit error below
                continue;
            }
            $method_fqsen = FullyQualifiedMethodName::make(
                $class_fqsen,
                $method_name
            );
            $result_types[] = $method_fqsen;
        }
        if (\count($result_types) === 0 && $class instanceof Clazz) {
            $this->emitIssue(
                Issue::UndeclaredMethodInCallable,
                $context->getLineNumberStart(),
                $method_name,
                (string)$union_type
            );
        }
        return $result_types;
    }

    /**
     * @param string $class_name (may also be 'self', 'parent', or 'static')
     * @return ?FullyQualifiedClassName
     */
    private function lookupClassOfCallableByName(string $class_name)
    {
        switch (\strtolower($class_name)) {
            case 'self':
            case 'static':
                $context = $this->context;
                if (!$context->isInClassScope()) {
                    $this->emitIssue(
                        Issue::ContextNotObject,
                        $context->getLineNumberStart(),
                        \strtolower($class_name)
                    );
                    return null;
                }
                return $context->getClassFQSEN();
            case 'parent':
                $context = $this->context;
                if (!$context->isInClassScope()) {
                    $this->emitIssue(
                        Issue::ContextNotObject,
                        $context->getLineNumberStart(),
                        \strtolower($class_name)
                    );
                    return null;
                }
                $class = $context->getClassInScope($this->code_base);
                if ($class->isTrait()) {
                    $this->emitIssue(
                        Issue::TraitParentReference,
                        $context->getLineNumberStart(),
                        (string)$class->getFQSEN()
                    );
                    return null;
                }
                if (!$class->hasParentType()) {
                    $this->emitIssue(
                        Issue::ParentlessClass,
                        $context->getLineNumberStart(),
                        (string)$class->getFQSEN()
                    );
                    return null;
                }
                return $class->getParentClassFQSEN();  // may or may not exist.
            default:
                // TODO: Reject invalid/empty class names earlier
                return FullyQualifiedClassName::makeFromExtractedNamespaceAndName($class_name);
        }
    }

    /**
     * @param string $class_name
     * @param string $method_name
     * @return void
     */
    private function emitNonObjectContextInCallableIssue(string $class_name, string $method_name)
    {
        $this->emitIssue(
            Issue::ContextNotObjectInCallable,
            $this->context->getLineNumberStart(),
            $class_name,
            "$class_name::$method_name"
        );
    }

    /**
     * @param string|Node $class_or_expr
     * @param string $method_name
     *
     * @return array<int,FullyQualifiedMethodName>
     * A list of CallableTypes associated with the given node
     */
    private function methodFQSENListFromParts($class_or_expr, $method_name) : array
    {
        $code_base = $this->code_base;
        $context = $this->context;

        if (!\is_string($method_name)) {
            if (!($method_name instanceof Node)) {
                // TODO: Warn about int/float here
                return [];
            }
            $method_name = (new ContextNode($code_base, $context, $method_name))->getEquivalentPHPScalarValue();
            if (!\is_string($method_name)) {
                // TODO: Check if union type is sane, e.g. callable ['MyClass', new stdClass()] is nonsense.
                return [];
            }
        }
        if (is_string($class_or_expr)) {
            if (\in_array(\strtolower($class_or_expr), ['static', 'self', 'parent'], true)) {
                // Allow 'static' but not '\static'
                if (!$context->isInClassScope()) {
                    $this->emitNonObjectContextInCallableIssue($class_or_expr, $method_name);
                    return [];
                }
                $class_fqsen = $context->getClassFQSEN();
            } else {
                $class_fqsen = $this->lookupClassOfCallableByName($class_or_expr);
                if (!$class_fqsen) {
                    return [];
                }
            }
        } else {
            $class_fqsen = (new ContextNode($code_base, $context, $class_or_expr))->resolveClassNameInContext();
            if (!$class_fqsen) {
                return $this->methodFQSENListFromObjectAndMethodName($class_or_expr, $method_name);
            }
            if (\in_array(\strtolower($class_fqsen->getName()), ['static', 'self', 'parent'], true)) {
                if (!$context->isInClassScope()) {
                    $this->emitNonObjectContextInCallableIssue((string)$class_fqsen, $method_name);
                    return [];
                }
                $class_fqsen = $context->getClassFQSEN();
            }
        }
        if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
            $this->emitIssue(
                Issue::UndeclaredClassInCallable,
                $context->getLineNumberStart(),
                (string)$class_fqsen,
                "$class_fqsen::$method_name"
            );
            return [];
        }
        $class = $code_base->getClassByFQSEN($class_fqsen);
        if (!$class->hasMethodWithName($code_base, $method_name)) {
            $this->emitIssue(
                Issue::UndeclaredStaticMethodInCallable,
                $context->getLineNumberStart(),
                "$class_fqsen::$method_name"
            );
            return [];
        }
        $method = $class->getMethodByName($code_base, $method_name);
        if (!$method->isStatic()) {
            $this->emitIssue(
                Issue::StaticCallToNonStatic,
                $context->getLineNumberStart(),
                "{$class->getFQSEN()}::{$method_name}()",
                (string)$method->getFQSEN(),
                $method->getFileRef()->getFile(),
                (string)$method->getFileRef()->getLineNumberStart()
            );
        }
        return [$method->getFQSEN()];
    }

    /**
     * @see ContextNode->getFunction() for a similar function
     */
    private function functionFQSENListFromFunctionName(string $function_name) : array
    {
        // TODO: Catch invalid code such as call_user_func('\\\\x\\\\y')
        $function_fqsen = FullyQualifiedFunctionName::make('', \ltrim($function_name, '\\'));
        if (!$this->code_base->hasFunctionWithFQSEN($function_fqsen)) {
            $this->emitIssue(
                Issue::UndeclaredFunctionInCallable,
                $this->context->getLineNumberStart(),
                $function_name
            );
            return [];
        }
        return [$function_fqsen];
    }

    /**
     * @param string|Node $node
     *
     * @return array<int,FullyQualifiedFunctionLikeName>
     * A list of CallableTypes associated with the given node
     *
     * @throws IssueException
     * An exception is thrown if we can't find a class for
     * the given type
     */
    private function functionLikeFQSENListFromNode($node) : array
    {
        $orig_node = $node;
        if ($node instanceof Node) {
            $node = (new ContextNode($this->code_base, $this->context, $node))->getEquivalentPHPValue();
        }
        if (\is_string($node)) {
            if (\stripos($node, '::') !== false) {
                list($class_name, $method_name) = \explode('::', $node, 2);
                return $this->methodFQSENListFromParts($class_name, $method_name);
            }
            return $this->functionFQSENListFromFunctionName($node);
        }
        if (\is_array($node)) {
            if (\count($node) !== 2) {
                $this->emitIssue(
                    Issue::TypeInvalidCallableArraySize,
                    $orig_node->lineno ?? 0,
                    \count($node)
                );
                return [];
            }
            $i = 0;
            foreach ($node as $key => $_) {
                if ($key !== $i) {
                    $this->emitIssue(
                        Issue::TypeInvalidCallableArrayKey,
                        $orig_node->lineno ?? 0,
                        $i
                    );
                    return [];
                }
                $i++;
            }
            return $this->methodFQSENListFromParts($node[0], $node[1]);
        }
        if (!($node instanceof Node)) {
            // TODO: Warn?
            return [];
        }

        // Get the types associated with the node
        $union_type = self::unionTypeFromNode(
            $this->code_base,
            $this->context,
            $node
        );

        $closure_types = [];
        foreach ($union_type->getTypeSet() as $type) {
            if ($type instanceof ClosureType && $type->hasKnownFQSEN()) {
                // TODO: Support class instances with __invoke()
                $fqsen = $type->asFQSEN();
                assert($fqsen instanceof FullyQualifiedFunctionLikeName);
                $closure_types[] = $fqsen;
            }
        }
        return $closure_types;
    }

    /**
     * @param CodeBase $code_base
     * @param Context $context
     * @param Node|string|float|int $node
     *
     * @return ?UnionType (Returns null when mixed)
     * TODO: Add an equivalent for Traversable and subclasses, once we have template support for Traversable<Key,T>
     */
    public static function unionTypeOfArrayKeyForNode(CodeBase $code_base, Context $context, $node)
    {
        $arg_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $node);
        return self::arrayKeyUnionTypeOfUnionType($arg_type);
    }

    /**
     *
     * @return ?UnionType (Returns null when mixed)
     * TODO: Add an equivalent for Traversable and subclasses, once we have template support for Traversable<Key,T>
     */
    public static function arrayKeyUnionTypeOfUnionType(UnionType $union_type)
    {
        if ($union_type->isEmpty()) {
            return null;
        }
        static $int_type;
        static $string_type;
        static $int_or_string_type;
        if ($int_type === null) {
            $int_type = IntType::instance(false);
            $string_type = StringType::instance(false);
            $int_or_string_type = new UnionType([$int_type, $string_type], true);
        }
        $key_enum_type = GenericArrayType::keyTypeFromUnionTypeKeys($union_type);
        switch ($key_enum_type) {
            case GenericArrayType::KEY_INT:
                return $int_type->asUnionType();
            case GenericArrayType::KEY_STRING:
                return $string_type->asUnionType();
            default:
                foreach ($union_type->getTypeSet() as $type) {
                    // The exact class Type is potentially invalid (includes objects) but not the subclass NativeType.
                    // The subclass IterableType of Native type is invalid, but ArrayType is a valid subclass of IterableType.
                    // And we just ignore scalars.
                    // And mixed could be a Traversable.
                    // So, don't infer anything if the union type contains any instances of the four classes.
                    // TODO: Check the expanded union type instead of anything with a class of exactly Type, searching for Traversable?
                    if (\in_array(\get_class($type), [Type::class, IterableType::class, TemplateType::class, MixedType::class])) {
                        return null;
                    }
                }
                return $int_or_string_type;
        }
    }
}
<?php declare(strict_types=1);
namespace Phan\AST;

use ast;
use ast\Node;
use ast\flags;

/**
 * This adds annotations for Phan analysis to a given node,
 * modifying the flags of a node in place.
 * This adds custom children ($node->children['phan_nf']) to node types that have a variable number of children or expected values for flags
 *
 * and returns the new Node.
 * The original \ast\Node objects are not modified.
 *
 * This adds to $node->children - Many different AST node kinds can be used in places Phan needs to know about
 * (for being potentially null/undefined)
 *
 * Current annotations:
 *
 * 1. Mark $x in isset($x['key']['nested']) as being acceptable to have null offsets.
 *    Same for $x in $x ?? null, empty($x['offset']), and so on.
 * 2. Mark $x and $x['key'] in "$x['key'] = $y" as being acceptable to be null or undefined.
 *    and so on (e.g. ['key' => $x[0]] = $y)
 */
class PhanAnnotationAdder
{
    const PHAN_NODE_FLAGS = 'phan_nf';

    const FLAG_IGNORE_NULLABLE = 1 << 29;
    const FLAG_IGNORE_UNDEF = 1 << 30;

    const FLAG_IGNORE_NULLABLE_AND_UNDEF = self::FLAG_IGNORE_UNDEF | self::FLAG_IGNORE_NULLABLE;

    public function __construct()
    {
    }

    /** @var array<int,Closure(Node):void> */
    private static $closures_for_kind;

    public static function init()
    {
        if (\is_array(self::$closures_for_kind)) {
            return;
        }
        self::initInner();
    }

    const FLAGS_NODE_TYPE_SET = [
        ast\AST_VAR => true,
        ast\AST_DIM => true,
        ast\AST_ASSIGN => true,
        ast\AST_ASSIGN_REF => true,
    ];

    /**
     * @param array $children
     * @param int $bit_set
     */
    private static function markArrayElements($children, $bit_set)
    {
        foreach ($children as $node) {
            if ($node instanceof Node) {
                $node->flags |= $bit_set;
            }
        }
    }

    /**
     * @param Node $node
     * @param int $bit_set
     */
    private static function markNode($node, $bit_set)
    {
        $kind = $node->kind;
        if (\array_key_exists($kind, self::FLAGS_NODE_TYPE_SET)) {
            $node->flags |= $bit_set;
        } elseif ($kind === ast\AST_ARRAY) {
            // flags and children are single-purpose right now
            self::markArrayElements($node->children, $bit_set);
        } else {
            $node->children[self::PHAN_NODE_FLAGS] = $bit_set;
        }
    }

    private static function initInner()
    {
        /**
         * @param Node $node
         * @return void
         */
        $binary_op_handler = function ($node) {
            if ($node->flags === flags\BINARY_COALESCE) {
                $inner_node = $node->children['left'];
                if ($inner_node instanceof Node) {
                    self::markNode($inner_node, self::FLAG_IGNORE_NULLABLE_AND_UNDEF);
                }
            }
        };
        /**
         * @param Node $node
         * @return void
         */
        $dim_handler = function ($node) {
            if ($node->flags & self::FLAG_IGNORE_NULLABLE_AND_UNDEF) {
                $inner_node = $node->children['expr'];
                if ($inner_node instanceof Node) {
                    self::markNode($inner_node, self::FLAG_IGNORE_NULLABLE_AND_UNDEF);
                }
            }
        };

        /**
         * @param Node $node
         * @return void
         */
        $ignore_nullable_and_undef_handler = function ($node) {
            $inner_node = $node->children['var'];
            if ($inner_node instanceof Node) {
                self::markNode($inner_node, self::FLAG_IGNORE_NULLABLE_AND_UNDEF);
            }
        };

        /**
         * @param Node $node
         * @return void
         */
        $ignore_nullable_and_undef_expr_handler = function ($node) {
            $inner_node = $node->children['expr'];
            if ($inner_node instanceof Node) {
                self::markNode($inner_node, self::FLAG_IGNORE_NULLABLE_AND_UNDEF);
            }
        };
        /**
         * @param Node $node
         * @return void
         */
        $ast_array_elem_handler = function ($node) {
            // Handle [$a1, $a2] = $array; - Don't warn about $node
            $bit = $node->flags & self::FLAG_IGNORE_UNDEF;
            if ($bit) {
                $inner_node = $node->children['value'];
                if (($inner_node instanceof Node)) {
                    self::markNode($inner_node, $bit);
                }
            }
        };

        self::$closures_for_kind = [
            ast\AST_BINARY_OP => $binary_op_handler,
            ast\AST_DIM => $dim_handler,
            ast\AST_EMPTY => $ignore_nullable_and_undef_expr_handler,
            ast\AST_ISSET => $ignore_nullable_and_undef_handler,
            ast\AST_UNSET => $ignore_nullable_and_undef_handler,
            ast\AST_ASSIGN => $ignore_nullable_and_undef_handler,
            ast\AST_ASSIGN_REF => $ignore_nullable_and_undef_handler,
            // Skip over AST_ARRAY
            ast\AST_ARRAY_ELEM => $ast_array_elem_handler,
        ];
    }

    /**
     * @param Node|array|int|string|float|bool|null $node
     * @return void
     */
    public static function applyFull($node)
    {
        if ($node instanceof Node) {
            $closure = self::$closures_for_kind[$node->kind] ?? null;
            if ($closure !== null) {
                $closure($node);
            }
            foreach ($node->children as $inner) {
                self::applyFull($inner);
            }
        } elseif (\is_array($node)) {
            foreach ($node as $inner) {
                self::applyFull($inner);
            }
        }
    }

    /**
     * @return void
     */
    private static function applyToScopeInner($node)
    {
        if ($node instanceof Node) {
            $kind = $node->kind;
            if ($kind === ast\AST_CLASS || $kind === ast\AST_FUNC_DECL || $kind === ast\AST_CLOSURE) {
                return;
            }

            $closure = self::$closures_for_kind[$kind] ?? null;
            if ($closure !== null) {
                $closure($node);
            }
            foreach ($node->children as $inner) {
                self::applyToScopeInner($inner);
            }
        } elseif (\is_array($node)) {
            foreach ($node as $inner) {
                self::applyToScopeInner($inner);
            }
        }
    }

    /**
     * @param Node $node a node beginning a scope such as AST_FUNC, AST_STMT_LIST, AST_METHOD, etc. (Assumes these nodes don't have any annotations.
     * @return void
     */
    public static function applyToScope(Node $node)
    {
        foreach ($node->children as $inner) {
            self::applyToScopeInner($inner);
        }
    }
}
PhanAnnotationAdder::init();
<?php declare(strict_types=1);
namespace Phan\AST;

use ast\Node;

/**
 * This simplifies a PHP AST into a form which is easier to analyze,
 * and returns the new Node.
 * The original \ast\Node objects are not modified.
 *
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 * @phan-file-suppress PhanPartialTypeMismatchArgumentInternal
 */
class ASTSimplifier
{
    public function __construct()
    {
    }

    /**
     * @param Node $node
     * @return array<int,Node> (Equivalent list of nodes to [$node], possibly a clone with modifications)
     */
    private function apply(Node $node) : array
    {
        switch ($node->kind) {
            case \ast\AST_FUNC_DECL:
            case \ast\AST_METHOD:
            case \ast\AST_CLOSURE:
            case \ast\AST_CLASS:
            case \ast\AST_DO_WHILE:
            case \ast\AST_FOR:
            case \ast\AST_FOREACH:
            case \ast\AST_WHILE:
                return [$this->applyToStmts($node)];
            //case \ast\AST_BREAK:
            //case \ast\AST_CONTINUE:
            //case \ast\AST_RETURN:
            //case \ast\AST_THROW:
            //case \ast\AST_EXIT:
            default:
                return [$node];
            case \ast\AST_STMT_LIST:
                return [$this->applyToStatementList($node)];
        // Conditional blocks:
            case \ast\AST_IF:
                return $this->normalizeIfStatement($node);
            case \ast\AST_TRY:
                return [$this->normalizeTryStatement($node)];
        }
    }

    /**
     * @param Node $node - A node which will have its child statements simplified.
     * @return Node - The same node, or an equivalent simplified node
     */
    private function applyToStmts(Node $node) : Node
    {
        $stmts = $node->children['stmts'];
        // Can be null, a single statement, or (possibly) a scalar instead of a node?
        if (!($stmts instanceof Node)) {
            return $node;
        }
        $new_stmts = $this->applyToStatementList($stmts);
        if ($new_stmts === $stmts) {
            return $node;
        }
        $new_node = clone($node);
        $new_node->children['stmts'] = $new_stmts;
        return $new_node;
    }

    /**
     * @param Node $statement_list - The statement list to simplify
     * @return Node - an equivalent statement list (Identical, or a clone)
     */
    private function applyToStatementList(Node $statement_list) : Node
    {
        if ($statement_list->kind !== \ast\AST_STMT_LIST) {
            $statement_list = self::buildStatementList($statement_list->lineno ?? 0, $statement_list);
        }
        $new_children = [];
        foreach ($statement_list->children as $child_node) {
            if ($child_node instanceof Node) {
                foreach ($this->apply($child_node) as $new_child_node) {
                    $new_children[] = $new_child_node;
                }
            } else {
                $new_children[] = $child_node;
            }
        }
        list($new_children, $modified) = $this->normalizeStatementList($new_children);
        if (!$modified && $new_children === $statement_list->children) {
            return $statement_list;
        }
        $clone_node = clone($statement_list);
        $clone_node->children = $new_children;
        return $clone_node;
    }

    /**
     * Creates a new node with kind \ast\AST_STMT_LIST from a list of 0 or more child nodes.
     */
    private static function buildStatementList(int $lineno, Node ...$child_nodes) : Node
    {
        return new Node(
            \ast\AST_STMT_LIST,
            0,
            $child_nodes,
            $lineno
        );
    }

    /**
     * @param array<int,?Node|?float|?int|?string|?float|?bool> $statements
     * @return array{0:array<int,\ast\Node>,1:bool} - [New/old list, bool $modified] An equivalent list after simplifying (or the original list)
     * @suppress PhanPartialTypeMismatchReturn
     */
    private function normalizeStatementList(array $statements) : array
    {
        $modified = false;
        $new_statements = [];
        foreach ($statements as $stmt) {
            $new_statements[] = $stmt;
            if (!($stmt instanceof Node)) {
                continue;
            }
            if ($stmt->kind !== \ast\AST_IF) {
                continue;
            }
            // Run normalizeIfStatement again.
            \array_pop($new_statements);
            \array_push($new_statements, ...$this->normalizeIfStatement($stmt));
            $modified = $modified || \end($new_statements) !== $stmt;
            continue;
        }
        return [$modified ? $new_statements : $statements, $modified];
    }

    /**
     * Replaces the last node in a list with a list of 0 or more nodes
     * @param array<int,Node> $nodes
     * @param \ast\Node ...$new_statements
     * @return void
     */
    private static function replaceLastNodeWithNodeList(array &$nodes, Node... $new_statements)
    {
        \assert(count($nodes) > 0);
        \array_pop($nodes);
        foreach ($new_statements as $stmt) {
            $nodes[] = $stmt;
        }
    }

    const NON_SHORT_CIRCUITING_BINARY_OPERATOR_FLAGS = [
        \ast\flags\BINARY_BOOL_XOR,
        \ast\flags\BINARY_IS_IDENTICAL,
        \ast\flags\BINARY_IS_NOT_IDENTICAL,
        \ast\flags\BINARY_IS_EQUAL,
        \ast\flags\BINARY_IS_NOT_EQUAL,
        \ast\flags\BINARY_IS_SMALLER,
        \ast\flags\BINARY_IS_SMALLER_OR_EQUAL,
        \ast\flags\BINARY_IS_GREATER,
        \ast\flags\BINARY_IS_GREATER_OR_EQUAL,
        \ast\flags\BINARY_SPACESHIP,
    ];

    /**
     * If this returns true, the expression has no side effects, and can safely be reordered.
     * (E.g. returns true for `MY_CONST` or `false` in `if (MY_CONST === ($x = y))`
     *
     * @param Node|string|float|int $node
     */
    private static function isExpressionWithoutSideEffects($node) : bool
    {
        if (!($node instanceof Node)) {
            return true;
        }
        switch ($node->kind) {
            case \ast\AST_CONST:
            case \ast\AST_MAGIC_CONST:
            case \ast\AST_NAME:
                return true;
            case \ast\AST_BINARY_OP:
            case \ast\AST_CLASS_CONST:
                return self::isExpressionWithoutSideEffects($node->children['class']);
            default:
                return false;
        }
    }

    /**
     * Converts an if statement to one which is easier for phan to analyze
     * E.g. repeatedly makes these conversions
     * if (A && B) {X} -> if (A) { if (B) {X}}
     * if ($var = A) {X} -> $var = A; if ($var) {X}
     * @return array<int,\ast\Node> - One or more nodes created from $original_node.
     *        Will return [$original_node] if no modifications were made.
     */
    private function normalizeIfStatement(Node $original_node) : array
    {
        $old_nodes = [];
        $nodes = [$original_node];
        // Repeatedly apply these rules
        while ($old_nodes !== $nodes) {
            $old_nodes = $nodes;
            $node = $nodes[count($nodes) - 1];
            $node->flags = 0;
            $if_cond = $node->children[0]->children['cond'];
            if (!($if_cond instanceof Node)) {
                break;  // No transformation rules apply here.
            }

            if ($if_cond->kind === \ast\AST_UNARY_OP &&
                $if_cond->flags === \ast\flags\UNARY_BOOL_NOT) {
                $cond_node = $if_cond->children['expr'];
                if ($cond_node instanceof Node &&
                        $cond_node->kind === \ast\AST_UNARY_OP &&
                        $cond_node->flags === \ast\flags\UNARY_BOOL_NOT) {
                    self::replaceLastNodeWithNodeList($nodes, $this->applyIfDoubleNegateReduction($node));
                    continue;
                }
                if (\count($node->children) === 1) {
                    self::replaceLastNodeWithNodeList($nodes, $this->applyIfNegatedToIfElseReduction($node));
                    continue;
                }
            }
            if ($if_cond->kind === \ast\AST_BINARY_OP && \in_array($if_cond->flags, self::NON_SHORT_CIRCUITING_BINARY_OPERATOR_FLAGS, true)) {
                // if (($var = A) === B) {X} -> $var = A; if ($var === B) { X}
                $if_cond_children = $if_cond->children;
                if (\in_array($if_cond_children['left']->kind ?? 0, [\ast\AST_ASSIGN, \ast\AST_ASSIGN_REF], true) &&
                        ($if_cond_children['left']->children['var']->kind ?? 0) === \ast\AST_VAR) {
                    self::replaceLastNodeWithNodeList($nodes, ...$this->applyAssignInLeftSideOfBinaryOpReduction($node));
                    continue;
                }
                if (\in_array($if_cond_children['right']->kind ?? 0, [\ast\AST_ASSIGN, \ast\AST_ASSIGN_REF], true) &&
                        ($if_cond_children['right']->children['var']->kind ?? 0) === \ast\AST_VAR &&
                        self::isExpressionWithoutSideEffects($if_cond_children['left'])) {
                    self::replaceLastNodeWithNodeList($nodes, ...$this->applyAssignInRightSideOfBinaryOpReduction($node));
                    continue;
                }
                // TODO: If the left hand side is a constant or class constant or literal, that's safe to rearrange as well
                // (But `foo($y = something()) && $x = $y` is not safe to rearrange)
            }
            if (count($node->children) === 1) {
                if ($if_cond->kind === \ast\AST_BINARY_OP &&
                        $if_cond->flags === \ast\flags\BINARY_BOOL_AND) {
                    self::replaceLastNodeWithNodeList($nodes, $this->applyIfAndReduction($node));
                    // if (A && B) {X} -> if (A) { if (B) {X}}
                    // Do this, unless there is an else statement that can be executed.
                    continue;
                }
            } elseif (count($node->children) === 2) {
                if ($if_cond->kind === \ast\AST_UNARY_OP &&
                        $if_cond->flags === \ast\flags\UNARY_BOOL_NOT &&
                        $node->children[1]->children['cond'] === null) {
                    self::replaceLastNodeWithNodeList($nodes, $this->applyIfNegateReduction($node));
                    continue;
                }
            } elseif (count($node->children) >= 3) {
                self::replaceLastNodeWithNodeList($nodes, $this->applyIfChainReduction($node));
                continue;
            }
            if ($if_cond->kind === \ast\AST_ASSIGN &&
                    $if_cond->children['var']->kind === \ast\AST_VAR) {
                // if ($var = A) {X} -> $var = A; if ($var) {X}
                // do this whether or not there is an else.
                // TODO: Could also reduce `if (($var = A) && B) {X} else if (C) {Y} -> $var = A; ....
                self::replaceLastNodeWithNodeList($nodes, ...$this->applyIfAssignReduction($node));
                continue;
            }
        }
        return $nodes;
    }

    /**
     * if (($var = A) === B) {X} -> $var = A; if ($var === B) { X }
     *
     * @return array{0:Node,1:Node}
     */
    private function applyAssignInLeftSideOfBinaryOpReduction(Node $node) : array
    {
        $inner_assign_statement = $node->children[0]->children['cond']->children['left'];
        \assert($inner_assign_statement instanceof Node);  // already checked
        $inner_assign_var = $inner_assign_statement->children['var'];

        \assert($inner_assign_var->kind === \ast\AST_VAR);

        $new_node_elem = clone($node->children[0]);
        $new_node_elem->children['cond']->children['left'] = $inner_assign_var;
        $new_node_elem->flags = 0;
        $new_node = clone($node);
        $new_node->children[0] = $new_node_elem;
        $new_node->lineno = $new_node_elem->lineno ?? 0;
        $new_node->flags = 0;
        return [$inner_assign_statement, $new_node];
    }

    /**
     * if (B === ($var = A)) {X} -> $var = A; if (B === $var) { X }
     *
     * @return array{0:Node,1:Node}
     */
    private function applyAssignInRightSideOfBinaryOpReduction(Node $node) : array
    {
        $inner_assign_statement = $node->children[0]->children['cond']->children['right'];
        $inner_assign_var = $inner_assign_statement->children['var'];

        \assert($inner_assign_statement instanceof Node);
        \assert($inner_assign_var->kind === \ast\AST_VAR);

        $new_node_elem = clone($node->children[0]);
        $new_node_elem->children['cond']->children['right'] = $inner_assign_var;
        $new_node_elem->flags = 0;
        $new_node = clone($node);
        $new_node->children[0] = $new_node_elem;
        $new_node->lineno = $new_node_elem->lineno ?? 0;
        $new_node->flags = 0;
        return [$inner_assign_statement, $new_node];
    }

    /**
     * Creates a new node with kind \ast\AST_IF from two branches
     */
    private function buildIfNode(Node $l, Node $r) : Node
    {
        \assert($l->kind === \ast\AST_IF_ELEM);
        \assert($r->kind === \ast\AST_IF_ELEM);
        return new Node(
            \ast\AST_IF,
            0,
            [$l, $r],
            $l->lineno ?? 0
        );
    }

    /**
     * maps if (A) {X} elseif (B) {Y} else {Z} -> if (A) {Y} else { if (B) {Y} else {Z}}
     */
    private function applyIfChainReduction(Node $node) : Node
    {
        $children = $node->children;  // Copy of array of Nodes of type IF_ELEM
        if (count($children) <= 2) {
            return $node;
        }
        \assert(\is_array($children));
        while (count($children) > 2) {
            $r = array_pop($children);
            $l = array_pop($children);
            assert($l instanceof Node && $r instanceof Node);
            $l->children['stmts']->flags = 0;
            $r->children['stmts']->flags = 0;
            $inner_if_node = self::buildIfNode($l, $r);
            $new_r = new Node(
                \ast\AST_IF_ELEM,
                0,
                [
                    'cond' => null,
                    'stmts' => self::buildStatementList($inner_if_node->lineno, ...($this->normalizeIfStatement($inner_if_node))),
                ],
                0
            );
            $children[] = $new_r;
        }
        // $children is an array of 2 nodes of type IF_ELEM
        return new Node(\ast\AST_IF, 0, $children, $node->lineno);
    }

    /**
     * Converts if (A && B) {X}` -> `if (A) { if (B){X}}`
     * @return Node simplified node logically equivalent to $node, with kind \ast\AST_IF.
     */
    private function applyIfAndReduction(Node $node) : Node
    {
        \assert(count($node->children) == 1);
        $inner_node_elem = clone($node->children[0]);  // AST_IF_ELEM
        $inner_node_elem->children['cond'] = $inner_node_elem->children['cond']->children['right'];
        $inner_node_elem->flags = 0;
        $inner_node_lineno = $inner_node_elem->lineno ?? 0;

        // Normalize code such as `if (A && (B && C)) {...}` recursively.
        $inner_node_stmts = $this->normalizeIfStatement(new Node(
            \ast\AST_IF,
            0,
            [$inner_node_elem],
            $inner_node_lineno
        ));

        $inner_node_stmt_list = new Node(\ast\AST_STMT_LIST, 0, $inner_node_stmts, $inner_node_lineno);
        $outer_node_elem = clone($node->children[0]);  // AST_IF_ELEM
        $outer_node_elem->children['cond'] = $node->children[0]->children['cond']->children['left'];
        $outer_node_elem->children['stmts'] = $inner_node_stmt_list;
        $outer_node_elem->flags = 0;
        return new Node(
            \ast\AST_IF,
            $node->lineno,
            [$outer_node_elem],
            0
        );
    }

    /**
     * Converts if ($x = A) {Y} -> $x = A; if ($x) {Y}
     * This allows analyzing variables set in if blocks outside of the `if` block
     * @return array{0:Node,1:Node} [$outer_assign_statement, $new_node]
     */
    private function applyIfAssignReduction(Node $node) : array
    {
        $outer_assign_statement = $node->children[0]->children['cond'];
        \assert($outer_assign_statement instanceof Node);
        $new_node_elem = clone($node->children[0]);
        $new_node_elem->children['cond'] = $new_node_elem->children['cond']->children['var'];
        $new_node_elem->flags = 0;
        $new_node = clone($node);
        $new_node->children[0] = $new_node_elem;
        $new_node->lineno = $new_node_elem->lineno ?? 0;
        $new_node->flags = 0;
        return [$outer_assign_statement, $new_node];
    }

    /**
     * Converts if (!x) {Y} else {Z} -> if (x) {Z} else {Y}
     * This improves phan's analysis for cases such as `if (!is_string($x))`.
     */
    private function applyIfNegateReduction(Node $node) : Node
    {
        \assert(count($node->children) === 2);
        \assert($node->children[0]->children['cond']->flags === \ast\flags\UNARY_BOOL_NOT);
        \assert($node->children[1]->children['cond'] === null);
        $new_node = clone($node);
        $new_node->children = [clone($new_node->children[1]), clone($new_node->children[0])];
        $new_node->children[0]->children['cond'] = $node->children[0]->children['cond']->children['expr'];
        $new_node->children[1]->children['cond'] = null;
        $new_node->flags = 0;
        return $new_node;
    }

    /**
     * Converts if (!!(x)) {Y} -> if (x) {Y}
     * This improves phan's analysis for cases such as `if (!!x)`
     */
    private function applyIfDoubleNegateReduction(Node $node) : Node
    {
        \assert($node->children[0]->children['cond']->flags === \ast\flags\UNARY_BOOL_NOT);
        \assert($node->children[0]->children['cond']->children['expr']->flags === \ast\flags\UNARY_BOOL_NOT);

        $new_cond = $node->children[0]->children['cond']->children['expr']->children['expr'];
        $new_node = clone($node);
        $new_node->flags = 0;
        $new_node->children[0] = clone($node->children[0]);
        $new_node->children[0]->flags = 0;
        $new_node->children[0]->children['cond'] = $new_cond;

        return $new_node;
    }

    private function applyIfNegatedToIfElseReduction(Node $node) : Node
    {
        \assert(\count($node->children) === 1);
        $if_elem = $node->children[0];
        \assert($if_elem->children['cond']->flags === \ast\flags\UNARY_BOOL_NOT);
        $lineno = $if_elem->lineno;
        $new_else_elem = new Node(
            \ast\AST_IF_ELEM,
            0,
            [
                'cond' => null,
                'stmts' => $if_elem->children['stmts'],
            ],
            $lineno
        );
        $new_if_elem = new Node(
            \ast\AST_IF_ELEM,
            0,
            [
                'cond' => $if_elem->children['cond']->children['expr'],
                'stmts' => new Node(\ast\AST_STMT_LIST, 0, [], $if_elem->lineno),
            ],
            $lineno
        );
        return new Node(
            \ast\AST_IF,
            0,
            [$new_if_elem, $new_else_elem],
            $node->lineno
        );
    }

    /**
     * Recurses on a list of 0 or more catch statements. (as in try/catch)
     * Returns an equivalent list of catch AST nodes (or the original if no changes were made)
     */
    private function normalizeCatchesList(Node $catches) : Node
    {
        $list = $catches->children;
        $new_list = array_map(function (Node $node) {
            return $this->applyToStmts($node);
        }, $list);
        if ($new_list === $list) {
            return $catches;
        }
        $new_catches = clone($catches);
        $new_catches->children = $new_list;
        $new_catches->flags = 0;
        return $new_catches;
    }

    /**
     * Recurses on a try/catch/finally node, applying simplifications(catch/finally are optional)
     * Returns an equivalent try/catch/finally node (or the original if no changes were made)
     */
    private function normalizeTryStatement(Node $node) : Node
    {
        $try = $node->children['try'];
        $catches = $node->children['catches'];
        $finally = $node->children['finally'] ?? null;
        $new_try = $this->applyToStatementList($try);
        $new_catches = $catches ? $this->normalizeCatchesList($catches) : $catches;
        $new_finally = $finally ? $this->applyToStatementList($finally) : $finally;
        if ($new_try === $try && $new_catches === $catches && $new_finally === $finally) {
            return $node;
        }
        $new_node = clone($node);
        $new_node->children['try'] = $new_try;
        $new_node->children['catches'] = $new_catches;
        $new_node->children['finally'] = $new_finally;
        $new_node->flags = 0;
        return $new_node;
    }

    public static function applyStatic(Node $node) : Node
    {
        $rewriter = new self();
        $nodes = $rewriter->apply($node);
        \assert(\count($nodes) === 1);
        return $nodes[0];
    }
}
<?php declare(strict_types=1);
namespace Phan\AST\Visitor;

use ast\Node;

/**
 * A visitor of AST nodes based on the node's flag value
 */
interface FlagVisitor
{
    /**
     * Visit a node with flag `\ast\flags\BINARY_ADD`
     */
    public function visitBinaryAdd(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_BITWISE_AND`
     */
    public function visitBinaryBitwiseAnd(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_BITWISE_OR`
     */
    public function visitBinaryBitwiseOr(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_BITWISE_XOR`
     */
    public function visitBinaryBitwiseXor(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_BOOL_XOR`
     */
    public function visitBinaryBoolXor(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_CONCAT`
     */
    public function visitBinaryConcat(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_DIV`
     */
    public function visitBinaryDiv(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_IS_EQUAL`
     */
    public function visitBinaryIsEqual(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_IS_IDENTICAL`
     */
    public function visitBinaryIsIdentical(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_IS_NOT_EQUAL`
     */
    public function visitBinaryIsNotEqual(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_IS_NOT_IDENTICAL`
     */
    public function visitBinaryIsNotIdentical(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_IS_SMALLER`
     */
    public function visitBinaryIsSmaller(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_IS_SMALLER_OR_EQUAL`
     */
    public function visitBinaryIsSmallerOrEqual(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_MOD`
     */
    public function visitBinaryMod(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_MUL`
     */
    public function visitBinaryMul(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_POW`
     */
    public function visitBinaryPow(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_SHIFT_LEFT`
     */
    public function visitBinaryShiftLeft(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_SHIFT_RIGHT`
     */
    public function visitBinaryShiftRight(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_SPACESHIP`
     */
    public function visitBinarySpaceship(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_SUB`
     */
    public function visitBinarySub(Node $node);

    /**
     * Visit a node with flag `\ast\flags\CLASS_ABSTRACT`
     */
    public function visitClassAbstract(Node $node);

    /**
     * Visit a node with flag `\ast\flags\CLASS_FINAL`
     */
    public function visitClassFinal(Node $node);

    /**
     * Visit a node with flag `\ast\flags\CLASS_INTERFACE`
     */
    public function visitClassInterface(Node $node);

    /**
     * Visit a node with flag `\ast\flags\CLASS_TRAIT`
     */
    public function visitClassTrait(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MODIFIER_ABSTRACT`
     */
    public function visitModifierAbstract(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MODIFIER_FINAL`
     */
    public function visitModifierFinal(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MODIFIER_PRIVATE`
     */
    public function visitModifierPrivate(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MODIFIER_PROTECTED`
     */
    public function visitModifierProtected(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MODIFIER_PUBLIC`
     */
    public function visitModifierPublic(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MODIFIER_STATIC`
     */
    public function visitModifierStatic(Node $node);

    /**
     * Visit a node with flag `\ast\flags\NAME_FQ`
     */
    public function visitNameFq(Node $node);

    /**
     * Visit a node with flag `\ast\flags\NAME_NOT_FQ`
     */
    public function visitNameNotFq(Node $node);

    /**
     * Visit a node with flag `\ast\flags\NAME_RELATIVE`
     */
    public function visitNameRelative(Node $node);

    /**
     * Visit a node with flag `\ast\flags\PARAM_REF`
     */
    public function visitParamRef(Node $node);

    /**
     * Visit a node with flag `\ast\flags\PARAM_VARIADIC`
     */
    public function visitParamVariadic(Node $node);

    /**
     * Visit a node with flag `\ast\flags\RETURNS_REF`
     */
    public function visitReturnsRef(Node $node);

    /**
     * Visit a node with flag `\ast\flags\TYPE_ARRAY`
     */
    public function visitUnionTypeArray(Node $node);

    /**
     * Visit a node with flag `\ast\flags\TYPE_BOOL`
     */
    public function visitUnionTypeBool(Node $node);

    /**
     * Visit a node with flag `\ast\flags\TYPE_CALLABLE`
     */
    public function visitUnionTypeCallable(Node $node);

    /**
     * Visit a node with flag `\ast\flags\TYPE_DOUBLE`
     */
    public function visitUnionTypeDouble(Node $node);

    /**
     * Visit a node with flag `\ast\flags\TYPE_LONG`
     */
    public function visitUnionTypeLong(Node $node);

    /**
     * Visit a node with flag `\ast\flags\TYPE_NULL`
     */
    public function visitUnionTypeNull(Node $node);

    /**
     * Visit a node with flag `\ast\flags\TYPE_OBJECT`
     */
    public function visitUnionTypeObject(Node $node);

    /**
     * Visit a node with flag `\ast\flags\TYPE_STRING`
     */
    public function visitUnionTypeString(Node $node);

    /**
     * Visit a node with flag `\ast\flags\UNARY_BITWISE_NOT`
     */
    public function visitUnaryBitwiseNot(Node $node);

    /**
     * Visit a node with flag `\ast\flags\UNARY_BOOL_NOT`
     */
    public function visitUnaryBoolNot(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_BOOL_AND`
     */
    public function visitBinaryBoolAnd(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_BOOL_OR`
     */
    public function visitBinaryBoolOr(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_COALESCE`
     */
    public function visitBinaryCoalesce(Node $node);
    /**
     * Visit a node with flag `\ast\flags\BINARY_IS_GREATER`
     */
    public function visitBinaryIsGreater(Node $node);

    /**
     * Visit a node with flag `\ast\flags\BINARY_IS_GREATER_OR_EQUAL`
     */
    public function visitBinaryIsGreaterOrEqual(Node $node);

    /**
     * Visit a node with flag `\ast\flags\CLASS_ANONYMOUS`
     */
    public function visitClassAnonymous(Node $node);

    /**
     * Visit a node with flag `\ast\flags\EXEC_EVAL`
     */
    public function visitExecEval(Node $node);

    /**
     * Visit a node with flag `\ast\flags\EXEC_INCLUDE`
     */
    public function visitExecInclude(Node $node);

    /**
     * Visit a node with flag `\ast\flags\EXEC_INCLUDE_ONCE`
     */
    public function visitExecIncludeOnce(Node $node);

    /**
     * Visit a node with flag `\ast\flags\EXEC_REQUIRE`
     */
    public function visitExecRequire(Node $node);

    /**
     * Visit a node with flag `\ast\flags\EXEC_REQUIRE_ONCE`
     */
    public function visitExecRequireOnce(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MAGIC_CLASS`
     */
    public function visitMagicClass(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MAGIC_DIR`
     */
    public function visitMagicDir(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MAGIC_FILE`
     */
    public function visitMagicFile(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MAGIC_FUNCTION`
     */
    public function visitMagicFunction(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MAGIC_LINE`
     */
    public function visitMagicLine(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MAGIC_METHOD`
     */
    public function visitMagicMethod(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MAGIC_NAMESPACE`
     */
    public function visitMagicNamespace(Node $node);

    /**
     * Visit a node with flag `\ast\flags\MAGIC_TRAIT`
     */
    public function visitMagicTrait(Node $node);

    /**
     * Visit a node with flag `\ast\flags\UNARY_MINUS`
     */
    public function visitUnaryMinus(Node $node);

    /**
     * Visit a node with flag `\ast\flags\UNARY_PLUS`
     */
    public function visitUnaryPlus(Node $node);

    /**
     * Visit a node with flag `\ast\flags\UNARY_SILENCE`
     */
    public function visitUnarySilence(Node $node);

    /**
     * Visit a node with flag `\ast\flags\USE_CONST`
     */
    public function visitUseConst(Node $node);

    /**
     * Visit a node with flag `\ast\flags\USE_FUNCTION`
     */
    public function visitUseFunction(Node $node);

    /**
     * Visit a node with flag `\ast\flags\USE_NORMAL`
     */
    public function visitUseNormal(Node $node);
}
<?php declare(strict_types=1);
namespace Phan\AST\Visitor;

use ast\Node;

/**
 * A visitor of AST nodes based on the node's kind value
 */
interface KindVisitor
{

    /**
     * Visit a node with kind `\ast\AST_ARRAY`
     */
    public function visitArgList(Node $node);

    /**
     * Visit a node with kind `\ast\AST_ARRAY`
     */
    public function visitArray(Node $node);

    /**
     * Visit a node with kind `ast\AST_ARRAY_ELEM`
     */
    public function visitArrayElem(Node $node);

    /**
     * Visit a node with kind `\ast\AST_ASSIGN`
     */
    public function visitAssign(Node $node);

    /**
     * Visit a node with kind `\ast\AST_ASSIGN_OP`
     */
    public function visitAssignOp(Node $node);

    /**
     * Visit a node with kind `\ast\AST_ASSIGN_REF`
     */
    public function visitAssignRef(Node $node);

    /**
     * Visit a node with kind `\ast\AST_BINARY_OP`
     */
    public function visitBinaryOp(Node $node);

    /**
     * Visit a node with kind `\ast\AST_BINARY_OP`
     */
    public function visitBreak(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CALL`
     */
    public function visitCall(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CAST`
     */
    public function visitCast(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CATCH`
     */
    public function visitCatch(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CLASS`
     */
    public function visitClass(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CLASS_CONST`
     */
    public function visitClassConst(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CLASS_CONST_DECL`
     */
    public function visitClassConstDecl(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CLOSURE`
     */
    public function visitClosure(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CLOSURE_USES`
     */
    public function visitClosureUses(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CLOSURE_VAR`
     */
    public function visitClosureVar(Node $node);

    /**
     * Visit a node with kind `\ast\AST_COALESCE`
     */
    public function visitCoalesce(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CONST`
     */
    public function visitConst(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CONST_DECL`
     */
    public function visitConstDecl(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CONST_ELEM`
     */
    public function visitConstElem(Node $node);

    /**
     * Visit a nod ewith kind `\ast\AST_DECLARE`
     */
    public function visitDeclare(Node $node);

    /**
     * Visit a node with kind `\ast\AST_DIM`
     */
    public function visitDim(Node $node);

    /**
     * Visit a node with kind `\ast\AST_DO_WHILE`
     */
    public function visitDoWhile(Node $node);

    /**
     * Visit a node with kind `\ast\AST_ECHO`
     */
    public function visitEcho(Node $node);
    /**
     *
     * Visit a node with kind `\ast\AST_EMPTY`
     */
    public function visitEmpty(Node $node);

    /**
     * Visit a node with kind `\ast\AST_ENCAPS_LIST`
     */
    public function visitEncapsList(Node $node);

    /**
     * Visit a node with kind `\ast\AST_EXIT`
     */
    public function visitExit(Node $node);

    /**
     * Visit a node with kind `\ast\AST_EXPR_LIST`
     */
    public function visitExprList(Node $node);

    /**
     * Visit a node with kind `\ast\AST_FOREACH`
     */
    public function visitForeach(Node $node);

    /**
     * Visit a node with kind `\ast\AST_FUNC_DECL`
     */
    public function visitFuncDecl(Node $node);

    /**
     * Visit a node with kind `\ast\AST_ISSET`
     */
    public function visitIsset(Node $node);

    /**
     * Visit a node with kind `\ast\AST_GLOBAL`
     */
    public function visitGlobal(Node $node);

    /**
     * Visit a node with kind `\ast\AST_GREATER`
     */
    public function visitGreater(Node $node);

    /**
     * Visit a node with kind `\ast\AST_GREATER_EQUAL`
     */
    public function visitGreaterEqual(Node $node);

    /**
     * Visit a node with kind `\ast\AST_GROUP_USE`
     */
    public function visitGroupUse(Node $node);

    /**
     * Visit a node with kind `\ast\AST_IF`
     */
    public function visitIf(Node $node);

    /**
     * Visit a node with kind `\ast\AST_IF_ELEM`
     */
    public function visitIfElem(Node $node);

    /**
     * Visit a node with kind `\ast\AST_INSTANCEOF`
     */
    public function visitInstanceof(Node $node);

    /**
     * Visit a node with kind `\ast\AST_MAGIC_CONST`
     */
    public function visitMagicConst(Node $node);

    /**
     * Visit a node with kind `\ast\AST_METHOD`
     */
    public function visitMethod(Node $node);

    /**
     * Visit a node with kind `\ast\AST_METHOD_CALL`
     */
    public function visitMethodCall(Node $node);

    /**
     * Visit a node with kind `\ast\AST_NAME`
     */
    public function visitName(Node $node);

    /**
     * Visit a node with kind `\ast\AST_NAMESPACE`
     */
    public function visitNamespace(Node $node);

    /**
     * Visit a node with kind `\ast\AST_NEW`
     */
    public function visitNew(Node $node);

    /**
     * Visit a node with kind `\ast\AST_PARAM`
     */
    public function visitParam(Node $node);

    /**
     * Visit a node with kind `\ast\AST_PARAM_LIST`
     */
    public function visitParamList(Node $node);

    /**
     * Visit a node with kind `\ast\AST_PRE_INC`
     */
    public function visitPreInc(Node $node);

    /**
     * Visit a node with kind `\ast\AST_PRINT`
     */
    public function visitPrint(Node $node);

    /**
     * Visit a node with kind `\ast\AST_PROP`
     */
    public function visitProp(Node $node);

    /**
     * Visit a node with kind `\ast\AST_PROP_DECL`
     */
    public function visitPropDecl(Node $node);

    /**
     * Visit a node with kind `\ast\AST_PROP_ELEM`
     */
    public function visitPropElem(Node $node);

    /**
     * Visit a node with kind `\ast\AST_RETURN`
     */
    public function visitReturn(Node $node);

    /**
     * Visit a node with kind `\ast\AST_STATIC`
     */
    public function visitStatic(Node $node);

    /**
     * Visit a node with kind `\ast\AST_STATIC_CALL`
     */
    public function visitStaticCall(Node $node);

    /**
     * Visit a node with kind `\ast\AST_STATIC_PROP`
     */
    public function visitStaticProp(Node $node);

    /**
     * Visit a node with kind `\ast\AST_STMT_LIST`
     */
    public function visitStmtList(Node $node);

    /**
     * Visit a node with kind `\ast\AST_SWITCH`
     */
    public function visitSwitch(Node $node);

    /**
     * Visit a node with kind `\ast\AST_SWITCH_CASE`
     */
    public function visitSwitchCase(Node $node);

    /**
     * Visit a node with kind `\ast\AST_SWITCH_LIST`
     */
    public function visitSwitchList(Node $node);

    /**
     * Visit a node with kind `\ast\AST_TYPE`
     */
    public function visitType(Node $node);

    /**
     * Visit a node with kind `\ast\AST_NULLABLE_TYPE`
     */
    public function visitNullableType(Node $node);

    /**
     * Visit a node with kind `\ast\AST_UNARY_MINUS`
     */
    public function visitUnaryMinus(Node $node);

    /**
     * Visit a node with kind `\ast\AST_UNARY_OP`
     */
    public function visitUnaryOp(Node $node);

    /**
     * Visit a node with kind `\ast\AST_USE`
     */
    public function visitUse(Node $node);

    /**
     * Visit a node with kind `\ast\AST_USE_ELEM`
     */
    public function visitUseElem(Node $node);

    /**
     * Visit a node with kind `\ast\AST_USE_TRAIT`
     */
    public function visitUseTrait(Node $node);

    /**
     * Visit a node with kind `\ast\AST_VAR`
     */
    public function visitVar(Node $node);

    /**
     * Visit a node with kind `\ast\AST_WHILE`
     */
    public function visitWhile(Node $node);

    /**
     * Visit a node with kind `\ast\AST_AND`
     */
    public function visitAnd(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CATCH_LIST`
     */
    public function visitCatchList(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CLONE`
     */
    public function visitClone(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CONDITIONAL`
     */
    public function visitConditional(Node $node);

    /**
     * Visit a node with kind `\ast\AST_CONTINUE`
     */
    public function visitContinue(Node $node);

    /**
     * Visit a node with kind `\ast\AST_FOR`
     */
    public function visitFor(Node $node);

    /**
     * Visit a node with kind `\ast\AST_GOTO`
     */
    public function visitGoto(Node $node);

    /**
     * Visit a node with kind `\ast\AST_HALT_COMPILER`
     */
    public function visitHaltCompiler(Node $node);

    /**
     * Visit a node with kind `\ast\AST_INCLUDE_OR_EVAL`
     */
    public function visitIncludeOrEval(Node $node);

    /**
     * Visit a node with kind `\ast\AST_LABEL`
     */
    public function visitLabel(Node $node);

    /**
     * Visit a node with kind `\ast\AST_METHOD_REFERENCE`
     */
    public function visitMethodReference(Node $node);

    /**
     * Visit a node with kind `\ast\AST_NAME_LIST`
     */
    public function visitNameList(Node $node);

    /**
     * Visit a node with kind `\ast\AST_OR`
     */
    public function visitOr(Node $node);

    /**
     * Visit a node with kind `\ast\AST_POST_DEC`
     */
    public function visitPostDec(Node $node);

    /**
     * Visit a node with kind `\ast\AST_POST_INC`
     */
    public function visitPostInc(Node $node);

    /**
     * Visit a node with kind `\ast\AST_PRE_DEC`
     */
    public function visitPreDec(Node $node);

    /**
     * Visit a node with kind `\ast\AST_REF`
     */
    public function visitRef(Node $node);

    /**
     * Visit a node with kind `\ast\AST_SHELL_EXEC`
     */
    public function visitShellExec(Node $node);

    /**
     * Visit a node with kind `\ast\AST_SILENCE`
     */
    public function visitSilence(Node $node);

    /**
     * Visit a node with kind `\ast\AST_THROW`
     */
    public function visitThrow(Node $node);

    /**
     * Visit a node with kind `\ast\AST_TRAIT_ADAPTATIONS`
     */
    public function visitTraitAdaptations(Node $node);

    /**
     * Visit a node with kind `\ast\AST_TRAIT_ALIAS`
     */
    public function visitTraitAlias(Node $node);

    /**
     * Visit a node with kind `\ast\AST_TRAIT_PRECEDENCE`
     */
    public function visitTraitPrecedence(Node $node);

    /**
     * Visit a node with kind `\ast\AST_TRY`
     */
    public function visitTry(Node $node);

    /**
     * Visit a node with kind `\ast\AST_UNARY_PLUS`
     */
    public function visitUnaryPlus(Node $node);

    /**
     * Visit a node with kind `\ast\AST_UNPACK`
     */
    public function visitUnpack(Node $node);

    /**
     * Visit a node with kind `\ast\AST_UNSET`
     */
    public function visitUnset(Node $node);

    /**
     * Visit a node with kind `\ast\AST_YIELD`
     */
    public function visitYield(Node $node);

    /**
     * Visit a node with kind `\ast\AST_YIELD_FROM`
     */
    public function visitYieldFrom(Node $node);
}
<?php declare(strict_types=1);
namespace Phan\AST\Visitor;

use Phan\Debug;
use ast\Node;

class Element
{
    use \Phan\Profile;

    /**
     * @var Node
     */
    private $node;

    /**
     * @param Node $node
     * Any AST node.
     */
    public function __construct(Node $node)
    {
        $this->node = $node;
    }

    // TODO: Revert this change back to the switch statement
    // when php 7.2 is released and phan supports php 7.2.
    // See https://github.com/php/php-src/pull/2427/files
    // This decreased the duration of running phan by about 4%
    const VISIT_LOOKUP_TABLE = [
        \ast\AST_ARG_LIST           => 'visitArgList',
        \ast\AST_ARRAY              => 'visitArray',
        \ast\AST_ARRAY_ELEM         => 'visitArrayElem',
        \ast\AST_ASSIGN             => 'visitAssign',
        \ast\AST_ASSIGN_OP          => 'visitAssignOp',
        \ast\AST_ASSIGN_REF         => 'visitAssignRef',
        \ast\AST_BINARY_OP          => 'visitBinaryOp',
        \ast\AST_BREAK              => 'visitBreak',
        \ast\AST_CALL               => 'visitCall',
        \ast\AST_CAST               => 'visitCast',
        \ast\AST_CATCH              => 'visitCatch',
        \ast\AST_CLASS              => 'visitClass',
        \ast\AST_CLASS_CONST        => 'visitClassConst',
        \ast\AST_CLASS_CONST_DECL   => 'visitClassConstDecl',
        \ast\AST_CLOSURE            => 'visitClosure',
        \ast\AST_CLOSURE_USES       => 'visitClosureUses',
        \ast\AST_CLOSURE_VAR        => 'visitClosureVar',
        \ast\AST_COALESCE           => 'visitCoalesce',
        \ast\AST_CONST              => 'visitConst',
        \ast\AST_CONST_DECL         => 'visitConstDecl',
        \ast\AST_CONST_ELEM         => 'visitConstElem',
        \ast\AST_DECLARE            => 'visitDeclare',
        \ast\AST_DIM                => 'visitDim',
        \ast\AST_DO_WHILE           => 'visitDoWhile',
        \ast\AST_ECHO               => 'visitEcho',
        \ast\AST_EMPTY              => 'visitEmpty',
        \ast\AST_ENCAPS_LIST        => 'visitEncapsList',
        \ast\AST_EXIT               => 'visitExit',
        \ast\AST_EXPR_LIST          => 'visitExprList',
        \ast\AST_FOREACH            => 'visitForeach',
        \ast\AST_FUNC_DECL          => 'visitFuncDecl',
        \ast\AST_ISSET              => 'visitIsset',
        \ast\AST_GLOBAL             => 'visitGlobal',
        \ast\AST_GREATER            => 'visitGreater',
        \ast\AST_GREATER_EQUAL      => 'visitGreaterEqual',
        \ast\AST_GROUP_USE          => 'visitGroupUse',
        \ast\AST_IF                 => 'visitIf',
        \ast\AST_IF_ELEM            => 'visitIfElem',
        \ast\AST_INSTANCEOF         => 'visitInstanceof',
        \ast\AST_MAGIC_CONST        => 'visitMagicConst',
        \ast\AST_METHOD             => 'visitMethod',
        \ast\AST_METHOD_CALL        => 'visitMethodCall',
        \ast\AST_NAME               => 'visitName',
        \ast\AST_NAMESPACE          => 'visitNamespace',
        \ast\AST_NEW                => 'visitNew',
        \ast\AST_PARAM              => 'visitParam',
        \ast\AST_PARAM_LIST         => 'visitParamList',
        \ast\AST_PRE_INC            => 'visitPreInc',
        \ast\AST_PRINT              => 'visitPrint',
        \ast\AST_PROP               => 'visitProp',
        \ast\AST_PROP_DECL          => 'visitPropDecl',
        \ast\AST_PROP_ELEM          => 'visitPropElem',
        \ast\AST_RETURN             => 'visitReturn',
        \ast\AST_STATIC             => 'visitStatic',
        \ast\AST_STATIC_CALL        => 'visitStaticCall',
        \ast\AST_STATIC_PROP        => 'visitStaticProp',
        \ast\AST_STMT_LIST          => 'visitStmtList',
        \ast\AST_SWITCH             => 'visitSwitch',
        \ast\AST_SWITCH_CASE        => 'visitSwitchCase',
        \ast\AST_SWITCH_LIST        => 'visitSwitchList',
        \ast\AST_TYPE               => 'visitType',
        \ast\AST_NULLABLE_TYPE      => 'visitNullableType',
        \ast\AST_UNARY_MINUS        => 'visitUnaryMinus',
        \ast\AST_UNARY_OP           => 'visitUnaryOp',
        \ast\AST_USE                => 'visitUse',
        \ast\AST_USE_ELEM           => 'visitUseElem',
        \ast\AST_USE_TRAIT          => 'visitUseTrait',
        \ast\AST_VAR                => 'visitVar',
        \ast\AST_WHILE              => 'visitWhile',
        \ast\AST_AND                => 'visitAnd',
        \ast\AST_CATCH_LIST         => 'visitCatchList',
        \ast\AST_CLONE              => 'visitClone',
        \ast\AST_CONDITIONAL        => 'visitConditional',
        \ast\AST_CONTINUE           => 'visitContinue',
        \ast\AST_FOR                => 'visitFor',
        \ast\AST_GOTO               => 'visitGoto',
        \ast\AST_HALT_COMPILER      => 'visitHaltCompiler',
        \ast\AST_INCLUDE_OR_EVAL    => 'visitIncludeOrEval',
        \ast\AST_LABEL              => 'visitLabel',
        \ast\AST_METHOD_REFERENCE   => 'visitMethodReference',
        \ast\AST_NAME_LIST          => 'visitNameList',
        \ast\AST_OR                 => 'visitOr',
        \ast\AST_POST_DEC           => 'visitPostDec',
        \ast\AST_POST_INC           => 'visitPostInc',
        \ast\AST_PRE_DEC            => 'visitPreDec',
        \ast\AST_REF                => 'visitRef',
        \ast\AST_SHELL_EXEC         => 'visitShellExec',
        \ast\AST_SILENCE            => 'visitSilence',
        \ast\AST_THROW              => 'visitThrow',
        \ast\AST_TRAIT_ADAPTATIONS  => 'visitTraitAdaptations',
        \ast\AST_TRAIT_ALIAS        => 'visitTraitAlias',
        \ast\AST_TRAIT_PRECEDENCE   => 'visitTraitPrecedence',
        \ast\AST_TRY                => 'visitTry',
        \ast\AST_UNARY_PLUS         => 'visitUnaryPlus',
        \ast\AST_UNPACK             => 'visitUnpack',
        \ast\AST_UNSET              => 'visitUnset',
        \ast\AST_YIELD              => 'visitYield',
        \ast\AST_YIELD_FROM         => 'visitYieldFrom',
    ];

    /**
     * Accepts a visitor that differentiates on the kind value
     * of the AST node.
     *
     * NOTE: This was turned into a static method for performance
     * because it was called extremely frequently.
     *
     * @suppress PhanUnreferencedPublicMethod Phan's code inlines this, but may be useful for some plugins
     */
    public static function acceptNodeAndKindVisitor(Node $node, KindVisitor $visitor)
    {
        $fn_name = self::VISIT_LOOKUP_TABLE[$node->kind] ?? null;
        if (\is_string($fn_name)) {
            return $visitor->{$fn_name}($node);
        } else {
            Debug::printNode($node);
            \assert(false, 'All node kinds must match');
        }
    }

    const VISIT_BINARY_LOOKUP_TABLE = [
        \ast\flags\BINARY_ADD => 'visitBinaryAdd',
        \ast\flags\BINARY_BITWISE_AND => 'visitBinaryBitwiseAnd',
        \ast\flags\BINARY_BITWISE_OR => 'visitBinaryBitwiseOr',
        \ast\flags\BINARY_BITWISE_XOR => 'visitBinaryBitwiseXor',
        \ast\flags\BINARY_BOOL_XOR => 'visitBinaryBoolXor',
        \ast\flags\BINARY_CONCAT => 'visitBinaryConcat',
        \ast\flags\BINARY_DIV => 'visitBinaryDiv',
        \ast\flags\BINARY_IS_EQUAL => 'visitBinaryIsEqual',
        \ast\flags\BINARY_IS_IDENTICAL => 'visitBinaryIsIdentical',
        \ast\flags\BINARY_IS_NOT_EQUAL => 'visitBinaryIsNotEqual',
        \ast\flags\BINARY_IS_NOT_IDENTICAL => 'visitBinaryIsNotIdentical',
        \ast\flags\BINARY_IS_SMALLER => 'visitBinaryIsSmaller',
        \ast\flags\BINARY_IS_SMALLER_OR_EQUAL => 'visitBinaryIsSmallerOrEqual',
        \ast\flags\BINARY_MOD => 'visitBinaryMod',
        \ast\flags\BINARY_MUL => 'visitBinaryMul',
        \ast\flags\BINARY_POW => 'visitBinaryPow',
        \ast\flags\BINARY_SHIFT_LEFT => 'visitBinaryShiftLeft',
        \ast\flags\BINARY_SHIFT_RIGHT => 'visitBinaryShiftRight',
        \ast\flags\BINARY_SPACESHIP => 'visitBinarySpaceship',
        \ast\flags\BINARY_SUB => 'visitBinarySub',
        \ast\flags\BINARY_BOOL_AND => 'visitBinaryBoolAnd',
        \ast\flags\BINARY_BOOL_OR => 'visitBinaryBoolOr',
        \ast\flags\BINARY_COALESCE => 'visitBinaryCoalesce',
        \ast\flags\BINARY_IS_GREATER => 'visitBinaryIsGreater',
        \ast\flags\BINARY_IS_GREATER_OR_EQUAL => 'visitBinaryIsGreaterOrEqual',
    ];

    /**
     * Accepts a visitor that differentiates on the flag value
     * of the AST node.
     */
    public static function acceptBinaryFlagVisitor(Node $node, FlagVisitor $visitor)
    {
        $fn_name = self::VISIT_BINARY_LOOKUP_TABLE[$node->flags] ?? null;
        if (\is_string($fn_name)) {
            return $visitor->{$fn_name}($node);
        } else {
            Debug::printNode($node);
            \assert(false, "All flags must match. Found " . self::flagDescription($node));
        }
    }

    /**
     * Accepts a visitor that differentiates on the flag value
     * of the AST node.
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function acceptClassFlagVisitor(FlagVisitor $visitor)
    {
        switch ($this->node->flags) {
            case \ast\flags\CLASS_ABSTRACT:
                return $visitor->visitClassAbstract($this->node);
            case \ast\flags\CLASS_FINAL:
                return $visitor->visitClassFinal($this->node);
            case \ast\flags\CLASS_INTERFACE:
                return $visitor->visitClassInterface($this->node);
            case \ast\flags\CLASS_TRAIT:
                return $visitor->visitClassTrait($this->node);
            case \ast\flags\CLASS_ANONYMOUS:
                return $visitor->visitClassAnonymous($this->node);
            default:
                \assert(
                    false,
                    "All flags must match. Found "
                    . self::flagDescription($this->node)
                );
                break;
        }
    }

    /**
     * Accepts a visitor that differentiates on the flag value
     * of the AST node.
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function acceptNameFlagVisitor(FlagVisitor $visitor)
    {
        switch ($this->node->flags) {
            case \ast\flags\NAME_FQ:
                return $visitor->visitNameFq($this->node);
            case \ast\flags\NAME_NOT_FQ:
                return $visitor->visitNameNotFq($this->node);
            case \ast\flags\NAME_RELATIVE:
                return $visitor->visitNameRelative($this->node);
            default:
                \assert(
                    false,
                    "All flags must match. Found "
                    . self::flagDescription($this->node)
                );
                break;
        }
    }

    /**
     * Accepts a visitor that differentiates on the flag value
     * of the AST node.
     *
     * TODO: This is wrong, a parameter can be neither ref nor variadic, or both.
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function acceptParamFlagVisitor(FlagVisitor $visitor)
    {
        switch ($this->node->flags) {
            case \ast\flags\PARAM_REF:
                return $visitor->visitParamRef($this->node);
            case \ast\flags\PARAM_VARIADIC:
                return $visitor->visitParamVariadic($this->node);
            default:
                \assert(
                    false,
                    "All flags must match. Found "
                    . self::flagDescription($this->node)
                );
                break;
        }
    }

    /**
     * Accepts a visitor that differentiates on the flag value
     * of the AST node.
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function acceptTypeFlagVisitor(FlagVisitor $visitor)
    {
        switch ($this->node->flags) {
            case \ast\flags\TYPE_ARRAY:
                return $visitor->visitUnionTypeArray($this->node);
            case \ast\flags\TYPE_BOOL:
                return $visitor->visitUnionTypeBool($this->node);
            case \ast\flags\TYPE_CALLABLE:
                return $visitor->visitUnionTypeCallable($this->node);
            case \ast\flags\TYPE_DOUBLE:
                return $visitor->visitUnionTypeDouble($this->node);
            case \ast\flags\TYPE_LONG:
                return $visitor->visitUnionTypeLong($this->node);
            case \ast\flags\TYPE_NULL:
                return $visitor->visitUnionTypeNull($this->node);
            case \ast\flags\TYPE_OBJECT:
                return $visitor->visitUnionTypeObject($this->node);
            case \ast\flags\TYPE_STRING:
                return $visitor->visitUnionTypeString($this->node);
            default:
                \assert(
                    false,
                    "All flags must match. Found "
                    . self::flagDescription($this->node)
                );
                break;
        }
    }

    /**
     * Accepts a visitor that differentiates on the flag value
     * of the AST node.
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function acceptUnaryFlagVisitor(FlagVisitor $visitor)
    {
        switch ($this->node->flags) {
            case \ast\flags\UNARY_BITWISE_NOT:
                return $visitor->visitUnaryBitwiseNot($this->node);
            case \ast\flags\UNARY_BOOL_NOT:
                return $visitor->visitUnaryBoolNot($this->node);
            case \ast\flags\UNARY_MINUS:
                return $visitor->visitUnaryMinus($this->node);
            case \ast\flags\UNARY_PLUS:
                return $visitor->visitUnaryPlus($this->node);
            case \ast\flags\UNARY_SILENCE:
                return $visitor->visitUnarySilence($this->node);
            default:
                \assert(
                    false,
                    "All flags must match. Found "
                    . self::flagDescription($this->node)
                );
                break;
        }
    }

    /**
     * Accepts a visitor that differentiates on the flag value
     * of the AST node.
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function acceptExecFlagVisitor(FlagVisitor $visitor)
    {
        switch ($this->node->flags) {
            case \ast\flags\EXEC_EVAL:
                return $visitor->visitExecEval($this->node);
            case \ast\flags\EXEC_INCLUDE:
                return $visitor->visitExecInclude($this->node);
            case \ast\flags\EXEC_INCLUDE_ONCE:
                return $visitor->visitExecIncludeOnce($this->node);
            case \ast\flags\EXEC_REQUIRE:
                return $visitor->visitExecRequire($this->node);
            case \ast\flags\EXEC_REQUIRE_ONCE:
                return $visitor->visitExecRequireOnce($this->node);
            default:
                \assert(
                    false,
                    "All flags must match. Found "
                    . self::flagDescription($this->node)
                );
                break;
        }
    }

    /**
     * Accepts a visitor that differentiates on the flag value
     * of the AST node.
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function acceptMagicFlagVisitor(FlagVisitor $visitor)
    {
        switch ($this->node->flags) {
            case \ast\flags\MAGIC_CLASS:
                return $visitor->visitMagicClass($this->node);
            case \ast\flags\MAGIC_DIR:
                return $visitor->visitMagicDir($this->node);
            case \ast\flags\MAGIC_FILE:
                return $visitor->visitMagicFile($this->node);
            case \ast\flags\MAGIC_FUNCTION:
                return $visitor->visitMagicFunction($this->node);
            case \ast\flags\MAGIC_LINE:
                return $visitor->visitMagicLine($this->node);
            case \ast\flags\MAGIC_METHOD:
                return $visitor->visitMagicMethod($this->node);
            case \ast\flags\MAGIC_NAMESPACE:
                return $visitor->visitMagicNamespace($this->node);
            case \ast\flags\MAGIC_TRAIT:
                return $visitor->visitMagicTrait($this->node);
            default:
                \assert(
                    false,
                    "All flags must match. Found "
                    . self::flagDescription($this->node)
                );
                break;
        }
    }

    /**
     * Accepts a visitor that differentiates on the flag value
     * of the AST node.
     *
     * @suppress PhanUnreferencedPublicMethod
     */
    public function acceptUseFlagVisitor(FlagVisitor $visitor)
    {
        switch ($this->node->flags) {
            case \ast\flags\USE_CONST:
                return $visitor->visitUseConst($this->node);
            case \ast\flags\USE_FUNCTION:
                return $visitor->visitUseFunction($this->node);
            case \ast\flags\USE_NORMAL:
                return $visitor->visitUseNormal($this->node);
            default:
                \assert(
                    false,
                    "All flags must match. Found "
                    . self::flagDescription($this->node)
                );
                break;
        }
    }

    private static function flagDescription(Node $node) : string
    {
        return Debug::astFlagDescription($node->flags ?? 0, $node->kind);
    }
}
<?php declare(strict_types=1);
namespace Phan\AST\Visitor;

use ast\Node;

/**
 * A visitor of AST nodes based on the node's flag value
 * which does nothing upon visiting a node
 */
abstract class FlagVisitorImplementation implements FlagVisitor
{

    abstract public function visit(Node $node);

    public function visitBinaryAdd(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryBitwiseAnd(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryBitwiseOr(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryBitwiseXor(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryBoolXor(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryConcat(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryDiv(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryIsEqual(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryIsIdentical(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryIsNotEqual(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryIsNotIdentical(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryIsSmaller(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryIsSmallerOrEqual(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryMod(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryMul(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryPow(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryShiftLeft(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryShiftRight(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinarySpaceship(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinarySub(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClassAbstract(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClassFinal(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClassInterface(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClassTrait(Node $node)
    {
        return $this->visit($node);
    }

    public function visitModifierAbstract(Node $node)
    {
        return $this->visit($node);
    }

    public function visitModifierFinal(Node $node)
    {
        return $this->visit($node);
    }

    public function visitModifierPrivate(Node $node)
    {
        return $this->visit($node);
    }

    public function visitModifierProtected(Node $node)
    {
        return $this->visit($node);
    }

    public function visitModifierPublic(Node $node)
    {
        return $this->visit($node);
    }

    public function visitModifierStatic(Node $node)
    {
        return $this->visit($node);
    }

    public function visitNameFq(Node $node)
    {
        return $this->visit($node);
    }

    public function visitNameNotFq(Node $node)
    {
        return $this->visit($node);
    }

    public function visitNameRelative(Node $node)
    {
        return $this->visit($node);
    }

    public function visitParamRef(Node $node)
    {
        return $this->visit($node);
    }

    public function visitParamVariadic(Node $node)
    {
        return $this->visit($node);
    }

    public function visitReturnsRef(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnionTypeArray(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnionTypeBool(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnionTypeCallable(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnionTypeDouble(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnionTypeLong(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnionTypeNull(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnionTypeObject(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnionTypeString(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnaryBitwiseNot(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnaryBoolNot(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryBoolAnd(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryBoolOr(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryCoalesce(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryIsGreater(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryIsGreaterOrEqual(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClassAnonymous(Node $node)
    {
        return $this->visit($node);
    }

    public function visitExecEval(Node $node)
    {
        return $this->visit($node);
    }

    public function visitExecInclude(Node $node)
    {
        return $this->visit($node);
    }

    public function visitExecIncludeOnce(Node $node)
    {
        return $this->visit($node);
    }

    public function visitExecRequire(Node $node)
    {
        return $this->visit($node);
    }

    public function visitExecRequireOnce(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMagicClass(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMagicDir(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMagicFile(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMagicFunction(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMagicLine(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMagicMethod(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMagicNamespace(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMagicTrait(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnaryMinus(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnaryPlus(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnarySilence(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUseConst(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUseFunction(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUseNormal(Node $node)
    {
        return $this->visit($node);
    }
}
<?php declare(strict_types=1);
namespace Phan\AST\Visitor;

use ast\Node;
use Phan\AST\Visitor\Element;
use Phan\Debug;

/**
 * A visitor of AST nodes based on the node's kind value
 * which does nothing upon visiting a node of any kind
 */
abstract class KindVisitorImplementation implements KindVisitor
{

    abstract public function visit(Node $node);

    /**
     * @param Node $node
     * A node to visit
     */
    public function __invoke(Node $node)
    {
        $fn_name = Element::VISIT_LOOKUP_TABLE[$node->kind] ?? 'handleMissingNodeKind';
        return $this->{$fn_name}($node);
    }

    /**
     * @suppress PhanUnreferencedPublicMethod
     */
    public function handleMissingNodeKind(Node $node)
    {
        fprintf(STDERR, "Unexpected Node kind. Node:\n%s\n", Debug::nodeToString($node));
        assert(false, 'All node kinds must match');
    }

    public function visitArgList(Node $node)
    {
        return $this->visit($node);
    }

    public function visitArray(Node $node)
    {
        return $this->visit($node);
    }

    public function visitArrayElem(Node $node)
    {
        return $this->visit($node);
    }

    public function visitAssign(Node $node)
    {
        return $this->visit($node);
    }

    public function visitAssignOp(Node $node)
    {
        return $this->visit($node);
    }

    public function visitAssignRef(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBinaryOp(Node $node)
    {
        return $this->visit($node);
    }

    public function visitBreak(Node $node)
    {
        return $this->visit($node);
    }

    public function visitCall(Node $node)
    {
        return $this->visit($node);
    }

    public function visitCast(Node $node)
    {
        return $this->visit($node);
    }

    public function visitCatch(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClass(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClassConst(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClassConstDecl(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClosure(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClosureUses(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClosureVar(Node $node)
    {
        return $this->visit($node);
    }

    public function visitCoalesce(Node $node)
    {
        return $this->visit($node);
    }

    public function visitConst(Node $node)
    {
        return $this->visit($node);
    }

    public function visitConstDecl(Node $node)
    {
        return $this->visit($node);
    }

    public function visitConstElem(Node $node)
    {
        return $this->visit($node);
    }

    public function visitDeclare(Node $node)
    {
        return $this->visit($node);
    }

    public function visitDim(Node $node)
    {
        return $this->visit($node);
    }

    public function visitDoWhile(Node $node)
    {
        return $this->visit($node);
    }

    public function visitEcho(Node $node)
    {
        return $this->visit($node);
    }

    public function visitEmpty(Node $node)
    {
        return $this->visit($node);
    }

    public function visitEncapsList(Node $node)
    {
        return $this->visit($node);
    }

    public function visitExit(Node $node)
    {
        return $this->visit($node);
    }

    public function visitExprList(Node $node)
    {
        return $this->visit($node);
    }

    public function visitForeach(Node $node)
    {
        return $this->visit($node);
    }

    public function visitFuncDecl(Node $node)
    {
        return $this->visit($node);
    }

    public function visitIsset(Node $node)
    {
        return $this->visit($node);
    }

    public function visitGlobal(Node $node)
    {
        return $this->visit($node);
    }

    public function visitGreater(Node $node)
    {
        return $this->visit($node);
    }

    public function visitGreaterEqual(Node $node)
    {
        return $this->visit($node);
    }

    public function visitGroupUse(Node $node)
    {
        return $this->visit($node);
    }

    public function visitIf(Node $node)
    {
        return $this->visit($node);
    }

    public function visitIfElem(Node $node)
    {
        return $this->visit($node);
    }

    public function visitInstanceof(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMagicConst(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMethod(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMethodCall(Node $node)
    {
        return $this->visit($node);
    }

    public function visitName(Node $node)
    {
        return $this->visit($node);
    }

    public function visitNamespace(Node $node)
    {
        return $this->visit($node);
    }

    public function visitNew(Node $node)
    {
        return $this->visit($node);
    }

    public function visitParam(Node $node)
    {
        return $this->visit($node);
    }

    public function visitParamList(Node $node)
    {
        return $this->visit($node);
    }

    public function visitPreInc(Node $node)
    {
        return $this->visit($node);
    }

    public function visitPrint(Node $node)
    {
        return $this->visit($node);
    }

    public function visitProp(Node $node)
    {
        return $this->visit($node);
    }

    public function visitPropDecl(Node $node)
    {
        return $this->visit($node);
    }

    public function visitPropElem(Node $node)
    {
        return $this->visit($node);
    }

    public function visitReturn(Node $node)
    {
        return $this->visit($node);
    }

    public function visitStatic(Node $node)
    {
        return $this->visit($node);
    }

    public function visitStaticCall(Node $node)
    {
        return $this->visit($node);
    }

    public function visitStaticProp(Node $node)
    {
        return $this->visit($node);
    }

    public function visitStmtList(Node $node)
    {
        return $this->visit($node);
    }

    public function visitSwitch(Node $node)
    {
        return $this->visit($node);
    }

    public function visitSwitchCase(Node $node)
    {
        return $this->visit($node);
    }

    public function visitSwitchList(Node $node)
    {
        return $this->visit($node);
    }

    public function visitType(Node $node)
    {
        return $this->visit($node);
    }

    public function visitNullableType(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnaryMinus(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnaryOp(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUse(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUseElem(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUseTrait(Node $node)
    {
        return $this->visit($node);
    }

    public function visitVar(Node $node)
    {
        return $this->visit($node);
    }

    public function visitWhile(Node $node)
    {
        return $this->visit($node);
    }


    public function visitAnd(Node $node)
    {
        return $this->visit($node);
    }

    public function visitCatchList(Node $node)
    {
        return $this->visit($node);
    }

    public function visitClone(Node $node)
    {
        return $this->visit($node);
    }

    public function visitConditional(Node $node)
    {
        return $this->visit($node);
    }

    public function visitContinue(Node $node)
    {
        return $this->visit($node);
    }

    public function visitFor(Node $node)
    {
        return $this->visit($node);
    }

    public function visitGoto(Node $node)
    {
        return $this->visit($node);
    }

    public function visitHaltCompiler(Node $node)
    {
        return $this->visit($node);
    }

    public function visitIncludeOrEval(Node $node)
    {
        return $this->visit($node);
    }

    public function visitLabel(Node $node)
    {
        return $this->visit($node);
    }

    public function visitMethodReference(Node $node)
    {
        return $this->visit($node);
    }

    public function visitNameList(Node $node)
    {
        return $this->visit($node);
    }

    public function visitOr(Node $node)
    {
        return $this->visit($node);
    }

    public function visitPostDec(Node $node)
    {
        return $this->visit($node);
    }

    public function visitPostInc(Node $node)
    {
        return $this->visit($node);
    }

    public function visitPreDec(Node $node)
    {
        return $this->visit($node);
    }

    public function visitRef(Node $node)
    {
        return $this->visit($node);
    }

    public function visitShellExec(Node $node)
    {
        return $this->visit($node);
    }

    public function visitSilence(Node $node)
    {
        return $this->visit($node);
    }

    public function visitThrow(Node $node)
    {
        return $this->visit($node);
    }

    public function visitTraitAdaptations(Node $node)
    {
        return $this->visit($node);
    }

    public function visitTraitAlias(Node $node)
    {
        return $this->visit($node);
    }

    public function visitTraitPrecedence(Node $node)
    {
        return $this->visit($node);
    }

    public function visitTry(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnaryPlus(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnpack(Node $node)
    {
        return $this->visit($node);
    }

    public function visitUnset(Node $node)
    {
        return $this->visit($node);
    }

    public function visitYield(Node $node)
    {
        return $this->visit($node);
    }

    public function visitYieldFrom(Node $node)
    {
        return $this->visit($node);
    }
}
<?php declare(strict_types=1);

namespace Phan\AST\TolerantASTConverter;

use Exception;
use Throwable;

/**
 * Should be handled similarly to parseError.
 * Note that getLine() can't be overridden
 */
class ParseException extends Exception
{
    private $line_number_start;

    public function __construct(string $message, int $line_number_start, Throwable $previous = null)
    {
        parent::__construct($message, 0, $previous);
        $this->line_number_start = $line_number_start;
    }

    public function getLineNumberStart() : int
    {
        return $this->line_number_start;
    }
}
<?php declare(strict_types=1);

namespace Phan\AST\TolerantASTConverter;

use function chr;
use function hexdec;
use function octdec;

use Error;

/**
 * This class is based on code from https://github.com/nikic/PHP-Parser/blob/master/lib/PhpParser/Node/Scalar/String_.php
 *
 * Original License
 * ----------------
 *
 * Copyright (c) 2011-2018 by Nikita Popov.
 *
 * Some rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *
 *     * The names of the contributors may not be used to endorse or
 *       promote products derived from this software without specific
 *       prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
final class String_
{
    const REPLACEMENTS = [
        '\\' => '\\',
        '$'  =>  '$',
        'n'  => "\n",
        'r'  => "\r",
        't'  => "\t",
        'f'  => "\f",
        'v'  => "\v",
        'e'  => "\x1B",
    ];

    /**
     * @internal
     *
     * Parses a string token.
     *
     * @param string $str String token content
     * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
     *
     * @return string The parsed string
     */
    public static function parse(string $str, bool $parseUnicodeEscape = true) : string
    {
        $bLength = 0;
        if ('b' === $str[0] || 'B' === $str[0]) {
            $bLength = 1;
        }

        if ('\'' === $str[$bLength]) {
            return \str_replace(
                ['\\\\', '\\\''],
                ['\\', '\''],
                \substr($str, $bLength + 1, -1)
            );
        } else {
            return self::parseEscapeSequences(
                substr($str, $bLength + 1, -1),
                '"',
                $parseUnicodeEscape
            );
        }
    }

    /**
     * @internal
     *
     * Parses escape sequences in strings (all string types apart from single quoted).
     *
     * @param string      $str   String without quotes
     * @param null|string $quote Quote type
     * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
     *
     * @return string String with escape sequences parsed
     */
    public static function parseEscapeSequences(string $str, $quote, bool $parseUnicodeEscape = true) : string
    {
        if (null !== $quote) {
            $str = \str_replace('\\' . $quote, $quote, $str);
        }

        $extra = '';
        if ($parseUnicodeEscape) {
            $extra = '|u\{([0-9a-fA-F]+)\}';
        }

        return \preg_replace_callback(
            '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~',
            function ($matches) {
                $str = $matches[1];

                if (isset(self::REPLACEMENTS[$str])) {
                    return self::REPLACEMENTS[$str];
                } elseif ('x' === $str[0] || 'X' === $str[0]) {
                    return chr(hexdec($str));
                } elseif ('u' === $str[0]) {
                    return self::codePointToUtf8(hexdec($matches[2]));
                } else {
                    return chr(octdec($str));
                }
            },
            $str
        );
    }

    /**
     * Converts a Unicode code point to its UTF-8 encoded representation.
     *
     * @param int $num Code point
     *
     * @return string UTF-8 representation of code point
     */
    private static function codePointToUtf8(int $num) : string
    {
        if ($num <= 0x7F) {
            return chr($num);
        }
        if ($num <= 0x7FF) {
            return chr(($num>>6) + 0xC0) . chr(($num&0x3F) + 0x80);
        }
        if ($num <= 0xFFFF) {
            return chr(($num>>12) + 0xE0) . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
        }
        if ($num <= 0x1FFFFF) {
            return chr(($num>>18) + 0xF0) . chr((($num>>12)&0x3F) + 0x80)
                 . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
        }
        throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large');
    }
}
<?php declare(strict_types=1);

namespace Phan\AST\TolerantASTConverter;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

/**
 * Source: https://github.com/TysonAndre/tolerant-php-parser-to-php-ast
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Tyson Andre
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
class NodeDumper
{
    /** @var string */
    private $file_contents;
    /** @var bool */
    private $include_offset;
    /** @var bool */
    private $include_token_kind;
    /** @var string */
    private $indent;

    // TODO: Pass an options array instead, or add setters?
    public function __construct(string $file_contents, bool $include_offset = false, bool $include_token_kind = false, string $indent = '    ')
    {
        $this->file_contents = $file_contents;
        $this->include_offset = $include_offset;
        $this->include_token_kind = $include_token_kind;
        $this->indent = $indent;
    }

    /** @return void */
    public function setIncludeOffset(bool $include_offset)
    {
        $this->include_offset = $include_offset;
    }

    /** @return void */
    public function setIncludeTokenKind(bool $include_token_kind)
    {
        $this->include_token_kind = $include_token_kind;
    }

    /**
     * @return void
     * @suppress PhanUnreferencedPublicMethod
     */
    public function setIndent(string $indent)
    {
        $this->indent = $indent;
    }

    public function dumpClassName(Node $ast_node) : string
    {
        $name = get_class($ast_node);
        if (stripos($name, 'Microsoft\\PhpParser\\') === 0) {
            $name = substr($name, 20);
        }
        return $name;
    }

    public function dumpTokenClassName(Token $ast_node) : string
    {
        $name = get_class($ast_node);
        if (stripos($name, 'Microsoft\\PhpParser\\') === 0) {
            $name = substr($name, 20);
        }
        return $name;
    }

    /**
     * @param Node|Token $ast_node
     * @param string $padding (to be echoed before the current node
     * @return string
     */
    public function dumpTreeAsString($ast_node, string $key = '', string $padding = '') : string
    {
        if ($ast_node instanceof Node) {
            $first_part = \sprintf(
                "%s%s%s%s\n",
                $padding,
                $key !== '' ? $key . ': ' : '',
                $this->dumpClassName($ast_node),
                $this->include_offset ? ' (@' . $ast_node->getStart() . ')' : ''
            );

            $result = $first_part;
            foreach ($ast_node->getChildNodesAndTokens() as $name => $child) {
                $result .= $this->dumpTreeAsString($child, $name, $padding . $this->indent);
            }
            return $result;
        } elseif ($ast_node instanceof Token) {
            return \sprintf(
                "%s%s%s: %s%s%s: %s\n",
                $padding,
                $key !== '' ? $key . ': ' : '',
                $this->dumpTokenClassName($ast_node),
                $ast_node->getTokenKindNameFromValue($ast_node->kind),
                $this->include_token_kind ? '(' . $ast_node->kind . ')' : '',
                $this->include_offset ? ' (@' . $ast_node->start . ')' : '',
                \json_encode(\substr($this->file_contents, $ast_node->fullStart, $ast_node->length))
            );
        } elseif (\is_scalar($ast_node) || $ast_node === null) {
            return \var_export($ast_node, true);
        } else {
            $type = is_object($ast_node) ? get_class($ast_node) : gettype($ast_node);
            throw new \InvalidArgumentException("Unexpected type of \$ast_node was seen in dumper: " . $type);
        }
    }

    /**
     * @param Node|Token $ast_node
     * @param string $padding (to be echoed before the current node
     * @return void
     * @suppress PhanUnreferencedPublicMethod
     */
    public function dumpTree($ast_node, string $key = '', string $padding = '')
    {
        echo $this->dumpTreeAsString($ast_node, $key, $padding);
    }
}
<?php
/**
 * Based on PHPDoc stub file for ast extension from
 * https://github.com/nikic/php-ast/blob/master/ast_stub.php
 *
 * @author Bill Schaller <bill@zeroedin.com>
 * @author Nikita Popov <nikic@php.net>
 *
 * With modifications to be a functional replacement for the data
 * structures and global constants of ext-ast. (for class ast\Node)
 *
 * This supports AST version 50
 *
 * However, this file does not define any global functions such as
 * ast\parse_code() and ast\parse_file(). (to avoid confusion)
 *
 * @author Tyson Andre
 */

// AST KIND CONSTANTS
namespace ast;

const AST_ARG_LIST = 128;
const AST_LIST = 255;
const AST_ARRAY = 129;
const AST_ENCAPS_LIST = 130;
const AST_EXPR_LIST = 131;
const AST_STMT_LIST = 132;
const AST_IF = 133;
const AST_SWITCH_LIST = 134;
const AST_CATCH_LIST = 135;
const AST_PARAM_LIST = 136;
const AST_CLOSURE_USES = 137;
const AST_PROP_DECL = 138;
const AST_CONST_DECL = 139;
const AST_CLASS_CONST_DECL = 140;
const AST_NAME_LIST = 141;
const AST_TRAIT_ADAPTATIONS = 142;
const AST_USE = 143;
const AST_NAME = 2048;
const AST_CLOSURE_VAR = 2049;
const AST_NULLABLE_TYPE = 2050;
const AST_FUNC_DECL = 66;
const AST_CLOSURE = 67;
const AST_METHOD = 68;
const AST_CLASS = 69;
const AST_MAGIC_CONST = 0;
const AST_TYPE = 1;
const AST_VAR = 256;
const AST_CONST = 257;
const AST_UNPACK = 258;
const AST_UNARY_PLUS = 259;
const AST_UNARY_MINUS = 260;
const AST_CAST = 261;
const AST_EMPTY = 262;
const AST_ISSET = 263;
const AST_SILENCE = 264;
const AST_SHELL_EXEC = 265;
const AST_CLONE = 266;
const AST_EXIT = 267;
const AST_PRINT = 268;
const AST_INCLUDE_OR_EVAL = 269;
const AST_UNARY_OP = 270;
const AST_PRE_INC = 271;
const AST_PRE_DEC = 272;
const AST_POST_INC = 273;
const AST_POST_DEC = 274;
const AST_YIELD_FROM = 275;
const AST_GLOBAL = 276;
const AST_UNSET = 277;
const AST_RETURN = 278;
const AST_LABEL = 279;
const AST_REF = 280;
const AST_HALT_COMPILER = 281;
const AST_ECHO = 282;
const AST_THROW = 283;
const AST_GOTO = 284;
const AST_BREAK = 285;
const AST_CONTINUE = 286;
const AST_DIM = 512;
const AST_PROP = 513;
const AST_STATIC_PROP = 514;
const AST_CALL = 515;
const AST_CLASS_CONST = 516;
const AST_ASSIGN = 517;
const AST_ASSIGN_REF = 518;
const AST_ASSIGN_OP = 519;
const AST_BINARY_OP = 520;
const AST_GREATER = 521;
const AST_GREATER_EQUAL = 522;
const AST_AND = 523;
const AST_OR = 524;
const AST_ARRAY_ELEM = 525;
const AST_NEW = 526;
const AST_INSTANCEOF = 527;
const AST_YIELD = 528;
const AST_COALESCE = 529;
const AST_STATIC = 530;
const AST_WHILE = 531;
const AST_DO_WHILE = 532;
const AST_IF_ELEM = 533;
const AST_SWITCH = 534;
const AST_SWITCH_CASE = 535;
const AST_DECLARE = 536;
const AST_PROP_ELEM = 774;
const AST_CONST_ELEM = 775;
const AST_USE_TRAIT = 537;
const AST_TRAIT_PRECEDENCE = 538;
const AST_METHOD_REFERENCE = 539;
const AST_NAMESPACE = 540;
const AST_USE_ELEM = 541;
const AST_TRAIT_ALIAS = 542;
const AST_GROUP_USE = 543;
const AST_METHOD_CALL = 768;
const AST_STATIC_CALL = 769;
const AST_CONDITIONAL = 770;
const AST_TRY = 771;
const AST_CATCH = 772;
const AST_PARAM = 773;
const AST_FOR = 1024;
const AST_FOREACH = 1025;
// END AST KIND CONSTANTS

// AST FLAG CONSTANTS
namespace ast\flags;

const NAME_FQ = 0;
const NAME_NOT_FQ = 1;
const NAME_RELATIVE = 2;
const MODIFIER_PUBLIC = 256;
const MODIFIER_PROTECTED = 512;
const MODIFIER_PRIVATE = 1024;
const MODIFIER_STATIC = 1;
const MODIFIER_ABSTRACT = 2;
const MODIFIER_FINAL = 4;
const RETURNS_REF = 67108864;
const CLASS_ABSTRACT = 32;
const CLASS_FINAL = 4;
const CLASS_TRAIT = 128;
const CLASS_INTERFACE = 64;
const CLASS_ANONYMOUS = 256;
const PARAM_REF = 1;
const PARAM_VARIADIC = 2;
const TYPE_NULL = 1;
const TYPE_BOOL = 13;
const TYPE_LONG = 4;
const TYPE_DOUBLE = 5;
const TYPE_STRING = 6;
const TYPE_ARRAY = 7;
const TYPE_OBJECT = 8;
const TYPE_CALLABLE = 14;
const TYPE_VOID = 18;
const TYPE_ITERABLE = 19;
const UNARY_BOOL_NOT = 13;
const UNARY_BITWISE_NOT = 12;
const UNARY_SILENCE = 260;
const UNARY_PLUS = 261;
const UNARY_MINUS = 262;
const BINARY_BOOL_AND = 259;
const BINARY_BOOL_OR = 258;
const BINARY_BOOL_XOR = 14;
const BINARY_BITWISE_OR = 9;
const BINARY_BITWISE_AND = 10;
const BINARY_BITWISE_XOR = 11;
const BINARY_CONCAT = 8;
const BINARY_ADD = 1;
const BINARY_SUB = 2;
const BINARY_MUL = 3;
const BINARY_DIV = 4;
const BINARY_MOD = 5;
const BINARY_POW = 166;
const BINARY_SHIFT_LEFT = 6;
const BINARY_SHIFT_RIGHT = 7;
const BINARY_IS_IDENTICAL = 15;
const BINARY_IS_NOT_IDENTICAL = 16;
const BINARY_IS_EQUAL = 17;
const BINARY_IS_NOT_EQUAL = 18;
const BINARY_IS_SMALLER = 19;
const BINARY_IS_SMALLER_OR_EQUAL = 20;
const BINARY_IS_GREATER = 256;
const BINARY_IS_GREATER_OR_EQUAL = 257;
const BINARY_SPACESHIP = 170;
const BINARY_COALESCE = 260;
const ASSIGN_BITWISE_OR = 31;
const ASSIGN_BITWISE_AND = 32;
const ASSIGN_BITWISE_XOR = 33;
const ASSIGN_CONCAT = 30;
const ASSIGN_ADD = 23;
const ASSIGN_SUB = 24;
const ASSIGN_MUL = 25;
const ASSIGN_DIV = 26;
const ASSIGN_MOD = 27;
const ASSIGN_POW = 167;
const ASSIGN_SHIFT_LEFT = 28;
const ASSIGN_SHIFT_RIGHT = 29;
const EXEC_EVAL = 1;
const EXEC_INCLUDE = 2;
const EXEC_INCLUDE_ONCE = 4;
const EXEC_REQUIRE = 8;
const EXEC_REQUIRE_ONCE = 16;
const USE_NORMAL = 361;
const USE_FUNCTION = 346;
const USE_CONST = 347;
const MAGIC_LINE = 370;
const MAGIC_FILE = 371;
const MAGIC_DIR = 372;
const MAGIC_NAMESPACE = 389;
const MAGIC_FUNCTION = 376;
const MAGIC_METHOD = 375;
const MAGIC_CLASS = 373;
const MAGIC_TRAIT = 374;
const ARRAY_SYNTAX_LIST = 1;
const ARRAY_SYNTAX_LONG = 2;
const ARRAY_SYNTAX_SHORT = 3;
// END AST FLAG CONSTANTS

namespace ast;

if (!class_exists('\ast\Node')) {
/**
 * This class describes a single node in a PHP AST.
 * @suppress PhanRedefineClassInternal
 */
    class Node
    {
        /** @var int AST Node Kind. Values are one of ast\AST_* constants. */
        public $kind;

        /**
         * @var int AST Flags.
         * Certain node kinds have flags that can be set.
         * These will be a bitfield of ast\flags\* constants.
         */
        public $flags;

        /** @var int Line the node starts in */
        public $lineno;

        /** @var array Child nodes (may be empty) */
        public $children;
        /**
     * A constructor which validates data types but not the values themselves.
     * For backwards compatibility reasons, all values are optional and properties default to null
     * @suppress PhanTypeMismatchProperty
     */
        public function __construct(int $kind = null, int $flags = null, array $children = null, int $lineno = null)
        {
            $this->kind = $kind;
            $this->flags = $flags;
            $this->children = $children;
            $this->lineno = $lineno;
        }
    }
}
<?php declare(strict_types=1);

namespace Phan\AST\TolerantASTConverter;

use ast;
use Microsoft\PhpParser;
use Microsoft\PhpParser\Diagnostic;
use Microsoft\PhpParser\DiagnosticsProvider;
use Microsoft\PhpParser\FilePositionMap;
use Microsoft\PhpParser\MissingToken;
use Microsoft\PhpParser\Parser;
use Microsoft\PhpParser\Token;
use Microsoft\PhpParser\TokenKind;

// If php-ast isn't loaded already, then load this file to generate equivalent
// class, constant, and function definitions.
if (!class_exists('\ast\Node')) {
    require_once __DIR__ . '/ast_shim.php';
}


/**
 * Source: https://github.com/TysonAndre/tolerant-php-parser-to-php-ast
 *
 * Uses Microsoft/tolerant-php-parser to create an instance of ast\Node.
 * Useful if the php-ast extension isn't actually installed.
 *
 * @author Tyson Andre
 *
 * TODO: Don't need to pass in $start_line for many of these functions
 *
 * This is implemented as a collection of static methods for performance,
 * but functionality is provided through instance methods.
 * (The private methods may become instance methods if the performance impact is negligible
 * in PHP and HHVM)
 *
 * The instance methods set all of the options (static variables)
 * each time they are invoked,
 * so it's possible to have multiple callers use this without affecting each other.
 *
 * Compatibility: PHP 7.1
 *
 * ----------------------------------------------------------------------------
 *
 *
 * License for TolerantASTConverter.php:
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017-2018 Tyson Andre
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 * @phan-file-suppress PhanPossiblyNullTypeReturn
 * @phan-file-suppress PhanPartialTypeMismatchReturn
 */
final class TolerantASTConverter
{
    // The latest stable version of php-ast.
    // For something > 50, update the library's release.
    const AST_VERSION = 50;

    // The versions that this supports
    const SUPPORTED_AST_VERSIONS = [self::AST_VERSION];

    const _IGNORED_STRING_TOKEN_KIND_SET = [
        TokenKind::OpenBraceDollarToken => true,
        TokenKind::OpenBraceToken => true,
        TokenKind::DollarOpenBraceToken => true,
        TokenKind::CloseBraceToken => true,
    ];

    // If this environment variable is set, this will throw.
    // (For debugging, may be removed in the future)
    const ENV_AST_THROW_INVALID = 'AST_THROW_INVALID';

    private static $php_version_id_parsing = PHP_VERSION_ID;

    /**
     * @var int - Internal counter for declarations, to generate __declId in `\ast\Node`s for declarations.
     */
    private static $decl_id = 0;

    /** @var bool */
    private static $should_add_placeholders = false;

    /** @var string */
    private static $file_contents = '';

    /** @var FilePositionMap */
    private static $file_position_map;

    /** @var bool */
    private static $parse_all_doc_comments = false;

    /** @var bool Sets equivalent static option in self::_start_parsing() */
    private $instance_should_add_placeholders = false;

    /**
     * @var int can be used to tweak behavior for compatibility.
     * Set to a newer version to support comments on class constants, etc.
     */
    private $instance_php_version_id_parsing = PHP_VERSION_ID;

    /**
     * @var bool can be used to force all doc comments to be parsed
     */
    private $instance_parse_all_doc_comments = false;

    // No-op.
    public function __construct()
    {
    }

    /** @return void */
    public function setShouldAddPlaceholders(bool $value)
    {
        $this->instance_should_add_placeholders = $value;
    }

    /** @return void */
    public function setPHPVersionId(int $value)
    {
        $this->instance_php_version_id_parsing = $value;
    }

    /**
     * Parse all doc comments, even the ones the current php version's php-ast would be incapable of providing.
     * @return void
     */
    public function setParseAllDocComments(bool $value)
    {
        $this->instance_parse_all_doc_comments = $value;
    }

    /**
     * @param Diagnostic[] $errors @phan-output-reference
     * @return \ast\Node
     */
    public function parseCodeAsPHPAST(string $file_contents, int $version, array &$errors = [])
    {
        if (!\in_array($version, self::SUPPORTED_AST_VERSIONS)) {
            throw new \InvalidArgumentException(sprintf("Unexpected version: want %s, got %d", \implode(', ', self::SUPPORTED_AST_VERSIONS), $version));
        }
        // Aside: this can be implemented as a stub.
        $parser_node = self::phpParserParse($file_contents, $errors);
        return $this->phpParserToPhpast($parser_node, $version, $file_contents);
    }

    /**
     * @return PhpParser\Node
     */
    public static function phpParserParse(string $file_contents, array &$errors = null) : PhpParser\Node
    {
        $parser = new Parser();  // TODO: In php 7.3, we might need to provide a version, due to small changes in lexing?
        $result = $parser->parseSourceFile($file_contents);
        $errors = DiagnosticsProvider::getDiagnostics($result);
        return $result;
    }


    /**
     * Visible for testing
     *
     * @param PhpParser\Node $parser_node
     * @param int $ast_version
     * @param string $file_contents
     * @return ast\Node
     * @suppress PhanPartialTypeMismatchReturn this should always be passed a statement list
     */
    public function phpParserToPhpast(PhpParser\Node $parser_node, int $ast_version, string $file_contents)
    {
        if (!\in_array($ast_version, self::SUPPORTED_AST_VERSIONS)) {
            throw new \InvalidArgumentException(sprintf("Unexpected version: want %s, got %d", implode(', ', self::SUPPORTED_AST_VERSIONS), $ast_version));
        }
        $this->startParsing($file_contents);
        $stmts = self::phpParserNodeToAstNode($parser_node);
        // return self::normalizeNamespaces($stmts);
        return $stmts;
    }

    /** @return void */
    private function startParsing(string $file_contents)
    {
        self::$decl_id = 0;
        self::$should_add_placeholders = $this->instance_should_add_placeholders;
        self::$php_version_id_parsing = $this->instance_php_version_id_parsing;
        self::$parse_all_doc_comments = $this->instance_parse_all_doc_comments;
        self::$file_position_map = new FilePositionMap($file_contents);
        // $file_contents required for looking up line numbers.
        // TODO: Other data structures?
        self::$file_contents = $file_contents;
    }

    /** @param null|bool|int|string|PhpParser\Node|Token|array $n */
    private static function debugDumpNodeOrToken($n) : string
    {
        if (is_scalar($n)) {
            return var_export($n, true);
        }
        if (!is_array($n)) {
            $n = [$n];
        }
        $result = [];
        foreach ($n as $e) {
            $dumper = new NodeDumper(self::$file_contents);
            $dumper->setIncludeTokenKind(true);
            $result[] = $dumper->dumpTreeAsString($e);
        }
        return implode("\n", $result);
    }

    /**
     * @param Token|PhpParser\Node[]|PhpParser\Node\StatementNode $parser_nodes
     * @param ?int $lineno
     * @param bool $return_null_on_empty (return null if non-array (E.g. semicolon is seen))
     * @return ?ast\Node
     */
    private static function phpParserStmtlistToAstNode($parser_nodes, $lineno, bool $return_null_on_empty = false)
    {
        if ($parser_nodes instanceof PhpParser\Node\Statement\CompoundStatementNode) {
            $parser_nodes = $parser_nodes->statements;
        } elseif ($parser_nodes instanceof PhpParser\Node\StatementNode) {
            if ($parser_nodes instanceof PhpParser\Node\Statement\EmptyStatement) {
                $parser_nodes = [];
            } else {
                $parser_nodes = [$parser_nodes];
            }
        } elseif ($parser_nodes instanceof Token) {
            if ($parser_nodes->kind === TokenKind::SemicolonToken) {
                if ($return_null_on_empty) {
                    return null;
                }
                return new ast\Node(
                    ast\AST_STMT_LIST,
                    0,
                    [],
                    $lineno ?? 0
                );
            }
        }

        if (!is_array($parser_nodes)) {
            throw new \RuntimeException("Unexpected type for statements: " . self::debugDumpNodeOrToken($parser_nodes));
        }
        $children = [];
        foreach ($parser_nodes as $parser_node) {
            try {
                $child_node = self::phpParserNodeToAstNode($parser_node);
            } catch (InvalidNodeException $e) {
                continue;
            }
            if (is_array($child_node)) {
                // Echo_ returns multiple children.
                foreach ($child_node as $child_node_part) {
                    $children[] = $child_node_part;
                }
            } elseif (!is_null($child_node)) {
                $children[] = $child_node;
            }
        }
        if (!is_int($lineno)) {
            foreach ($parser_nodes as $parser_node) {
                $child_node_line = self::getEndLine($parser_node);
                if ($child_node_line > 0) {
                    $lineno = $child_node_line;
                    break;
                }
            }
        }
        return new ast\Node(ast\AST_STMT_LIST, 0, $children, $lineno ?? 0);
    }

    private static function phpParserExprListToExprList(PhpParser\Node\DelimitedList\ExpressionList $expressions_list, int $lineno) : ast\Node
    {
        $children = [];
        $expressions_children = $expressions_list->children;
        foreach ($expressions_children as $expr) {
            if ($expr instanceof Token && $expr->kind === TokenKind::CommaToken) {
                continue;
            }
            $child_node = self::phpParserNodeToAstNode($expr);
            if (is_array($child_node)) {
                // Echo_ returns multiple children in php-ast
                foreach ($child_node as $child_node_part) {
                    $children[] = $child_node_part;
                }
            } elseif (!is_null($child_node)) {
                $children[] = $child_node;
            }
        }
        foreach ($expressions_children as $parser_node) {
            $child_node_line = self::getEndLine($parser_node);
            if ($child_node_line > 0) {
                $lineno = $child_node_line;
                break;
            }
        }
        return new ast\Node(
            ast\AST_EXPR_LIST,
            0,
            $children,
            $lineno
        );
    }

    /**
     * @param PhpParser\Node|Token $n - The node from PHP-Parser
     * @return ast\Node|ast\Node[]|string|int|float|bool|null - whatever ast\parse_code would return as the equivalent.
     */
    final private static function phpParserNonValueNodeToAstNode($n)
    {
        if (!($n instanceof PhpParser\Node) && !($n instanceof Token)) {
            throw new \InvalidArgumentException("Invalid type for node: " . (\is_object($n) ? \get_class($n) : \gettype($n)) . ": " . self::debugDumpNodeOrToken($n));
        }

        static $callback_map;
        static $fallback_closure;
        if (\is_null($callback_map)) {
            $callback_map = self::initHandleMap();
            /** @param PhpParser\Node|Token $n */
            $fallback_closure = function ($n, int $unused_start_line) {
                return self::astStub($n);
            };
        }
        $callback = $callback_map[\get_class($n)] ?? $fallback_closure;
        return $callback($n, self::getStartLine($n));
    }

    /**
     * @param PhpParser\Node|Token $n - The node from PHP-Parser
     * @return ast\Node|ast\Node[]|string|int|float|bool - whatever ast\parse_code would return as the equivalent.
     * @throws InvalidNodeException when self::$should_add_placeholders is false, like many of these methods.
     */
    final private static function phpParserNodeToAstNodeOrPlaceholderExpr($n)
    {
        if (!self::$should_add_placeholders) {
            return self::phpParserNodeToAstNode($n);
        }
        try {
            return self::phpParserNodeToAstNode($n);
        } catch (InvalidNodeException $_) {
            return self::newPlaceholderExpression($n);
        }
    }

    /**
     * @param PhpParser\Node|Token $n - The node from PHP-Parser
     * @return ast\Node|ast\Node[]|string|int|float|bool|null - whatever ast\parse_code would return as the equivalent.
     */
    final private static function phpParserNodeToAstNode($n)
    {
        if (!($n instanceof PhpParser\Node) && !($n instanceof Token)) {
            throw new \InvalidArgumentException("Invalid type for node: " . (\is_object($n) ? \get_class($n) : \gettype($n)) . ": " . self::debugDumpNodeOrToken($n));
        }

        static $callback_map;
        static $fallback_closure;
        if (\is_null($callback_map)) {
            $callback_map = self::initHandleMap();
            /** @param PhpParser\Node|Token $n */
            $fallback_closure = function ($n, int $unused_start_line) {
                return self::astStub($n);
            };
        }
        $callback = $callback_map[\get_class($n)] ?? $fallback_closure;
        $result = $callback($n, self::getStartLine($n));
        if (($result instanceof ast\Node) && $result->kind === ast\AST_NAME) {
            return new ast\Node(ast\AST_CONST, 0, ['name' => $result], $result->lineno);
        }
        return $result;
    }

    /**
     * @param PhpParser\Node|Token $n
     */
    private static function getStartLine($n) : int
    {
        // TODO: binary search in an array mapping line number to character offset?
        // Currently returns character offset.
        return self::$file_position_map->getStartLine($n);
    }

    /** @param ?PhpParser\Node|?Token $n */
    private static function getEndLine($n) : int
    {
        if (!$n) {
            return 0;
        }
        if (!\is_object($n)) {
            throw new \InvalidArgumentException("Invalid type passed to getEndLine: " . self::debugDumpNodeOrToken($n));
        }
        return self::$file_position_map->getEndLine($n);
    }

    /** @param ?PhpParser\Node|?Token $n */
    private static function getEndLineForNodeOrToken($n) : int
    {
        if (!$n) {
            return 0;
        }
        return self::$file_position_map->getEndLine($n);
    }

    /**
     * This returns an array of values mapping class names to the closures which converts them to a scalar or ast\Node
     *
     * Why not a switch? Switches are slow until php 7.2, and there are dozens of class names to handle.
     *
     * - In php <= 7.1, the interpreter would loop through all possible cases, and compare against the value one by one.
     * - There are a lot of local variables to look at.
     *
     * @return \Closure[]
     */
    private static function initHandleMap() : array
    {
        $closures = [
            /** @return ?ast\Node */
            'Microsoft\PhpParser\Node\SourceFileNode' => function (PhpParser\Node\SourceFileNode $n, int $start_line) {
                return self::phpParserStmtlistToAstNode($n->statementList, $start_line, false);
            },
            'Microsoft\PhpParser\Node\Expression\ArgumentExpression' => function (PhpParser\Node\Expression\ArgumentExpression $n, int $start_line) {
                // FIXME support foo(...$args)
                $result = self::phpParserNodeToAstNode($n->expression);
                if ($n->dotDotDotToken !== null) {
                    return new ast\Node(ast\AST_UNPACK, 0, ['expr' => $result], $start_line);
                }
                return $result;
            },
            'Microsoft\PhpParser\Node\Expression\SubscriptExpression' => function (PhpParser\Node\Expression\SubscriptExpression $n, int $start_line) : ast\Node {
                return new ast\Node(ast\AST_DIM, 0, [
                    'expr' => self::phpParserNodeToAstNode($n->postfixExpression),
                    'dim' => $n->accessExpression !== null ? self::phpParserNodeToAstNode($n->accessExpression) : null,
                ], $start_line);
            },
            /** @return ?ast\Node */
            'Microsoft\PhpParser\Node\Expression\AssignmentExpression' => function (PhpParser\Node\Expression\AssignmentExpression $n, int $start_line) {
                try {
                    $var_node = self::phpParserNodeToAstNode($n->leftOperand);
                } catch (InvalidNodeException $e) {
                    if (self::$should_add_placeholders) {
                        $var_node = new ast\Node(ast\AST_VAR, 0, ['name' => '__INCOMPLETE_VARIABLE__'], $start_line);
                    } else {
                        // convert `;= $b;` to `;$b;`
                        return self::phpParserNodeToAstNode($n->rightOperand);
                    }
                }
                $expr_node = self::phpParserNodeToAstNodeOrPlaceholderExpr($n->rightOperand);
                // FIXME switch on $n->kind
                return self::astNodeAssign(
                    $var_node,
                    $expr_node,
                    $start_line,
                    $n->byRef !== null
                );
            },
            'Microsoft\PhpParser\Node\Expression\BinaryExpression' => function (PhpParser\Node\Expression\BinaryExpression $n, int $start_line) : ast\Node {
                static $lookup = [
                    TokenKind::AmpersandAmpersandToken              => ast\flags\BINARY_BOOL_AND,
                    TokenKind::AmpersandToken                       => ast\flags\BINARY_BITWISE_AND,
                    TokenKind::AndKeyword                           => ast\flags\BINARY_BOOL_AND,
                    TokenKind::AsteriskAsteriskToken                => ast\flags\BINARY_POW,
                    TokenKind::AsteriskToken                        => ast\flags\BINARY_MUL,
                    TokenKind::BarBarToken                          => ast\flags\BINARY_BOOL_OR,
                    TokenKind::BarToken                             => ast\flags\BINARY_BITWISE_OR,
                    TokenKind::CaretToken                           => ast\flags\BINARY_BITWISE_XOR,
                    TokenKind::DotToken                             => ast\flags\BINARY_CONCAT,
                    TokenKind::EqualsEqualsEqualsToken              => ast\flags\BINARY_IS_IDENTICAL,
                    TokenKind::EqualsEqualsToken                    => ast\flags\BINARY_IS_EQUAL,
                    TokenKind::ExclamationEqualsEqualsToken         => ast\flags\BINARY_IS_NOT_IDENTICAL,
                    TokenKind::ExclamationEqualsToken               => ast\flags\BINARY_IS_NOT_EQUAL,
                    TokenKind::GreaterThanEqualsToken               => ast\flags\BINARY_IS_GREATER_OR_EQUAL,
                    TokenKind::GreaterThanGreaterThanToken          => ast\flags\BINARY_SHIFT_RIGHT,
                    TokenKind::GreaterThanToken                     => ast\flags\BINARY_IS_GREATER,
                    TokenKind::LessThanEqualsGreaterThanToken       => ast\flags\BINARY_SPACESHIP,
                    TokenKind::LessThanEqualsToken                  => ast\flags\BINARY_IS_SMALLER_OR_EQUAL,
                    TokenKind::LessThanLessThanToken                => ast\flags\BINARY_SHIFT_LEFT,
                    TokenKind::LessThanToken                        => ast\flags\BINARY_IS_SMALLER,
                    TokenKind::MinusToken                           => ast\flags\BINARY_SUB,
                    TokenKind::OrKeyword                            => ast\flags\BINARY_BOOL_OR,
                    TokenKind::PercentToken                         => ast\flags\BINARY_MOD,
                    TokenKind::PlusToken                            => ast\flags\BINARY_ADD,
                    TokenKind::QuestionQuestionToken                => ast\flags\BINARY_COALESCE,
                    TokenKind::SlashToken                           => ast\flags\BINARY_DIV,
                    TokenKind::XorKeyword                           => ast\flags\BINARY_BOOL_XOR,
                ];
                static $assign_lookup = [
                    TokenKind::AmpersandEqualsToken                 => \ast\flags\BINARY_BITWISE_AND,
                    TokenKind::AsteriskAsteriskEqualsToken          => \ast\flags\BINARY_POW,
                    TokenKind::AsteriskEqualsToken                  => \ast\flags\BINARY_MUL,
                    TokenKind::BarEqualsToken                       => \ast\flags\BINARY_BITWISE_OR,
                    TokenKind::CaretEqualsToken                     => \ast\flags\BINARY_BITWISE_XOR,
                    TokenKind::DotEqualsToken                       => \ast\flags\BINARY_CONCAT,
                    TokenKind::MinusEqualsToken                     => \ast\flags\BINARY_SUB,
                    TokenKind::PercentEqualsToken                   => \ast\flags\BINARY_MOD,
                    TokenKind::PlusEqualsToken                      => \ast\flags\BINARY_ADD,
                    TokenKind::SlashEqualsToken                     => \ast\flags\BINARY_DIV,
                    TokenKind::GreaterThanGreaterThanEqualsToken    => ast\flags\BINARY_SHIFT_RIGHT,
                    TokenKind::LessThanLessThanEqualsToken          => ast\flags\BINARY_SHIFT_LEFT,
                ];
                $kind = $n->operator->kind;
                if ($kind === TokenKind::InstanceOfKeyword) {
                    return new ast\Node(ast\AST_INSTANCEOF, 0, [
                        'expr'  => self::phpParserNodeToAstNode($n->leftOperand),
                        'class' => self::phpParserNonValueNodeToAstNode($n->rightOperand),
                    ], $start_line);
                }
                $ast_kind = $lookup[$kind] ?? null;
                if ($ast_kind === null) {
                    $ast_kind = $assign_lookup[$kind] ?? null;
                    assert($ast_kind !== null, "missing $kind (" . Token::getTokenKindNameFromValue($kind) . ")");
                    return self::astNodeAssignop($ast_kind, $n, $start_line);
                }
                return self::astNodeBinaryop($ast_kind, $n, $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\UnaryOpExpression' => function (PhpParser\Node\Expression\UnaryOpExpression $n, int $start_line) : ast\Node {
                static $lookup = [
                    TokenKind::TildeToken                   => ast\flags\UNARY_BITWISE_NOT,
                    TokenKind::MinusToken                   => ast\flags\UNARY_MINUS,
                    TokenKind::PlusToken                    => ast\flags\UNARY_PLUS,
                    TokenKind::ExclamationToken             => ast\flags\UNARY_BOOL_NOT,
                ];
                $kind = $n->operator->kind;
                $ast_kind = $lookup[$kind] ?? null;
                \assert($ast_kind !== null, "missing $kind(" . Token::getTokenKindNameFromValue($kind) . ")");
                return self::astNodeUnaryOp($ast_kind, self::phpParserNodeToAstNode($n->operand), $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\CastExpression' => function (PhpParser\Node\Expression\CastExpression $n, int $start_line) : ast\Node {
                static $lookup = [
                    TokenKind::ArrayCastToken   => ast\flags\TYPE_ARRAY,
                    TokenKind::BoolCastToken    => ast\flags\TYPE_BOOL,
                    TokenKind::DoubleCastToken  => ast\flags\TYPE_DOUBLE,
                    TokenKind::IntCastToken     => ast\flags\TYPE_LONG,
                    TokenKind::ObjectCastToken  => ast\flags\TYPE_OBJECT,
                    TokenKind::StringCastToken  => ast\flags\TYPE_STRING,
                    TokenKind::UnsetCastToken   => ast\flags\TYPE_NULL,
                ];
                $kind = $n->castType->kind;
                $ast_kind = $lookup[$kind] ?? null;
                assert($ast_kind !== null, "missing $kind");
                return self::astNodeCast($ast_kind, $n, $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\AnonymousFunctionCreationExpression' => function (PhpParser\Node\Expression\AnonymousFunctionCreationExpression $n, int $start_line) : ast\Node {
                $return_type_line = self::getEndLine($n->returnType) ?: $start_line;
                $return_type = self::phpParserTypeToAstNode($n->returnType, $return_type_line);
                if ($n->questionToken !== null) {
                    $return_type = new ast\Node(ast\AST_NULLABLE_TYPE, 0, ['type' => $return_type], $return_type_line);
                }
                return self::astDeclClosure(
                    $n->byRefToken !== null,
                    $n->staticModifier !== null,
                    self::phpParserParamsToAstParams($n->parameters, $start_line),
                    self::phpParserClosureUsesToAstClosureUses($n->anonymousFunctionUseClause->useVariableNameList ?? null, $start_line),
                    self::phpParserStmtlistToAstNode($n->compoundStatementOrSemicolon->statements, self::getStartLine($n->compoundStatementOrSemicolon), false),
                    $return_type,
                    $start_line,
                    self::getEndLine($n),
                    self::resolveDocCommentForClosure($n)
                );
                // FIXME: add a test of ClassQualifiedName to php-ast
            },
            /** @return ?ast\Node */
            'Microsoft\PhpParser\Node\Expression\ScopedPropertyAccessExpression' => function (PhpParser\Node\Expression\ScopedPropertyAccessExpression $n, int $start_line) {
                $member_name = $n->memberName;
                if ($member_name instanceof PhpParser\Node\Expression\Variable) {
                    return new ast\Node(
                        ast\AST_STATIC_PROP,
                        0,
                        [
                            'class' => self::phpParserNonValueNodeToAstNode($n->scopeResolutionQualifier),
                            'prop' => self::phpParserNodeToAstNode($member_name->name),
                        ],
                        $start_line
                    );
                } else {
                    \assert($member_name instanceof Token);
                    if (\get_class($member_name) !== Token::class) {
                        if (self::$should_add_placeholders) {
                            $member_name = '__INCOMPLETE_CLASS_CONST__';
                        } else {
                            throw new InvalidNodeException();
                        }
                    } else {
                        $member_name = self::tokenToString($member_name);
                    }
                    return self::phpParserClassconstfetchToAstClassconstfetch($n->scopeResolutionQualifier, $member_name, $start_line);
                }
            },
            'Microsoft\PhpParser\Node\Expression\CloneExpression' => function (PhpParser\Node\Expression\CloneExpression $n, int $start_line) : ast\Node {
                return new ast\Node(ast\AST_CLONE, 0, ['expr' => self::phpParserNodeToAstNode($n->expression)], $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\ErrorControlExpression' => function (PhpParser\Node\Expression\ErrorControlExpression $n, int $start_line) : ast\Node {
                return self::astNodeUnaryOp(ast\flags\UNARY_SILENCE, self::phpParserNodeToAstNode($n->operand), $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\EmptyIntrinsicExpression' => function (PhpParser\Node\Expression\EmptyIntrinsicExpression $n, int $start_line) : ast\Node {
                return new ast\Node(ast\AST_EMPTY, 0, ['expr' => self::phpParserNodeToAstNode($n->expression)], $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\EvalIntrinsicExpression' => function (PhpParser\Node\Expression\EvalIntrinsicExpression $n, int $start_line) : ast\Node {
                return self::astNodeEval(
                    self::phpParserNodeToAstNode($n->expression),
                    $start_line
                );
            },
            /** @return string|ast\Node */
            'Microsoft\PhpParser\Token' => function (PhpParser\Token $token, int $start_line) {
                $kind = $token->kind;
                $str = self::tokenToString($token);
                if ($kind === TokenKind::StaticKeyword) {
                    return new ast\Node(ast\AST_NAME, ast\flags\NAME_NOT_FQ, ['name' => $str], $start_line);
                }
                return $str;
            },
            /**
             * @throws InvalidNodeException
             */
            'Microsoft\PhpParser\MissingToken' => function (PhpParser\MissingToken $unused_node, int $_) {
                throw new InvalidNodeException();
            },
            /**
             * @throws InvalidNodeException
             */
            'Microsoft\PhpParser\SkippedToken' => function (PhpParser\SkippedToken $unused_node, int $_) {
                throw new InvalidNodeException();
            },
            'Microsoft\PhpParser\Node\Expression\ExitIntrinsicExpression' => function (PhpParser\Node\Expression\ExitIntrinsicExpression $n, int $start_line) {
                $expr_node = $n->expression !== null ? self::phpParserNodeToAstNode($n->expression) : null;
                return new ast\Node(ast\AST_EXIT, 0, ['expr' => $expr_node], $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\CallExpression' => function (PhpParser\Node\Expression\CallExpression $n, int $start_line) : ast\Node {
                $callable_expression = $n->callableExpression;
                $arg_list = self::phpParserArgListToAstArgList($n->argumentExpressionList, $start_line);
                if ($callable_expression instanceof PhpParser\Node\Expression\MemberAccessExpression) {  // $a->f()
                    return self::astNodeMethodCall(
                        self::phpParserNonValueNodeToAstNode($callable_expression->dereferencableExpression),
                        self::phpParserNodeToAstNode($callable_expression->memberName),
                        $arg_list,
                        $start_line
                    );
                } elseif ($callable_expression instanceof PhpParser\Node\Expression\ScopedPropertyAccessExpression) {  // a::f()
                    return self::astNodeStaticCall(
                        self::phpParserNonValueNodeToAstNode($callable_expression->scopeResolutionQualifier),
                        self::phpParserNodeToAstNode($callable_expression->memberName),
                        $arg_list,
                        $start_line
                    );
                } else {  // f()
                    return self::astNodeCall(
                        self::phpParserNonValueNodeToAstNode($callable_expression),
                        $arg_list,
                        $start_line
                    );
                }
            },
            'Microsoft\PhpParser\Node\Expression\ScriptInclusionExpression' => function (PhpParser\Node\Expression\ScriptInclusionExpression $n, int $start_line) : ast\Node {
                return self::astNodeInclude(
                    self::phpParserNodeToAstNode($n->expression),
                    $start_line,
                    $n->requireOrIncludeKeyword
                );
            },
            'Microsoft\PhpParser\Node\Expression\IssetIntrinsicExpression' => function (PhpParser\Node\Expression\IssetIntrinsicExpression $n, int $start_line) : ast\Node {
                $ast_issets = [];
                foreach ($n->expressions->children as $var) {
                    if ($var instanceof Token && $var->kind === TokenKind::CommaToken) {
                        continue;
                    }
                    $ast_issets[] = new ast\Node(ast\AST_ISSET, 0, [
                        'var' => self::phpParserNodeToAstNode($var),
                    ], $start_line);
                }
                $e = $ast_issets[0];
                for ($i = 1; $i < \count($ast_issets); $i++) {
                    $right = $ast_issets[$i];
                    $e = new ast\Node(
                        ast\AST_BINARY_OP,
                        ast\flags\BINARY_BOOL_AND,
                        [
                            'left' => $e,
                            'right' => $right,
                        ],
                        $e->lineno
                    );
                }
                return $e;
            },
            'Microsoft\PhpParser\Node\Expression\ArrayCreationExpression' => function (PhpParser\Node\Expression\ArrayCreationExpression $n, int $start_line) : ast\Node {
                return self::phpParserArrayToAstArray($n, $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\ListIntrinsicExpression' => function (PhpParser\Node\Expression\ListIntrinsicExpression $n, int $start_line) : ast\Node {
                return self::phpParserListToAstList($n, $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\ObjectCreationExpression' => function (PhpParser\Node\Expression\ObjectCreationExpression $n, int $start_line) : ast\Node {
                $end_line = self::getEndLine($n);
                $class_type_designator = $n->classTypeDesignator;
                if ($class_type_designator instanceof Token && $class_type_designator->kind === TokenKind::ClassKeyword) {
                    // Node of type AST_CLASS
                    $class_node = self::astStmtClass(
                        ast\flags\CLASS_ANONYMOUS,
                        null,
                        $n->classBaseClause !== null ? self::phpParserNonValueNodeToAstNode($n->classBaseClause->baseClass) : null,
                        $n->classInterfaceClause,
                        self::phpParserStmtlistToAstNode($n->classMembers->classMemberDeclarations ?? [], $start_line, false),
                        $start_line,
                        $end_line,
                        $n->getDocCommentText()
                    );
                } else {
                    $class_node = self::phpParserNonValueNodeToAstNode($class_type_designator);
                }
                return new ast\Node(ast\AST_NEW, 0, [
                    'class' => $class_node,
                    'args' => self::phpParserArgListToAstArgList($n->argumentExpressionList, $start_line),
                ], $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\ParenthesizedExpression' => function (PhpParser\Node\Expression\ParenthesizedExpression $n, int $_) {
                return self::phpParserNodeToAstNode($n->expression);
            },
            'Microsoft\PhpParser\Node\Expression\PrefixUpdateExpression' => function (PhpParser\Node\Expression\PrefixUpdateExpression $n, int $start_line) : ast\Node {
                switch ($n->incrementOrDecrementOperator->kind) {
                    case TokenKind::PlusPlusToken:
                        $type = ast\AST_PRE_INC;
                        break;
                    case TokenKind::MinusMinusToken:
                        $type = ast\AST_PRE_DEC;
                        break;
                    default:
                        throw new \RuntimeException('impossible operator ' . $n->incrementOrDecrementOperator->kind);
                }

                return new ast\Node($type, 0, ['var' => self::phpParserNodeToAstNode($n->operand)], $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\PostfixUpdateExpression' => function (PhpParser\Node\Expression\PostfixUpdateExpression $n, int $start_line) : ast\Node {
                switch ($n->incrementOrDecrementOperator->kind) {
                    case TokenKind::PlusPlusToken:
                        $type = ast\AST_POST_INC;
                        break;
                    case TokenKind::MinusMinusToken:
                        $type = ast\AST_POST_DEC;
                        break;
                    default:
                        throw new \RuntimeException('impossible operator ' . $n->incrementOrDecrementOperator->kind);
                }

                return new ast\Node($type, 0, ['var' => self::phpParserNodeToAstNode($n->operand)], $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\PrintIntrinsicExpression' => function (PhpParser\Node\Expression\PrintIntrinsicExpression $n, int $start_line) : ast\Node {
                return new ast\Node(
                    ast\AST_PRINT,
                    0,
                    ['expr' => self::phpParserNodeToAstNode($n->expression)],
                    $start_line
                );
            },
            /** @return ?ast\Node */
            'Microsoft\PhpParser\Node\Expression\MemberAccessExpression' => function (PhpParser\Node\Expression\MemberAccessExpression $n, int $start_line) {
                return self::phpParserMemberAccessExpressionToAstProp($n, $start_line);
            },
            'Microsoft\PhpParser\Node\Expression\TernaryExpression' => function (PhpParser\Node\Expression\TernaryExpression $n, int $start_line) : ast\Node {
                return new ast\Node(
                    ast\AST_CONDITIONAL,
                    0,
                    [
                        'cond' => self::phpParserNodeToAstNode($n->condition),
                        'true' => $n->ifExpression !== null ? self::phpParserNodeToAstNode($n->ifExpression) : null,
                        'false' => self::phpParserNodeToAstNode($n->elseExpression),
                    ],
                    $start_line
                );
            },
            /** @return ?ast\Node */
            'Microsoft\PhpParser\Node\Expression\Variable' => function (PhpParser\Node\Expression\Variable $n, int $start_line) {
                $name_node = $n->name;
                // Note: there are 2 different ways to handle an Error. 1. Add a placeholder. 2. remove all of the statements in that tree.
                if ($name_node instanceof PhpParser\Node) {
                    if ($name_node instanceof PhpParser\Node\Expression\Variable && $name_node->name instanceof PhpParser\MissingToken) {
                        // TODO remove after https://github.com/Microsoft/tolerant-php-parser/issues/188 is fixed (should be part of 0.0.10)
                        if (self::$should_add_placeholders) {
                            $name_node = '__INCOMPLETE_VARIABLE__';
                        } else {
                            throw new InvalidNodeException();
                        }
                    } else {
                        $name_node = self::phpParserNodeToAstNode($name_node);
                    }
                } elseif ($name_node instanceof Token) {
                    if ($name_node instanceof PhpParser\MissingToken) {
                        if (self::$should_add_placeholders) {
                            $name_node = '__INCOMPLETE_VARIABLE__';
                        } else {
                            throw new InvalidNodeException();
                        }
                    } else {
                        if ($name_node->kind === TokenKind::VariableName) {
                            $name_node = self::variableTokenToString($name_node);
                        } else {
                            $name_node = self::tokenToString($name_node);
                        }
                    }
                }
                return new ast\Node(ast\AST_VAR, 0, ['name' => $name_node], $start_line);
            },
            /**
             * @return ast\Node
             */
            'Microsoft\PhpParser\Node\Expression\BracedExpression' => function (PhpParser\Node\Expression\BracedExpression $n, int $_) {
                return self::phpParserNodeToAstNode($n->expression);
            },
            'Microsoft\PhpParser\Node\Expression\YieldExpression' => function (PhpParser\Node\Expression\YieldExpression $n, int $start_line) : ast\Node {
                switch ($n->yieldOrYieldFromKeyword->kind) {
                    case TokenKind::YieldKeyword:
                        $kind = ast\AST_YIELD;
                        break;
                    case TokenKind::YieldFromKeyword:
                        $kind = ast\AST_YIELD_FROM;
                        break;
                    default:
                        throw new \RuntimeException("Invalid yield expression kind {$n->yieldOrYieldFromKeyword->kind}");
                }
                $array_element = $n->arrayElement;
                $elementValue = $array_element->elementValue ?? null;
                // Workaround for <= 0.0.5
                $ast_expr = ($elementValue !== null && !($elementValue instanceof MissingToken)) ? self::phpParserNodeToAstNode($elementValue) : null;
                if ($kind === \ast\AST_YIELD) {
                    $children = [
                        'value' => $ast_expr,
                        'key' => ($array_element->elementKey ?? null) !== null ? self::phpParserNodeToAstNode($array_element->elementKey) : null,
                    ];
                } else {
                    $children = [
                        'expr' => $ast_expr,
                    ];
                }
                return new ast\Node(
                    $kind,
                    0,
                    $children,
                    $start_line
                );
            },
            'Microsoft\PhpParser\Node\ReservedWord' => function (PhpParser\Node\ReservedWord $n, int $start_line) : ast\Node {
                return new ast\Node(
                    ast\AST_NAME,
                    ast\flags\NAME_NOT_FQ,
                    ['name' => self::tokenToString($n->children)],
                    $start_line
                );
            },
            'Microsoft\PhpParser\Node\QualifiedName' => function (PhpParser\Node\QualifiedName $n, int $start_line) : ast\Node {
                return self::astNodeName($n, $start_line);
            },
            'Microsoft\PhpParser\Node\Parameter' => function (PhpParser\Node\Parameter $n, int $start_line) : ast\Node {
                $type_line = self::getEndLine($n->typeDeclaration) ?: $start_line;
                return self::astNodeParam(
                    $n->questionToken !== null,
                    $n->byRefToken !== null,
                    $n->dotDotDotToken !== null,
                    self::phpParserTypeToAstNode($n->typeDeclaration, $type_line),
                    self::variableTokenToString($n->variableName),
                    $n->default !== null ? self::phpParserNodeToAstNode($n->default) : null,
                    $start_line
                );
            },
            /** @return int|float */
            'Microsoft\PhpParser\Node\NumericLiteral' => function (PhpParser\Node\NumericLiteral $n, int $_) {
                $text = self::tokenToString($n->children);
                $as_int = filter_var($text, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_OCTAL | FILTER_FLAG_ALLOW_HEX);
                if ($as_int !== false) {
                    return $as_int;
                }
                return (float)$text;
            },
            /**
             * @return ast\Node|string
             */
            'Microsoft\PhpParser\Node\StringLiteral' => function (PhpParser\Node\StringLiteral $n, int $_) {
                $children = $n->children;
                if ($children instanceof Token) {
                    $inner_node = self::parseQuotedString($n);
                } elseif (\count($children) === 0) {
                    $inner_node = '';
                } elseif (\count($children) === 1 && $children[0] instanceof Token) {
                    $inner_node = self::parseQuotedString($n);
                } else {
                    $inner_node_parts = [];
                    foreach ($children as $part) {
                        if ($part instanceof PhpParser\Node) {
                            $inner_node_parts[] = self::phpParserNodeToAstNode($part);
                        } else {
                            $kind = $part->kind;
                            if (array_key_exists($kind, self::_IGNORED_STRING_TOKEN_KIND_SET)) {
                                continue;
                            }
                            // ($part->kind === TokenKind::EncapsedAndWhitespace)
                            $start_quote_text = self::tokenToString($n->startQuote);
                            $end_quote_text = self::tokenToString($n->endQuote);
                            $raw_string = self::tokenToRawString($part);

                            // Pass in '"\\n"' and get "\n" (somewhat inefficient)
                            $represented_string = String_::parse($start_quote_text . $raw_string . $end_quote_text);
                            $inner_node_parts[] = $represented_string;
                        }
                    }
                    $inner_node = new ast\Node(ast\AST_ENCAPS_LIST, 0, $inner_node_parts, self::getStartLine($children[0]));
                }
                if ($n->startQuote !== null && $n->startQuote->kind === TokenKind::BacktickToken) {
                    return new ast\Node(ast\AST_SHELL_EXEC, 0, ['expr' => $inner_node], self::getStartLine($children[0]));
                    // TODO: verify match
                }
                return $inner_node;
            },
            'Microsoft\PhpParser\Node\Statement\CompoundStatementNode' => function (PhpParser\Node\Statement\CompoundStatementNode $n, int $_) {
                $children = [];
                foreach ($n->statements as $parser_node) {
                    $child_node = self::phpParserNodeToAstNode($parser_node);
                    if (is_array($child_node)) {
                        // Echo_ returns multiple children.
                        foreach ($child_node as $child_node_part) {
                            $children[] = $child_node_part;
                        }
                    } elseif (!is_null($child_node)) {
                        $children[] = $child_node;
                    }
                }
                return $children;
            },
            /**
             * @return int|string|ast\Node|null
             * null if incomplete
             * int|string for no-op scalar statements like `;2;`
             */
            'Microsoft\PhpParser\Node\Statement\ExpressionStatement' => function (PhpParser\Node\Statement\ExpressionStatement $n, int $_) {
                return self::phpParserNodeToAstNode($n->expression);
            },
            /**
             * @suppress PhanTypeMismatchArgument incorrect phpdoc for breakoutLevel
             */
            'Microsoft\PhpParser\Node\Statement\BreakOrContinueStatement' => function (PhpParser\Node\Statement\BreakOrContinueStatement $n, int $start_line) : ast\Node {
                $raw_kind = $n->breakOrContinueKeyword->kind;
                switch ($raw_kind) {
                    case TokenKind::BreakKeyword:
                        $kind = ast\AST_BREAK;
                        break;
                    case TokenKind::ContinueKeyword:
                        $kind = ast\AST_CONTINUE;
                        break;
                    default:
                        throw new \InvalidArgumentException("Invalid BreakOrContinueStatement token $raw_kind");
                }
                return new ast\Node($kind, 0, ['depth' => isset($n->breakoutLevel) ? (int)self::phpParserNodeToAstNode($n->breakoutLevel) : null], $start_line);
            },
            'Microsoft\PhpParser\Node\CatchClause' => function (PhpParser\Node\CatchClause $n, int $start_line) : ast\Node {
                $catch_node = self::phpParserNonValueNodeToAstNode($n->qualifiedName);
                $catch_list_node = new ast\Node(ast\AST_NAME_LIST, 0, [$catch_node], $catch_node->lineno);
                // TODO: Change to handle multiple exception types in catch clauses
                // after https://github.com/Microsoft/tolerant-php-parser/issues/103 is supported
                return self::astStmtCatch(
                    $catch_list_node,
                    self::variableTokenToString($n->variableName),
                    self::phpParserStmtlistToAstNode($n->compoundStatement, $start_line, true),
                    $start_line
                );
            },
            'Microsoft\PhpParser\Node\Statement\InterfaceDeclaration' => function (PhpParser\Node\Statement\InterfaceDeclaration $n, int $start_line) : ast\Node {
                $end_line = self::getEndLine($n) ?: $start_line;
                return self::astStmtClass(
                    ast\flags\CLASS_INTERFACE,
                    self::tokenToString($n->name),
                    self::interfaceBaseClauseToNode($n->interfaceBaseClause),
                    null,
                    self::phpParserStmtlistToAstNode($n->interfaceMembers->interfaceMemberDeclarations ?? [], $start_line, false),
                    $start_line,
                    $end_line,
                    $n->getDocCommentText()
                );
            },
            'Microsoft\PhpParser\Node\Statement\ClassDeclaration' => function (PhpParser\Node\Statement\ClassDeclaration $n, int $start_line) : ast\Node {
                $end_line = self::getEndLine($n);
                return self::astStmtClass(
                    self::phpParserClassModifierToAstClassFlags($n->abstractOrFinalModifier),
                    self::tokenToString($n->name),
                    $n->classBaseClause !== null ? self::phpParserNonValueNodeToAstNode($n->classBaseClause->baseClass) : null,
                    $n->classInterfaceClause,
                    self::phpParserStmtlistToAstNode($n->classMembers->classMemberDeclarations ?? [], self::getStartLine($n->classMembers), false),
                    $start_line,
                    $end_line,
                    $n->getDocCommentText()
                );
            },
            'Microsoft\PhpParser\Node\Statement\TraitDeclaration' => function (PhpParser\Node\Statement\TraitDeclaration $n, int $start_line) : ast\Node {
                $end_line = self::getEndLine($n) ?: $start_line;
                return self::astStmtClass(
                    ast\flags\CLASS_TRAIT,
                    self::tokenToString($n->name),
                    null,
                    null,
                    self::phpParserStmtlistToAstNode($n->traitMembers->traitMemberDeclarations ?? [], self::getStartLine($n->traitMembers), false),
                    $start_line,
                    $end_line,
                    $n->getDocCommentText()
                );
            },
            'Microsoft\PhpParser\Node\ClassConstDeclaration' => function (PhpParser\Node\ClassConstDeclaration $n, int $start_line) : ast\Node {
                return self::phpParserClassConstToAstNode($n, $start_line);
            },
            'Microsoft\PhpParser\Node\MissingMemberDeclaration' => function (PhpParser\Node\MissingMemberDeclaration $unused_n, int $unused_start_line) {
                // This node type is generated for something that isn't a function/constant/property. e.g. "public example();"
                return null;
            },
            'Microsoft\PhpParser\Node\MethodDeclaration' => function (PhpParser\Node\MethodDeclaration $n, int $start_line) : ast\Node {
                $statements = $n->compoundStatementOrSemicolon;
                $return_type = self::phpParserTypeToAstNode($n->returnType, self::getEndLine($n->returnType) ?: $start_line);
                if ($n->questionToken !== null) {
                    $return_type = new ast\Node(ast\AST_NULLABLE_TYPE, 0, ['type' => $return_type], $start_line);
                }
                return self::newAstDecl(
                    ast\AST_METHOD,
                    self::phpParserVisibilityToAstVisibility($n->modifiers) | ($n->byRefToken !== null ? ast\flags\RETURNS_REF : 0),
                    [
                        'params' => self::phpParserParamsToAstParams($n->parameters, $start_line),
                        'uses' => null,
                        'stmts' => self::phpParserStmtlistToAstNode($statements, self::getStartLine($statements), true),
                        'returnType' => $return_type,
                    ],
                    $start_line,
                    $n->getDocCommentText(),
                    self::variableTokenToString($n->name),
                    self::getEndLine($n),
                    self::nextDeclId()
                );
            },
            'Microsoft\PhpParser\Node\Statement\ConstDeclaration' => function (PhpParser\Node\Statement\ConstDeclaration $n, int $start_line) : ast\Node {
                return self::phpParserConstToAstNode($n, $start_line);
            },
            'Microsoft\PhpParser\Node\Statement\DeclareStatement' => function (PhpParser\Node\Statement\DeclareStatement $n, int $start_line) : ast\Node {
                $doc_comment = $n->getDocCommentText();
                $directive = $n->declareDirective;
                assert($directive instanceof PhpParser\Node\DeclareDirective);
                return self::astStmtDeclare(
                    self::phpParserDeclareListToAstDeclares($directive, $start_line, $doc_comment),
                    $n->statements !== null ? self::phpParserStmtlistToAstNode($n->statements, $start_line, true) : null,
                    $start_line
                );
            },
            'Microsoft\PhpParser\Node\Statement\DoStatement' => function (PhpParser\Node\Statement\DoStatement $n, int $start_line) : ast\Node {
                return new ast\Node(
                    ast\AST_DO_WHILE,
                    0,
                    [
                        'stmts' => self::phpParserStmtlistToAstNode($n->statement, $start_line, false),
                        'cond' => self::phpParserNodeToAstNode($n->expression),
                    ],
                    $start_line
                );
            },
            /**
             * @return ast\Node|ast\Node[]
             */
            'Microsoft\PhpParser\Node\Expression\EchoExpression' => function (PhpParser\Node\Expression\EchoExpression $n, int $start_line) {
                $ast_echos = [];
                foreach ($n->expressions->children as $expr) {
                    if ($expr instanceof Token && $expr->kind === TokenKind::CommaToken) {
                        continue;
                    }
                    $ast_echos[] = new ast\Node(
                        ast\AST_ECHO,
                        0,
                        ['expr' => self::phpParserNodeToAstNode($expr)],
                        $start_line
                    );
                }
                return \count($ast_echos) === 1 ? $ast_echos[0] : $ast_echos;
            },
            'Microsoft\PhpParser\Node\ForeachKey' => function (PhpParser\Node\ForeachKey $n, int $_) : ast\Node {
                return self::phpParserNodeToAstNode($n->expression);
            },
            'Microsoft\PhpParser\Node\Statement\ForeachStatement' => function (PhpParser\Node\Statement\ForeachStatement $n, int $start_line) : ast\Node {
                $foreach_value = $n->foreachValue;
                $value = self::phpParserNodeToAstNode($foreach_value->expression);
                if ($foreach_value->ampersand) {
                    $value = new ast\Node(
                        ast\AST_REF,
                        0,
                        ['var' => $value],
                        $value->lineno ?? $start_line
                    );
                }
                return new ast\Node(
                    ast\AST_FOREACH,
                    0,
                    [
                        'expr' => self::phpParserNodeToAstNode($n->forEachCollectionName),
                        'value' => $value,
                        'key' => $n->foreachKey !== null ? self::phpParserNodeToAstNode($n->foreachKey) : null,
                        'stmts' => self::phpParserStmtlistToAstNode($n->statements, $start_line, true),
                    ],
                    $start_line
                );
                //return self::phpParserStmtlistToAstNode($n->statements, $start_line);
            },
            'Microsoft\PhpParser\Node\FinallyClause' => function (PhpParser\Node\FinallyClause $n, int $start_line) : ast\Node {
                return self::phpParserStmtlistToAstNode($n->compoundStatement, $start_line, false);
            },
            'Microsoft\PhpParser\Node\Statement\FunctionDeclaration' => function (PhpParser\Node\Statement\FunctionDeclaration $n, int $start_line) : ast\Node {
                $end_line = self::getEndLine($n) ?: $start_line;
                $return_type = $n->returnType;
                $return_type_line = ($return_type ? self::getEndLineForNodeOrToken($return_type) : 0) ?: $end_line;
                $ast_return_type = self::phpParserTypeToAstNode($return_type, $return_type_line);
                if ($n->questionToken !== null) {
                    $ast_return_type = new ast\Node(ast\AST_NULLABLE_TYPE, 0, ['type' => $ast_return_type], $return_type_line);
                }

                return self::astDeclFunction(
                    $n->byRefToken !== null,
                    self::tokenToString($n->name),
                    self::phpParserParamsToAstParams($n->parameters, $start_line),
                    $ast_return_type,
                    self::phpParserStmtlistToAstNode($n->compoundStatementOrSemicolon, self::getStartLine($n->compoundStatementOrSemicolon), false),
                    $start_line,
                    $end_line,
                    $n->getDocCommentText()
                );
            },
            /** @return ast\Node|ast\Node[] */
            'Microsoft\PhpParser\Node\Statement\GlobalDeclaration' => function (PhpParser\Node\Statement\GlobalDeclaration $n, int $start_line) {
                $global_nodes = [];
                foreach ($n->variableNameList->children ?? [] as $var) {
                    if ($var instanceof Token && $var->kind === TokenKind::CommaToken) {
                        continue;
                    }
                    $global_nodes[] = new ast\Node(ast\AST_GLOBAL, 0, ['var' => self::phpParserNodeToAstNode($var)], self::getEndLine($var) ?: $start_line);
                }
                return \count($global_nodes) === 1 ? $global_nodes[0] : $global_nodes;
            },
            'Microsoft\PhpParser\Node\Statement\IfStatementNode' => function (PhpParser\Node\Statement\IfStatementNode $n, int $start_line) : ast\Node {
                return self::phpParserIfStmtToAstIfStmt($n, $start_line);
            },
            /** @return ast\Node|ast\Node[] */
            'Microsoft\PhpParser\Node\Statement\InlineHtml' => function (PhpParser\Node\Statement\InlineHtml $n, int $start_line) {
                $text = $n->text;
                if ($text === null) {
                    return [];  // For the beginning/end of files
                }
                return new ast\Node(
                    ast\AST_ECHO,
                    0,
                    ['expr' =>self::tokenToRawString($n->text)],
                    $start_line
                );
            },
            /** @suppress PhanTypeMismatchArgument TODO: Make ForStatement have more accurate docs? */
            'Microsoft\PhpParser\Node\Statement\ForStatement' => function (PhpParser\Node\Statement\ForStatement $n, int $start_line) : ast\Node {
                return new ast\Node(
                    ast\AST_FOR,
                    0,
                    [
                        'init' => $n->forInitializer !== null ? self::phpParserExprListToExprList($n->forInitializer, $start_line) : null,
                        'cond' => $n->forControl !== null     ? self::phpParserExprListToExprList($n->forControl, $start_line) : null,
                        'loop' => $n->forEndOfLoop !== null   ? self::phpParserExprListToExprList($n->forEndOfLoop, $start_line) : null,
                        'stmts' => self::phpParserStmtlistToAstNode($n->statements, $start_line, true),
                    ],
                    $start_line
                );
            },
            /** @return ast\Node[] */
            'Microsoft\PhpParser\Node\Statement\NamespaceUseDeclaration' => function (PhpParser\Node\Statement\NamespaceUseDeclaration $n, int $start_line) {
                $useClauses = $n->useClauses;
                $results = [];
                $parserUseKind = $n->functionOrConst->kind ?? null;
                foreach ($useClauses->children as $useClause) {
                    \assert($useClause instanceof PhpParser\Node\NamespaceUseClause);
                    $namespace_name = \rtrim(self::phpParserNameToString($useClause->namespaceName), '\\');
                    if ($useClause->groupClauses !== null) {
                        $results[] = self::astStmtGroupUse(
                            $parserUseKind,  // E.g. kind is FunctionKeyword or ConstKeyword or null
                            $namespace_name,
                            self::phpParserNamespaceUseListToAstUseList($useClause->groupClauses->children ?? []),
                            $start_line
                        );
                    } else {
                        $alias_token = $useClause->namespaceAliasingClause->name ?? null;
                        $alias = $alias_token !== null ? self::tokenToString($alias_token) : null;
                        $results[] = self::astStmtUse($parserUseKind, $namespace_name, $alias, $start_line);
                    }
                }
                return $results;
            },
            'Microsoft\PhpParser\Node\Statement\NamespaceDefinition' => function (PhpParser\Node\Statement\NamespaceDefinition $n, int $start_line) : ast\Node {
                $stmt = $n->compoundStatementOrSemicolon;
                $name_node = $n->name;
                if ($stmt instanceof PhpParser\Node) {
                    $stmts_start_line = self::getStartLine($stmt);
                    $ast_stmt = self::phpParserStmtlistToAstNode($n->compoundStatementOrSemicolon, $stmts_start_line, true);
                    $start_line = $name_node !== null ? self::getStartLine($name_node) : $stmts_start_line;  // imitate php-ast
                } else {
                    $ast_stmt = null;
                }
                return new ast\Node(
                    ast\AST_NAMESPACE,
                    0,
                    [
                        'name' => $name_node !== null ? self::phpParserNameToString($name_node) : null,
                        'stmts' => $ast_stmt,
                    ],
                    $start_line
                );
            },
            'Microsoft\PhpParser\Node\Statement\EmptyStatement' => function (PhpParser\Node\Statement\EmptyStatement $unused_node, int $unused_start_line) : array {
                // `;;`
                return [];
            },
            'Microsoft\PhpParser\Node\PropertyDeclaration' => function (PhpParser\Node\PropertyDeclaration $n, int $start_line) : ast\Node {
                return self::phpParserPropertyToAstNode($n, $start_line);
            },
            'Microsoft\PhpParser\Node\Statement\ReturnStatement' => function (PhpParser\Node\Statement\ReturnStatement $n, int $start_line) : ast\Node {
                $expr_node = $n->expression !== null ? self::phpParserNodeToAstNode($n->expression) : null;
                return new ast\Node(ast\AST_RETURN, 0, ['expr' => $expr_node], $start_line);
            },
            /** @return ast\Node|ast\Node[] */
            'Microsoft\PhpParser\Node\Statement\FunctionStaticDeclaration' => function (PhpParser\Node\Statement\FunctionStaticDeclaration $n, int $start_line) {
                $static_nodes = [];
                foreach ($n->staticVariableNameList->children ?? [] as $var) {
                    if ($var instanceof Token) {
                        continue;
                    }
                    assert($var instanceof PhpParser\Node\StaticVariableDeclaration);  // FIXME error tolerance

                    $static_nodes[] = new ast\Node(ast\AST_STATIC, 0, [
                        'var' => new ast\Node(ast\AST_VAR, 0, ['name' => self::phpParserNodeToAstNode($var->variableName)], self::getEndLine($var) ?: $start_line),
                        'default' => $var->assignment !== null ? self::phpParserNodeToAstNode($var->assignment) : null,
                    ], self::getEndLine($var) ?: $start_line);
                }
                return \count($static_nodes) === 1 ? $static_nodes[0] : $static_nodes;
            },
            'Microsoft\PhpParser\Node\Statement\SwitchStatementNode' => function (PhpParser\Node\Statement\SwitchStatementNode $n, int $start_line) : ast\Node {
                return self::phpParserSwitchListToAstSwitch($n, $start_line);
            },
            'Microsoft\PhpParser\Node\Statement\ThrowStatement' => function (PhpParser\Node\Statement\ThrowStatement $n, int $start_line) : ast\Node {
                return new ast\Node(
                    ast\AST_THROW,
                    0,
                    ['expr' => self::phpParserNodeToAstNode($n->expression)],
                    $start_line
                );
            },

            'Microsoft\PhpParser\Node\TraitUseClause' => function (PhpParser\Node\TraitUseClause $n, int $start_line) : ast\Node {
                $clauses_list_node = $n->traitSelectAndAliasClauses;
                if ($clauses_list_node instanceof PhpParser\Node\DelimitedList\TraitSelectOrAliasClauseList) {
                    $adaptations_inner = [];
                    foreach ($clauses_list_node->children as $select_or_alias_clause) {
                        if ($select_or_alias_clause instanceof Token) {
                            continue;
                        }
                        assert($select_or_alias_clause instanceof PhpParser\Node\TraitSelectOrAliasClause);
                        $adaptations_inner[] = self::phpParserNodeToAstNode($select_or_alias_clause);
                    }
                    $adaptations = new ast\Node(ast\AST_TRAIT_ADAPTATIONS, 0, $adaptations_inner, $adaptations_inner[0]->lineno ?? $start_line);
                } else {
                    $adaptations = null;
                }
                return new ast\Node(
                    ast\AST_USE_TRAIT,
                    0,
                    [
                        'traits' => self::phpParserNameListToAstNameList($n->traitNameList->children ?? [], $start_line),
                        'adaptations' => $adaptations,
                    ],
                    $start_line
                );
            },

            'Microsoft\PhpParser\Node\TraitSelectOrAliasClause' => function (PhpParser\Node\TraitSelectOrAliasClause $n, int $start_line) : ast\Node {
                // FIXME targetName phpdoc is wrong.
                $name = $n->name;
                $target_name = $n->targetName;
                if ($n->asOrInsteadOfKeyword->kind === TokenKind::InsteadOfKeyword) {
                    $member_name_list = $name->memberName;
                    if (\is_object($member_name_list)) {
                        $member_name_list = [$member_name_list];
                    }
                    // Trait::y insteadof OtherTrait
                    $trait_node = self::phpParserNonValueNodeToAstNode($name->scopeResolutionQualifier);
                    $method_node = self::phpParserNameListToAstNameList($member_name_list, $start_line);
                    $target_node = self::phpParserNonValueNodeToAstNode($target_name);
                    $outer_method_node = new ast\Node(ast\AST_METHOD_REFERENCE, 0, [
                        'class' => $trait_node,
                        'method' => $method_node->children[0]
                    ], $start_line);
                    $target_node = new ast\Node(ast\AST_NAME_LIST, 0, [$target_node], $start_line);

                    assert(\count($member_name_list) === 1);  // TODO: can this be simplified?
                    $children = [
                        'method' => $outer_method_node,
                        'insteadof' => $target_node,
                    ];
                    return new ast\Node(ast\AST_TRAIT_PRECEDENCE, 0, $children, $start_line);
                } else {
                    if ($name instanceof PhpParser\Node\Expression\ScopedPropertyAccessExpression) {
                        $class_node = self::phpParserNonValueNodeToAstNode($name->scopeResolutionQualifier);
                        $method_node = self::phpParserNodeToAstNode($name->memberName);
                    } else {
                        $class_node = null;
                        $method_node = self::phpParserNameToString($name);
                    }
                    $flags = self::phpParserVisibilityToAstVisibility($n->modifiers, false);
                    $target_name = $target_name !== null ? self::phpParserNameToString($target_name) : null;
                    $children = [
                        'method' => new ast\Node(ast\AST_METHOD_REFERENCE, 0, [
                            'class' => $class_node,
                            'method' => $method_node,
                        ], $start_line),
                        'alias' => $target_name,
                    ];

                    return new ast\Node(ast\AST_TRAIT_ALIAS, $flags, $children, $start_line);
                }
            },
            'Microsoft\PhpParser\Node\Statement\TryStatement' => function (PhpParser\Node\Statement\TryStatement $n, int $start_line) : ast\Node {
                if (!is_array($n->catchClauses)) {
                    throw new \Error(sprintf("Unsupported type %s\n%s", get_class($n), self::debugDumpNodeOrToken($n->catchClauses)));
                }
                return self::astNodeTry(
                    self::phpParserStmtlistToAstNode($n->compoundStatement, $start_line, false), // $n->try
                    self::phpParserCatchlistToAstCatchlist($n->catchClauses, $start_line),
                    isset($n->finallyClause) ? self::phpParserStmtlistToAstNode($n->finallyClause->compoundStatement, self::getStartLine($n->finallyClause), false) : null,
                    $start_line
                );
            },
            /** @return ast\Node|ast\Node[] */
            'Microsoft\PhpParser\Node\Expression\UnsetIntrinsicExpression' => function (PhpParser\Node\Expression\UnsetIntrinsicExpression $n, int $start_line) {
                $stmts = [];
                foreach ($n->expressions->children as $var) {
                    if ($var instanceof Token) {
                        // Skip over ',' and invalid tokens
                        continue;
                    }
                    $stmts[] = new ast\Node(ast\AST_UNSET, 0, ['var' => self::phpParserNodeToAstNode($var)], self::getEndLine($var) ?: $start_line);
                }
                return \count($stmts) === 1 ? $stmts[0] : $stmts;
            },
            'Microsoft\PhpParser\Node\Statement\WhileStatement' => function (PhpParser\Node\Statement\WhileStatement $n, int $start_line) : ast\Node {
                return self::astNodeWhile(
                    self::phpParserNodeToAstNode($n->expression),
                    self::phpParserStmtlistToAstNode($n->statements, $start_line, true),
                    $start_line
                );
            },
            'Microsoft\PhpParser\Node\Statement\GotoStatement' => function (PhpParser\Node\Statement\GotoStatement $n, int $start_line) : ast\Node {
                return new ast\Node(ast\AST_GOTO, 0, ['label' => self::tokenToString($n->name)], $start_line);
            },
            /** @return ast\Node[] */
            'Microsoft\PhpParser\Node\Statement\NamedLabelStatement' => function (PhpParser\Node\Statement\NamedLabelStatement $n, int $start_line) {
                $label = new ast\Node(ast\AST_LABEL, 0, ['name' => self::tokenToString($n->name)], $start_line);
                $statement = self::phpParserNodeToAstNode($n->statement);
                return [$label, $statement];
            },
        ];

        foreach ($closures as $key => $_) {
            \assert(\class_exists($key), "Class $key should exist");
        }
        return $closures;
    }

    private static function astNodeTry(
        $try_node,
        $catches_node,
        $finally_node,
        int $start_line
    ) : ast\Node {
        // Return fields of $node->children in the same order as php-ast
        $children = [
            'try' => $try_node,
        ];
        if ($catches_node !== null) {
            $children['catches'] = $catches_node;
        }
        $children['finally'] = $finally_node;
        return new ast\Node(ast\AST_TRY, 0, $children, $start_line);
    }

    private static function astStmtCatch(ast\Node $types, string $var, $stmts, int $lineno) : ast\Node
    {
        return new ast\Node(
            ast\AST_CATCH,
            0,
            [
                'class' => $types,
                'var' => new ast\Node(ast\AST_VAR, 0, ['name' => $var], $lineno),
                'stmts' => $stmts,
            ],
            $lineno
        );
    }

    private static function phpParserCatchlistToAstCatchlist(array $catches, int $lineno) : ast\Node
    {
        $children = [];
        foreach ($catches as $parser_catch) {
            $children[] = self::phpParserNonValueNodeToAstNode($parser_catch);
        }
        return new ast\Node(ast\AST_CATCH_LIST, 0, $children, $children[0]->lineno ?? $lineno);
    }

    private static function phpParserNameListToAstNameList(array $types, int $line) : ast\Node
    {
        $ast_types = [];
        foreach ($types as $type) {
            if ($type instanceof Token && $type->kind === TokenKind::CommaToken) {
                continue;
            }
            $ast_types[] = self::phpParserNonValueNodeToAstNode($type);
        }
        return new ast\Node(ast\AST_NAME_LIST, 0, $ast_types, $line);
    }

    private static function astNodeWhile($cond, $stmts, int $start_line) : ast\Node
    {
        return new ast\Node(
            ast\AST_WHILE,
            0,
            [
                'cond' => $cond,
                'stmts' => $stmts,
            ],
            $start_line
        );
    }

    private static function astNodeAssign($var, $expr, int $line, bool $ref) : ast\Node
    {
        return new ast\Node(
            $ref ? ast\AST_ASSIGN_REF : ast\AST_ASSIGN,
            0,
            [
                'var'  => $var,
                'expr' => $expr,
            ],
            $line
        );
    }

    private static function astNodeUnaryOp(int $flags, $expr, int $line) : ast\Node
    {
        return new ast\Node(ast\AST_UNARY_OP, $flags, ['expr' => $expr], $line);
    }

    private static function astNodeCast(int $flags, PhpParser\Node\Expression\CastExpression $n, int $line) : ast\Node
    {
        return new ast\Node(ast\AST_CAST, $flags, ['expr' => self::phpParserNodeToAstNode($n->operand)], self::getEndLine($n) ?: $line);
    }

    private static function astNodeEval($expr, int $line) : ast\Node
    {
        return new ast\Node(ast\AST_INCLUDE_OR_EVAL, ast\flags\EXEC_EVAL, ['expr' => $expr], $line);
    }

    private static function phpParserIncludeTokenToAstIncludeFlags(Token $type) : int
    {
        $type_name = \strtolower(self::tokenToString($type));
        switch ($type_name) {
            case 'include':
                return ast\flags\EXEC_INCLUDE;
            case 'include_once':
                return ast\flags\EXEC_INCLUDE_ONCE;
            case 'require':
                return ast\flags\EXEC_REQUIRE;
            case 'require_once':
                return ast\flags\EXEC_REQUIRE_ONCE;
            default:
                throw new \Error("Unrecognized PhpParser include/require type $type_name");
        }
    }
    private static function astNodeInclude($expr, int $line, Token $type) : ast\Node
    {
        $flags = self::phpParserIncludeTokenToAstIncludeFlags($type);
        return new ast\Node(ast\AST_INCLUDE_OR_EVAL, $flags, ['expr' => $expr], $line);
    }

    /**
     * @param PhpParser\Node\QualifiedName|Token|null $type
     * @return ?ast\Node
     */
    private static function phpParserTypeToAstNode($type, int $line)
    {
        if (is_null($type)) {
            return null;
        }
        $original_type = $type;
        if ($type instanceof PhpParser\Node\QualifiedName) {
            $type = self::phpParserNameToString($type);
        } elseif ($type instanceof Token) {
            $type = self::tokenToString($type);
        }
        if (\is_string($type)) {
            switch (\strtolower($type)) {
                case 'null':
                    $flags = ast\flags\TYPE_NULL;
                    break;
                case 'bool':
                    $flags = ast\flags\TYPE_BOOL;
                    break;
                case 'int':
                    $flags = ast\flags\TYPE_LONG;
                    break;
                case 'float':
                    $flags = ast\flags\TYPE_DOUBLE;
                    break;
                case 'string':
                    $flags = ast\flags\TYPE_STRING;
                    break;
                case 'array':
                    $flags = ast\flags\TYPE_ARRAY;
                    break;
                case 'object':
                    $flags = ast\flags\TYPE_OBJECT;
                    break;
                case 'callable':
                    $flags = ast\flags\TYPE_CALLABLE;
                    break;
                case 'void':
                    $flags = ast\flags\TYPE_VOID;
                    break;
                case 'iterable':
                    $flags = ast\flags\TYPE_ITERABLE;
                    break;
                default:
                    // TODO: Refactor this into a function accepting a QualifiedName
                    if ($original_type->globalSpecifier !== null) {
                        $ast_kind = ast\flags\NAME_FQ;
                    } elseif (($original_type->relativeSpecifier->namespaceKeyword ?? null) !== null) {
                        $ast_kind = ast\flags\NAME_RELATIVE;
                    } else {
                        $ast_kind = ast\flags\NAME_NOT_FQ;
                    }
                    return new ast\Node(
                        ast\AST_NAME,
                        $ast_kind,
                        ['name' => $type],
                        $line
                    );
            }
            return new ast\Node(ast\AST_TYPE, $flags, [], $line);
        }
        return self::phpParserNodeToAstNode($type);
    }

    /**
     * @param bool $by_ref
     * @param ?ast\Node $type
     * @param string $name
     */
    private static function astNodeParam(bool $is_nullable, bool $by_ref, bool $variadic, $type, $name, $default, int $line) : ast\Node
    {
        if ($is_nullable) {
            $type = self::astNodeNullableType(
                $type,
                $line
            );
        }
        return new ast\Node(
            ast\AST_PARAM,
            ($by_ref ? ast\flags\PARAM_REF : 0) | ($variadic ? ast\flags\PARAM_VARIADIC : 0),
            [
                'type' => $type,
                'name' => $name,
                'default' => $default,
            ],
            $line
        );
    }

    private static function astNodeNullableType(ast\Node $type, int $line) : ast\Node
    {
        return new ast\Node(
            ast\AST_NULLABLE_TYPE,
            0,
            ['type' => $type],
            $line
        );
    }

    private static function astNodeName(PhpParser\Node\QualifiedName $name, int $line) : ast\Node
    {
        $name_parts = $name->nameParts;
        if (\count($name_parts) === 1) {
            $part = $name_parts[0];
            $imploded_parts = self::tokenToString($part);
            if ($part->kind === TokenKind::Name) {
                if (\preg_match('@__(LINE|FILE|DIR|FUNCTION|CLASS|TRAIT|METHOD|NAMESPACE)__@i', $imploded_parts) > 0) {
                    return new \ast\Node(
                        ast\AST_MAGIC_CONST,
                        self::_MAGIC_CONST_LOOKUP[\strtoupper($imploded_parts)],
                        [],
                        self::getStartLine($part)
                    );
                }
            }
        } else {
            // FIXME: skip over whitespace and \\
            $imploded_parts = self::phpParserNameToString($name);
        }
        if ($name->globalSpecifier !== null) {
            $ast_kind = ast\flags\NAME_FQ;
        } elseif (($name->relativeSpecifier->namespaceKeyword ?? null) !== null) {
            $ast_kind = ast\flags\NAME_RELATIVE;
        } else {
            $ast_kind = ast\flags\NAME_NOT_FQ;
        }
        return new ast\Node(ast\AST_NAME, $ast_kind, ['name' => $imploded_parts], $line);
    }

    /** @param ?PhpParser\Node\DelimitedList\ParameterDeclarationList $parser_params */
    private static function phpParserParamsToAstParams($parser_params, int $line) : ast\Node
    {
        $new_params = [];
        foreach ($parser_params->children ?? [] as $parser_node) {
            if ($parser_node instanceof Token) {
                continue;
            }
            $new_params[] = self::phpParserNodeToAstNode($parser_node);
        }
        return new ast\Node(
            ast\AST_PARAM_LIST,
            0,
            $new_params,
            $new_params[0]->lineno ?? $line
        );
    }

    /**
     * @suppress PhanTypeMismatchProperty - Deliberately setting $node->kind to a string instead of an integer.
     */
    private static function astStub($parser_node) : ast\Node
    {
        // Debugging code.
        if (\getenv(self::ENV_AST_THROW_INVALID)) {
            throw new \Error("TODO:" . get_class($parser_node));
        }

        $node = new ast\Node();
        $node->kind = "TODO:" . get_class($parser_node);
        $node->flags = 0;
        $node->lineno = self::getStartLine($parser_node);
        $node->children = [];
        return $node;
    }

    /**
     * @param ?PhpParser\Node\DelimitedList\UseVariableNameList $uses
     * @param int $line
     * @return ?ast\Node
     */
    private static function phpParserClosureUsesToAstClosureUses(
        $uses,
        int $line
    ) {
        if (count($uses->children ?? []) === 0) {
            return null;
        }
        $ast_uses = [];
        foreach ($uses->children as $use) {
            if ($use instanceof Token) {
                continue;
            }
            assert($use instanceof PhpParser\Node\UseVariableName);
            $ast_uses[] = new ast\Node(ast\AST_CLOSURE_VAR, $use->byRef ? 1 : 0, ['name' => self::tokenToString($use->variableName)], self::getStartLine($use));
        }
        return new ast\Node(ast\AST_CLOSURE_USES, 0, $ast_uses, $ast_uses[0]->lineno ?? $line);
    }

    /**
     * @return ?string
     */
    private static function resolveDocCommentForClosure(PhpParser\Node\Expression\AnonymousFunctionCreationExpression $node)
    {
        $doc_comment = $node->getDocCommentText();
        if ($doc_comment) {
            return $doc_comment;
        }
        for ($prev_node = $node; $node = $node->parent; $prev_node = $node) {
            if ($node instanceof PhpParser\Node\Expression\AssignmentExpression ||
                $node instanceof PhpParser\Node\Expression\ParenthesizedExpression ||
                $node instanceof PhpParser\Node\ArrayElement ||
                $node instanceof PhpParser\Node\Statement\ReturnStatement) {
                $doc_comment = $node->getDocCommentText();
                if ($doc_comment) {
                    return $doc_comment;
                }
                continue;
            }
            if ($node instanceof PhpParser\Node\Expression\ArgumentExpression) {
                // Skip ArgumentExpression and the PhpParser\Node\DelimitedList\ArgumentExpressionList
                // to get to the CallExpression
                $node = $node->parent->parent;
                // fall through
            }
            if ($node instanceof PhpParser\Node\Expression\MemberAccessExpression) {
                // E.g. ((Closure)->bindTo())
                if ($prev_node !== $node->dereferencableExpression) {
                    return null;
                }
                $doc_comment = $node->getDocCommentText();
                if ($doc_comment) {
                    return $doc_comment;
                }
                continue;
            }
            if ($node instanceof PhpParser\Node\Expression\CallExpression) {
                if ($prev_node === $node->callableExpression) {
                    $doc_comment = $node->getDocCommentText();
                    if ($doc_comment) {
                        return $doc_comment;
                    }
                    continue;
                }
                if ($node->callableExpression instanceof PhpParser\Node\Expression\AnonymousFunctionCreationExpression) {
                    return null;
                }
                $found = false;
                foreach ($node->argumentExpressionList->children ?? [] as $argument_expression) {
                    if (!($argument_expression instanceof PhpParser\Node\Expression\ArgumentExpression)) {
                        continue;
                    }
                    $expression = $argument_expression->expression;
                    if ($expression === $prev_node) {
                        $found = true;
                        $doc_comment = $node->getDocCommentText();
                        if ($doc_comment) {
                            return $doc_comment;
                        }
                        break;
                    }
                    if (!($expression instanceof PhpParser\Node)) {
                        continue;
                    }
                    if ($expression instanceof PhpParser\Node\ConstElement || $expression instanceof PhpParser\Node\NumericLiteral || $expression instanceof PhpParser\Node\StringLiteral) {
                        continue;
                    }
                    return null;
                }

                if ($found) {
                    continue;
                }
            }
            break;
        }
        return null;
    }

    /**
     * @param ?string $doc_comment
     */
    private static function astDeclClosure(
        bool $by_ref,
        bool $static,
        ast\Node $params,
        $uses,
        $stmts,
        $return_type,
        int $start_line,
        int $end_line,
        $doc_comment
    ) : ast\Node {
        return self::newAstDecl(
            ast\AST_CLOSURE,
            ($by_ref ? ast\flags\RETURNS_REF : 0) | ($static ? ast\flags\MODIFIER_STATIC : 0),
            [
                'params' => $params,
                'uses' => $uses,
                'stmts' => $stmts,
                'returnType' => $return_type,
            ],
            $start_line,
            $doc_comment,
            '{closure}',
            $end_line,
            self::nextDeclId()
        );
    }

    /**
     * @param ?ast\Node $return_type
     * @param ?ast\Node $stmts (TODO: create empty statement list instead of null)
     * @param ?string $doc_comment
     */
    private static function astDeclFunction(
        bool $by_ref,
        string $name,
        ast\Node $params,
        $return_type,
        $stmts,
        int $line,
        int $end_line,
        $doc_comment
    ) : ast\Node {
        return self::newAstDecl(
            ast\AST_FUNC_DECL,
            $by_ref ? ast\flags\RETURNS_REF : 0,
            [
                'params' => $params,
                'uses' => null,
                'stmts' => $stmts,
                'returnType' => $return_type,
            ],
            $line,
            $doc_comment,
            $name,
            $end_line,
            self::nextDeclId()
        );
    }

    /**
     * @param ?Token $flags
     */
    private static function phpParserClassModifierToAstClassFlags($flags) : int
    {
        if ($flags === null) {
            return 0;
        }
        switch ($flags->kind) {
            case TokenKind::AbstractKeyword:
                return ast\flags\CLASS_ABSTRACT;
            case TokenKind::FinalKeyword:
                return ast\flags\CLASS_FINAL;
            default:
                throw new \InvalidArgumentException("Unexpected kind '" . Token::getTokenKindNameFromValue($flags->kind) . "'");
        }
    }

    /**
     * @param ?PhpParser\Node\InterfaceBaseClause $node
     * @return ?ast\Node
     */
    private static function interfaceBaseClauseToNode($node)
    {
        if (!$node instanceof PhpParser\Node\InterfaceBaseClause) {
            // TODO: real placeholder?
            return null;
        }

        $interface_extends_name_list = [];
        foreach ($node->interfaceNameList->children ?? [] as $implement) {
            if ($implement instanceof Token && $implement->kind === TokenKind::CommaToken) {
                continue;
            }
            $interface_extends_name_list[] = self::phpParserNonValueNodeToAstNode($implement);
        }
        return new ast\Node(ast\AST_NAME_LIST, 0, $interface_extends_name_list, $interface_extends_name_list[0]->lineno);
    }

    /**
     * @param int $flags
     * @param ?string $name
     * @param ?ast\Node $extends
     * @param ?PhpParser\Node\ClassInterfaceClause $implements
     * @param ?ast\Node $stmts
     * @param int $line
     * @param int $end_line
     * @param ?string $doc_comment
     */
    private static function astStmtClass(
        int $flags,
        $name,
        $extends,
        $implements,
        $stmts,
        int $line,
        int $end_line,
        $doc_comment
    ) : ast\Node {
        if ($name === null) {
            $flags |= ast\flags\CLASS_ANONYMOUS;
        }

        if (($flags & ast\flags\CLASS_INTERFACE) > 0) {
            $children = [
                'extends'    => null,
                'implements' => $extends,
                'stmts'      => $stmts,
            ];
        } else {
            if ($implements !== null) {
                $ast_implements_inner = [];
                foreach ($implements->interfaceNameList->children ?? [] as $implement) {
                    // TODO: simplify?
                    if ($implement instanceof Token && $implement->kind === TokenKind::CommaToken) {
                        continue;
                    }
                    $ast_implements_inner[] = self::phpParserNonValueNodeToAstNode($implement);
                }
                if (\count($ast_implements_inner) > 0) {
                    $ast_implements = new ast\Node(ast\AST_NAME_LIST, 0, $ast_implements_inner, $ast_implements_inner[0]->lineno);
                } else {
                    $ast_implements = null;
                }
            } else {
                $ast_implements = null;
            }
            $children = [
                'extends'    => $extends,
                'implements' => $ast_implements,
                'stmts'      => $stmts,
            ];
        }

        return self::newAstDecl(
            ast\AST_CLASS,
            $flags,
            $children,
            $line,
            $doc_comment,
            $name,
            $end_line,
            self::nextDeclId()
        );
    }

    /**
     * @param ?PhpParser\Node\DelimitedList\ArgumentExpressionList $args
     */
    private static function phpParserArgListToAstArgList($args, int $line) : ast\Node
    {
        $ast_args = [];
        foreach ($args->children ?? [] as $arg) {
            if ($arg instanceof Token && $arg->kind === TokenKind::CommaToken) {
                continue;
            }
            $ast_args[] = self::phpParserNodeToAstNode($arg);
        }
        return new ast\Node(ast\AST_ARG_LIST, 0, $ast_args, $args ? self::getStartLine($args) : $line);
    }

    /**
     * @param ?int $kind
     */
    private static function phpParserNamespaceUseKindToASTUseFlags($kind) : int
    {
        switch ($kind ?? 0) {
            case TokenKind::FunctionKeyword:
                return ast\flags\USE_FUNCTION;
            case TokenKind::ConstKeyword:
                return ast\flags\USE_CONST;
            case 0:
                return ast\flags\USE_NORMAL;
            default:
                throw new \InvalidArgumentException("Unexpected kind '" . Token::getTokenKindNameFromValue($kind ?? 0) . "'");
        }
    }

    /**
     * @param Token[]|PhpParser\Node\NamespaceUseGroupClause[] $uses
     * @return ast\Node[]
     */
    private static function phpParserNamespaceUseListToAstUseList(array $uses) : array
    {
        $ast_uses = [];
        foreach ($uses as $use_clause) {
            if ($use_clause instanceof Token) {
                continue;
            }
            // ast doesn't fill in an alias if it's identical to the real name,
            // but phpParser does?
            $namespace_name = \rtrim(self::phpParserNameToString($use_clause->namespaceName), '\\');
            $alias_token = $use_clause->namespaceAliasingClause->name ?? null;
            $alias = $alias_token !== null ? self::tokenToString($alias_token) : null;

            $ast_uses[] = new ast\Node(
                ast\AST_USE_ELEM,
                self::phpParserNamespaceUseKindToASTUseFlags($use_clause->functionOrConst->kind ?? 0),
                [
                    'name' => $namespace_name,
                    'alias' => $alias !== $namespace_name ? $alias : null,
                ],
                self::getStartLine($use_clause)
            );
        }
        return $ast_uses;
    }

    /**
     * @param ?string $alias
     * @return ast\Node
     */
    private static function astStmtUse($type, string $name, $alias, int $line) : ast\Node
    {
        $use_inner = new ast\Node(ast\AST_USE_ELEM, 0, ['name' => $name, 'alias' => $alias], $line);
        return new ast\Node(
            ast\AST_USE,
            self::phpParserNamespaceUseKindToASTUseFlags($type),
            [$use_inner],
            $line
        );
    }

    /**
     * @param ?int $type
     * @param ?string $prefix
     */
    private static function astStmtGroupUse($type, $prefix, array $uses, int $line) : ast\Node
    {
        $flags = self::phpParserNamespaceUseKindToASTUseFlags($type);
        $uses = new ast\Node(ast\AST_USE, 0, $uses, $line);
        if ($flags === ast\flags\USE_NORMAL) {
            foreach ($uses->children as $use) {
                if ($use->flags !== 0) {
                    $flags = 0;
                    break;
                }
            }
        } else {
            foreach ($uses->children as $use) {
                if ($use->flags === ast\flags\USE_NORMAL) {
                    $use->flags = 0;
                }
            }
        }

        return new ast\Node(
            ast\AST_GROUP_USE,
            $flags,
            [
                'prefix' => $prefix,
                'uses' => $uses,
            ],
            $line
        );
    }

    private static function astIfElem($cond, $stmts, int $line) : ast\Node
    {
        return new ast\Node(ast\AST_IF_ELEM, 0, ['cond' => $cond, 'stmts' => $stmts], $line);
    }

    private static function phpParserSwitchListToAstSwitch(PhpParser\Node\Statement\SwitchStatementNode $node, int $start_line) : ast\Node
    {
        $stmts = [];
        $node_line = self::getEndLine($node) ?? $start_line;
        foreach ($node->caseStatements as $case) {
            $case_line = self::getEndLine($case);
            $stmts[] = new ast\Node(
                ast\AST_SWITCH_CASE,
                0,
                [
                    'cond' => $case->expression !== null ? self::phpParserNodeToAstNode($case->expression) : null,
                    'stmts' => self::phpParserStmtlistToAstNode($case->statementList, $case_line, false),
                ],
                $case_line ?? $node_line
            );
        }
        return new ast\Node(ast\AST_SWITCH, 0, [
            'cond' => self::phpParserNodeToAstNode($node->expression),
            'stmts' => new ast\Node(ast\AST_SWITCH_LIST, 0, $stmts, $stmts[0]->lineno ?? $node_line),
        ], $node_line);
    }

    private static function phpParserIfStmtToAstIfStmt(PhpParser\Node\Statement\IfStatementNode $node, int $start_line) : ast\Node
    {
        $start_line = self::getStartLine($node);
        $if_elem = self::astIfElem(
            self::phpParserNodeToAstNode($node->expression),
            self::phpParserStmtlistToAstNode($node->statements, self::getStartLine($node->statements), true),
            $start_line
        );
        $if_elems = [$if_elem];
        foreach ($node->elseIfClauses as $else_if) {
            $if_elem_line = self::getStartLine($else_if);
            $if_elem = self::astIfElem(
                self::phpParserNodeToAstNode($else_if->expression),
                self::phpParserStmtlistToAstNode($else_if->statements, self::getStartLine($else_if->statements)),
                $if_elem_line
            );
            $if_elems[] = $if_elem;
        }
        $parser_else_node = $node->elseClause;
        if ($parser_else_node) {
            $parser_else_line = self::getStartLine($parser_else_node->statements);
            $if_elems[] = self::astIfElem(
                null,
                self::phpParserStmtlistToAstNode($parser_else_node->statements, $parser_else_line),
                $parser_else_line
            );
        }
        return new ast\Node(ast\AST_IF, 0, $if_elems, $start_line);
    }

    private static function astNodeBinaryop(int $flags, PhpParser\Node\Expression\BinaryExpression $n, int $start_line) : \ast\Node
    {
        try {
            $left_node = self::phpParserNodeToAstNode($n->leftOperand);
        } catch (InvalidNodeException $e) {
            if (self::$should_add_placeholders) {
                $left_node = self::newPlaceholderExpression($n->leftOperand);
            } else {
                // convert `;$b ^;` to `;$b;`
                return self::phpParserNodeToAstNode($n->rightOperand);
            }
        }
        try {
            $right_node = self::phpParserNodeToAstNode($n->rightOperand);
        } catch (InvalidNodeException $e) {
            if (self::$should_add_placeholders) {
                $right_node = self::newPlaceholderExpression($n->rightOperand);
            } else {
                // convert `;^ $b;` to `;$b;`
                return $left_node;
            }
        }

        return new ast\Node(
            ast\AST_BINARY_OP,
            $flags,
            [
                'left' => $left_node,
                'right' => $right_node,
            ],
            $start_line
        );
    }

    // Binary assignment operation such as +=
    private static function astNodeAssignop(int $flags, PhpParser\Node\Expression\BinaryExpression $n, int $start_line) : \ast\Node
    {
        try {
            $var_node = self::phpParserNodeToAstNode($n->leftOperand);
        } catch (InvalidNodeException $e) {
            if (self::$should_add_placeholders) {
                $var_node = new ast\Node(ast\AST_VAR, 0, ['name' => '__INCOMPLETE_VARIABLE__'], $start_line);
            } else {
                // convert `;= $b;` to `;$b;`
                return self::phpParserNodeToAstNode($n->rightOperand);
            }
        }
        $expr_node = self::phpParserNodeToAstNode($n->rightOperand);
        return new ast\Node(
            ast\AST_ASSIGN_OP,
            $flags,
            [
                'var' => $var_node,
                'expr' => $expr_node,
            ],
            $start_line
        );
    }

    /**
     * @param PhpParser\Node\Expression\AssignmentExpression|PhpParser\Node\Expression\Variable $n
     * @param ?string $doc_comment
     */
    private static function phpParserPropelemToAstPropelem($n, $doc_comment) : ast\Node
    {
        if ($n instanceof PhpParser\Node\Expression\AssignmentExpression) {
            $name_node = $n->leftOperand;
            assert($name_node instanceof PhpParser\Node\Expression\Variable);
            $children = [
                'name' => self::phpParserNodeToAstNode($name_node->name),
                'default' => $n->rightOperand ? self::phpParserNodeToAstNode($n->rightOperand) : null,
            ];
        } elseif ($n instanceof PhpParser\Node\Expression\Variable) {
            $children = [
                'name' => self::tokenToString($n->name),
                'default' => null,
            ];
        } else {
            throw new \InvalidArgumentException("Unexpected class for property element: Expected Variable or AssignmentExpression, got: " . self::debugDumpNodeOrToken($n));
        }

        $start_line = self::getStartLine($n);

        $children['docComment'] = self::extractPhpdocComment($n) ?? $doc_comment;
        return new ast\Node(ast\AST_PROP_ELEM, 0, $children, $start_line);
    }

    /**
     * @param ?string $doc_comment
     */
    private static function phpParserConstelemToAstConstelem(PhpParser\Node\ConstElement $n, $doc_comment) : ast\Node
    {
        $start_line = self::getStartLine($n);
        $children = [
            'name' => self::variableTokenToString($n->name),
            'value' => self::phpParserNodeToAstNode($n->assignment),
        ];

        if (self::$php_version_id_parsing >= 70100 || self::$parse_all_doc_comments) {
            $children['docComment'] = self::extractPhpdocComment($n) ?? $doc_comment;
        }
        return new ast\Node(ast\AST_CONST_ELEM, 0, $children, $start_line);
    }

    /**
     * @param Token[] $visibility
     */
    private static function phpParserVisibilityToAstVisibility(array $visibility, bool $automatically_add_public = true) : int
    {
        $ast_visibility = 0;
        foreach ($visibility as $token) {
            switch ($token->kind) {
                case TokenKind::PublicKeyword:
                    $ast_visibility |= ast\flags\MODIFIER_PUBLIC;
                    break;
                case TokenKind::ProtectedKeyword:
                    $ast_visibility |= ast\flags\MODIFIER_PROTECTED;
                    break;
                case TokenKind::PrivateKeyword:
                    $ast_visibility |= ast\flags\MODIFIER_PRIVATE;
                    break;
                case TokenKind::StaticKeyword:
                    $ast_visibility |= ast\flags\MODIFIER_STATIC;
                    break;
                case TokenKind::AbstractKeyword:
                    $ast_visibility |= ast\flags\MODIFIER_ABSTRACT;
                    break;
                case TokenKind::FinalKeyword:
                    $ast_visibility |= ast\flags\MODIFIER_FINAL;
                    break;
                default:
                    throw new \RuntimeException("Unexpected visibility modifier '" . Token::getTokenKindNameFromValue($token->kind) . "'");
            }
        }
        if ($automatically_add_public && !($ast_visibility & (ast\flags\MODIFIER_PUBLIC|ast\flags\MODIFIER_PROTECTED|ast\flags\MODIFIER_PRIVATE))) {
            $ast_visibility |= ast\flags\MODIFIER_PUBLIC;
        }
        return $ast_visibility;
    }

    /**
     * @suppress PhanTypeMismatchArgument casting to a more specific node
     */
    private static function phpParserPropertyToAstNode(PhpParser\Node\PropertyDeclaration $n, int $start_line) : ast\Node
    {
        $prop_elems = [];
        $doc_comment = $n->getDocCommentText();

        foreach ($n->propertyElements->children ?? [] as $i => $prop) {
            if ($prop instanceof Token) {
                continue;
            }
            $prop_elems[] = self::phpParserPropelemToAstPropelem($prop, $i === 0 ? $doc_comment : null);
        }
        $flags = self::phpParserVisibilityToAstVisibility($n->modifiers, false);

        return new ast\Node(ast\AST_PROP_DECL, $flags, $prop_elems, $prop_elems[0]->lineno ?? (self::getStartLine($n) ?: $start_line));
    }

    /**
     * @suppress PhanTypeMismatchArgument casting to something more specific
     */
    private static function phpParserClassConstToAstNode(PhpParser\Node\ClassConstDeclaration $n, int $start_line) : ast\Node
    {
        $const_elems = [];
        $doc_comment = $n->getDocCommentText();
        foreach ($n->constElements->children ?? [] as $i => $const_elem) {
            if ($const_elem instanceof Token) {
                continue;
            }
            $const_elems[] = self::phpParserConstelemToAstConstelem($const_elem, $i === 0 ? $doc_comment : null);
        }
        $flags = self::phpParserVisibilityToAstVisibility($n->modifiers);

        return new ast\Node(ast\AST_CLASS_CONST_DECL, $flags, $const_elems, $const_elems[0]->lineno ?? $start_line);
    }

    private static function phpParserConstToAstNode(PhpParser\Node\Statement\ConstDeclaration $n, int $start_line) : ast\Node
    {
        $const_elems = [];
        $doc_comment = $n->getDocCommentText();
        foreach ($n->constElements->children as $i => $prop) {
            if ($prop instanceof Token) {
                continue;
            }
            assert($prop instanceof PhpParser\Node\ConstElement);
            $const_elems[] = self::phpParserConstelemToAstConstelem($prop, $i === 0 ? $doc_comment : null);
        }

        return new ast\Node(ast\AST_CONST_DECL, 0, $const_elems, $const_elems[0]->lineno ?? $start_line);
    }

    /**
     * @param ?string $first_doc_comment
     */
    private static function phpParserDeclareListToAstDeclares(PhpParser\Node\DeclareDirective $declare, int $start_line, $first_doc_comment) : ast\Node
    {
        $ast_declare_elements = [];
        $children = [
            'name' => self::tokenToString($declare->name),
            'value' => self::tokenToScalar($declare->literal),
        ];
        $doc_comment = self::extractPhpdocComment($declare) ?? $first_doc_comment;
        // $first_doc_comment = null;
        if (self::$php_version_id_parsing >= 70100 || self::$parse_all_doc_comments) {
            $children['docComment'] = $doc_comment;
        }
        $node = new ast\Node(ast\AST_CONST_ELEM, 0, $children, self::getStartLine($declare));
        $ast_declare_elements[] = $node;
        return new ast\Node(ast\AST_CONST_DECL, 0, $ast_declare_elements, $start_line);
    }

    /**
     * @param ?ast\Node $stmts
     */
    private static function astStmtDeclare(ast\Node $declares, $stmts, int $start_line) : ast\Node
    {
        $children = [
            'declares' => $declares,
            'stmts' => $stmts,
        ];
        return new ast\Node(ast\AST_DECLARE, 0, $children, $start_line);
    }

    private static function astNodeCall($expr, $args, int $start_line) : ast\Node
    {
        if (\is_string($expr)) {
            if (substr($expr, 0, 1) === '\\') {
                $expr = substr($expr, 1);
            }
            $expr = new ast\Node(ast\AST_NAME, ast\flags\NAME_FQ, ['name' => $expr], $start_line);
        }
        return new ast\Node(ast\AST_CALL, 0, ['expr' => $expr, 'args' => $args], $start_line);
    }

    /**
     * @param ast\Node $expr
     * @param ast\Node $method
     */
    private static function astNodeMethodCall($expr, $method, ast\Node $args, int $start_line) : ast\Node
    {
        return new ast\Node(ast\AST_METHOD_CALL, 0, ['expr' => $expr, 'method' => $method, 'args' => $args], $start_line);
    }

    /**
     * @param ast\Node|string $class
     * @param ast\Node $method
     */
    private static function astNodeStaticCall($class, $method, ast\Node $args, int $start_line) : ast\Node
    {
        // TODO: is this applicable?
        if (\is_string($class)) {
            if (\substr($class, 0, 1) === '\\') {
                $class = \substr($class, 1);
            }
            $class = new ast\Node(ast\AST_NAME, ast\flags\NAME_FQ, ['name' => $class], $start_line);
        }
        return new ast\Node(ast\AST_STATIC_CALL, 0, ['class' => $class, 'method' => $method, 'args' => $args], $start_line);
    }

    /**
     * TODO: Get rid of this function?
     * @return ?string the doc comment, or null
     */
    private static function extractPhpdocComment($comments)
    {
        if (\is_string($comments)) {
            return $comments;
        }
        if ($comments instanceof PhpParser\Node) {
            // TODO: Extract only the substring with doc comment text?
            return $comments->getDocCommentText() ?: null;
        }
        if ($comments === null) {
            return null;
        }
        assert(\is_array($comments));
        if (\count($comments) === 0) {
            return null;
        }
    }

    /**
     * @suppress PhanPluginUnusedVariable $prev_was_element
     */
    private static function phpParserListToAstList(PhpParser\Node\Expression\ListIntrinsicExpression $n, int $start_line) : ast\Node
    {
        $ast_items = [];
        $prev_was_element = false;
        foreach ($n->listElements->children ?? [] as $item) {
            if ($item instanceof Token) {
                if (!$prev_was_element) {
                    $ast_items[] = null;
                    continue;
                }
                $prev_was_element = false;
                continue;
            } else {
                $prev_was_element = true;
            }
            assert($item instanceof PhpParser\Node\ArrayElement);
            $ast_items[] = new ast\Node(ast\AST_ARRAY_ELEM, 0, [
                'value' => self::phpParserNodeToAstNode($item->elementValue),
                'key' => $item->elementKey !== null ? self::phpParserNodeToAstNode($item->elementKey) : null,
            ], self::getStartLine($item));
        }
        if (self::$php_version_id_parsing < 70100 && \count($ast_items) === 0) {
            $ast_items[] = null;
        }
        return new ast\Node(ast\AST_ARRAY, ast\flags\ARRAY_SYNTAX_LIST, $ast_items, $start_line);
    }

    /**
     * @suppress PhanPluginUnusedVariable $prev_was_element
     */
    private static function phpParserArrayToAstArray(PhpParser\Node\Expression\ArrayCreationExpression $n, int $start_line) : ast\Node
    {
        $ast_items = [];
        $prev_was_element = false;
        foreach ($n->arrayElements->children ?? [] as $item) {
            if ($item instanceof Token) {
                if (!$prev_was_element) {
                    $ast_items[] = null;
                    continue;
                }
                $prev_was_element = false;
                continue;
            } else {
                $prev_was_element = true;
            }
            assert($item instanceof PhpParser\Node\ArrayElement);
            $flags = $item->byRef ? ast\flags\PARAM_REF : 0;
            $ast_items[] = new ast\Node(ast\AST_ARRAY_ELEM, $flags, [
                'value' => self::phpParserNodeToAstNode($item->elementValue),
                'key' => $item->elementKey !== null ? self::phpParserNodeToAstNode($item->elementKey) : null,
            ], self::getStartLine($item));
        }
        if (self::$php_version_id_parsing < 70100) {
            $flags = 0;
        } else {
            $kind = $n->openParenOrBracket->kind;
            if ($kind === TokenKind::OpenBracketToken) {
                $flags = ast\flags\ARRAY_SYNTAX_SHORT;
            } else {
                $flags = ast\flags\ARRAY_SYNTAX_LONG;
            }
        }
        // Workaround for ast line choice
        return new ast\Node(ast\AST_ARRAY, $flags, $ast_items, $ast_items[0]->lineno ?? $start_line);
    }

    /** @return ?ast\Node */
    private static function phpParserMemberAccessExpressionToAstProp(PhpParser\Node\Expression\MemberAccessExpression $n, int $start_line)
    {
        // TODO: Check for incomplete tokens?
        $member_name = $n->memberName;
        try {
            $name = self::phpParserNodeToAstNode($member_name);  // complex expression
        } catch (InvalidNodeException $e) {
            if (self::$should_add_placeholders) {
                $name = '__INCOMPLETE_PROPERTY__';
            } else {
                throw $e;
            }
        }
        return new ast\Node(ast\AST_PROP, 0, [
            'expr'  => self::phpParserNodeToAstNode($n->dereferencableExpression),
            'prop'  => $name,  // ast\Node|string
        ], $start_line);
    }

    /**
     * @return int|string|float|bool|null
     */
    private static function tokenToScalar(Token $n)
    {
        $str = self::tokenToString($n);
        $int = \filter_var($str, FILTER_VALIDATE_INT);
        if ($int !== false) {
            return $int;
        }
        $float = \filter_var($str, FILTER_VALIDATE_FLOAT);
        if ($float !== false) {
            return $float;
        }

        return String_::parse($str);
    }

    private static function parseQuotedString(PhpParser\Node\StringLiteral $n) : string
    {
        $start = $n->getStart();
        $text = \substr(self::$file_contents, $start, $n->getEndPosition() - $start);
        return String_::parse($text);
    }

    /**
     * @suppress PhanPartialTypeMismatchArgumentInternal hopefully in range
     */
    private static function variableTokenToString(Token $n) : string
    {
        return \ltrim(\trim($n->getText(self::$file_contents)), '$');
    }

    private static function tokenToRawString(Token $n) : string
    {
        return $n->getText(self::$file_contents);
    }

    /** @internal */
    const _MAGIC_CONST_LOOKUP = [
        '__LINE__' => \ast\flags\MAGIC_LINE,
        '__FILE__' => \ast\flags\MAGIC_FILE,
        '__DIR__' => \ast\flags\MAGIC_DIR,
        '__NAMESPACE__' => \ast\flags\MAGIC_NAMESPACE,
        '__FUNCTION__' => \ast\flags\MAGIC_FUNCTION,
        '__METHOD__' => \ast\flags\MAGIC_METHOD,
        '__CLASS__' => \ast\flags\MAGIC_CLASS,
        '__TRAIT__' => \ast\flags\MAGIC_TRAIT,
    ];

    // FIXME don't use in places expecting non-strings.
    /**
     * @phan-suppress PhanPartialTypeMismatchArgumentInternal hopefully in range
     */
    private static function tokenToString(Token $n) : string
    {
        $result = \trim($n->getText(self::$file_contents));
        $kind = $n->kind;
        if ($kind === TokenKind::VariableName) {
            return \trim($result, '$');
        }
        return $result;
    }

    /**
     * @param PhpParser\Node\Expression|PhpParser\Node\QualifiedName|Token $scope_resolution_qualifier
     */
    private static function phpParserClassconstfetchToAstClassconstfetch($scope_resolution_qualifier, string $name, int $start_line) : ast\Node
    {
        return new ast\Node(ast\AST_CLASS_CONST, 0, [
            'class' => self::phpParserNonValueNodeToAstNode($scope_resolution_qualifier),
            'const' => $name,
        ], $start_line);
    }


    /**
     * @return string
     */
    private static function phpParserNameToString(PhpParser\Node\QualifiedName $name) : string
    {
        $nameParts = $name->nameParts;
        // TODO: Handle error case (can there be missing parts?)
        $result = '';
        foreach ($nameParts as $part) {
            $result .= \trim(self::tokenToString($part));
        }
        return $result;
    }

    /**
     * NOTE: this may be removed in the future.
     *
     * @return ast\Node
     */
    private static function newAstDecl(int $kind, int $flags, array $children, int $lineno, string $doc_comment = null, string $name = null, int $end_lineno = 0, int $decl_id = -1) : ast\Node
    {
        $children50 = [];
        $children50['name'] = $name;
        $children50['docComment'] = $doc_comment;
        $children50 += $children;
        if ($decl_id >= 0) {
            $children50['__declId'] = $decl_id;
        }
        $node = new ast\Node($kind, $flags, $children50, $lineno);
        if (is_int($end_lineno)) {
            $node->endLineno = $end_lineno;
        }
        return $node;
    }

    private static function nextDeclId() : int
    {
        return self::$decl_id++;
    }

    /** @param PhpParser\Node|Token $n the node or token to convert to a placeholder */
    private static function newPlaceholderExpression($n) : ast\Node
    {
        $start_line = self::getStartLine($n);
        $name_node = new ast\Node(ast\AST_NAME, ast\flags\NAME_FQ, ['name' => '__INCOMPLETE_EXPR__'], $start_line);
        return new ast\Node(ast\AST_CONST, 0, ['name' => $name_node], $start_line);
    }
}
<?php declare(strict_types=1);

namespace Phan\AST\TolerantASTConverter;

use Exception;

/**
 * @internal thrown when processing something that would become an invalid variable.
 */
final class InvalidNodeException extends Exception
{
}
<?php declare(strict_types=1);
namespace Phan;

use Phan\AST\AnalysisVisitor;
use Phan\AST\Visitor\Element;
use Phan\AST\UnionTypeVisitor;
use Phan\Analysis\BlockExitStatusChecker;
use Phan\Analysis\ConditionVisitor;
use Phan\Analysis\NegatedConditionVisitor;
use Phan\Analysis\ContextMergeVisitor;
use Phan\Analysis\PostOrderAnalysisVisitor;
use Phan\Analysis\PreOrderAnalysisVisitor;
use Phan\Language\Context;
use Phan\Language\Element\Comment;
use Phan\Language\Element\Variable;
use Phan\Language\FQSEN\FullyQualifiedPropertyName;
use Phan\Language\Type;
use Phan\Language\UnionType;
use Phan\Language\Scope\BranchScope;
use Phan\Language\Scope\GlobalScope;
use Phan\Language\Scope\PropertyScope;
use Phan\Plugin\ConfigPluginSet;
use ast\Node;

/**
 * Analyze blocks of code
 *
 * - Uses `\Phan\Analysis\PreOrderAnalysisVisitor` for pre-order analysis of a node (E.g. entering a function to analyze)
 * - Recursively analyzes child nodes
 * - Uses `\Phan\Analysis\PostOrderAnalysisVisitor` for post-order analysis of a node (E.g. analyzing a statement with the updated Context and emitting issues)
 * - If there is more than one possible child context, merges state from them (variable types)
 *
 * @see $this->visit
 *
 * @phan-file-suppress PhanPartialTypeMismatchArgument
 */
class BlockAnalysisVisitor extends AnalysisVisitor
{

    /**
     * @var Node[]
     * The parent of the current node
     */
    private $parent_node_list = [];

    /**
     * @param CodeBase $code_base
     * The code base within which we're operating
     *
     * @param Context $context
     * The context of the parser at the node for which we'd
     * like to determine a type
     *
     * @param ?Node $parent_node
     * The parent of the node being analyzed
     */
    public function __construct(
        CodeBase $code_base,
        Context $context,
        Node $parent_node = null
    ) {
        parent::__construct($code_base, $context);
        if ($parent_node) {
            $this->parent_node_list[] = $parent_node;
        }
    }

    // No-ops for frequent node types
    public function visitVar(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        // Let any configured plugins do a pre-order
        // analysis of the node.
        ConfigPluginSet::instance()->preAnalyzeNode(
            $this->code_base,
            $context,
            $node
        );

        \assert(!empty($context), 'Context cannot be null');

        // With a context that is inside of the node passed
        // to this method, we analyze all children of the
        // node.
        $name_node = $node->children['name'];
        // E.g. ${expr()} is valid PHP. Recurse if that's a node.
        if ($name_node instanceof Node) {
            // Step into each child node and get an
            // updated context for the node
            $context = $this->analyzeAndGetUpdatedContext($context, $node, $name_node);
        }

        $context = $this->postOrderAnalyze($context, $node);

        return $context;
    }

    /**
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function visitUseElem(Node $node) : Context
    {
        // Could invoke plugins, but not right now
        return $this->context;
    }

    /**
     * @suppress PhanAccessMethodInternal
     */
    public function visitNamespace(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        // If there are multiple namespaces in the file, have to warn about unused entries in the current namespace first.
        // If this is the first namespace, then there wouldn't be any use statements yet.
        $context->warnAboutUnusedUseElements($this->code_base);

        // Visit the given node populating the code base
        // with anything we learn and get a new context
        // indicating the state of the world within the
        // given node
        $context = (new PreOrderAnalysisVisitor(
            $this->code_base,
            $context
        ))->visitNamespace($node);

        \assert(!empty($context), 'Context cannot be null');

        // We already imported namespace constants earlier; use those.
        $context->importNamespaceMapFromParsePhase($this->code_base);

        // Let any configured plugins do a pre-order
        // analysis of the node.
        ConfigPluginSet::instance()->preAnalyzeNode(
            $this->code_base,
            $context,
            $node
        );

        // The namespace may either have a list of statements (`namespace Foo {}`)
        // or be null (`namespace Foo;`)
        $stmts_node = $node->children['stmts'];
        if ($stmts_node instanceof Node) {
            assert($stmts_node->kind === \ast\AST_STMT_LIST);
            $context = $this->analyzeAndGetUpdatedContext($context, $node, $stmts_node);
        }

        return $this->postOrderAnalyze($context, $node);
    }

    /**
     * @suppress PhanPluginUnusedPublicMethodArgument
     */
    public function visitName(Node $node) : Context
    {
        // Could invoke plugins, but not right now
        return $this->context;
    }

    public function visitStmtList(Node $node) : Context
    {
        $context = $this->context;
        $plugin_set = ConfigPluginSet::instance();
        $plugin_set->preAnalyzeNode(
            $this->code_base,
            $context,
            $node
        );
        foreach ($node->children as $child_node) {
            // Skip any non Node children.
            if (!($child_node instanceof Node)) {
                if (\is_string($child_node) && \strpos($child_node, '@phan-') !== false) {
                    // Add @phan-var and @phan-suppress annotations in string literals to the local scope
                    $this->analyzeSubstituteVarAssert($this->code_base, $context, $child_node);
                }
                continue;
            }
            $context->clearCachedUnionTypes();

            // Step into each child node and get an
            // updated context for the node
            $context = $this->analyzeAndGetUpdatedContext($context, $node, $child_node);
        }
        $plugin_set->postAnalyzeNode(
            $this->code_base,
            $context,
            $node,
            $this->parent_node_list
        );
        return $context;
    }

    const PHAN_VAR_REGEX =
        '/@(phan-var(?:-force)?)\b\s*(' . UnionType::union_type_regex . ')\s*&?\\$' . Comment::WORD_REGEX . '/';

    const PHAN_SUPPRESS_REGEX =
        '/@phan-file-suppress\s+' . Comment::WORD_REGEX . '/';

    /**
     * Parses annotations such as "(at)phan-var int $myVar" and "(at)phan-var-force ?MyClass $varName" annotations from inline string literals.
     * (php-ast isn't able to parse inline doc comments, so string literals are used for rare edge cases where assert/if statements don't work)
     *
     * Modifies the type of the variable (in the scope of $context) to be identical to the annotated union type.
     * @return void
     */
    private function analyzeSubstituteVarAssert(CodeBase $code_base, Context $context, string $text)
    {
        $has_known_annotations = false;
        if (\preg_match_all(self::PHAN_VAR_REGEX, $text, $matches, PREG_SET_ORDER) > 0) {
            $has_known_annotations = true;
            foreach ($matches as $group) {
                $annotation_name = $group[1];
                $type_string = $group[2];
                $var_name = $group[20];
                $type = UnionType::fromStringInContext($type_string, $context, Type::FROM_PHPDOC);
                $this->createVarForInlineComment($code_base, $context, $var_name, $type, $annotation_name === 'phan-var-force');
            }
        }

        if (\preg_match_all(self::PHAN_SUPPRESS_REGEX, $text, $matches, PREG_SET_ORDER) > 0) {
            $has_known_annotations = true;
            foreach ($matches as $group) {
                $issue_name = $group[1];
                $code_base->addFileLevelSuppression($context->getFile(), $issue_name);
            }
        }

        if (!$has_known_annotations && preg_match('/@phan-.*/', $text, $match) > 0) {
            Issue::maybeEmit(
                $code_base,
                $context,
                Issue::UnextractableAnnotation,
                $context->getLineNumberStart(),
                rtrim($match[0])
            );
        }
        return;
    }

    /**
     * @return void
     * @see ConditionVarUtil::getVariableFromScope
     */
    private function createVarForInlineComment(CodeBase $code_base, Context $context, string $var_name, UnionType $type, bool $create_variable)
    {
        if (!$context->getScope()->hasVariableWithName($var_name)) {
            if (Variable::isHardcodedVariableInScopeWithName($var_name, $context->isInGlobalScope())) {
                return;
            }
            if (!$create_variable && !($context->isInGlobalScope() && Config::getValue('ignore_undeclared_variables_in_global_scope'))) {
                Issue::maybeEmit(
                    $code_base,
                    $context,
                    Issue::UndeclaredVariable,
                    $context->getLineNumberStart(),
                    $var_name
                );
                return;
            }
            $variable = new Variable(
                $context,
                $var_name,
                $type,
                0
            );
            $context->addScopeVariable($variable);
            return;
        }
        $variable = $context->getScope()->getVariableByName(
            $var_name
        );
        $variable->setUnionType($type);
    }

    /**
     * For non-special nodes, we propagate the context and scope
     * from the parent, through the children and return the
     * modified scope
     *
     *          │
     *          ▼
     *       ┌──●
     *       │
     *       ●──●──●
     *             │
     *          ●──┘
     *          │
     *          ▼
     *
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visit(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        // Visit the given node populating the code base
        // with anything we learn and get a new context
        // indicating the state of the world within the
        // given node
        $context = (new PreOrderAnalysisVisitor(
            $this->code_base,
            $context
        ))->{Element::VISIT_LOOKUP_TABLE[$node->kind] ?? 'handleMissingNodeKind'}($node);

        // Let any configured plugins do a pre-order
        // analysis of the node.
        ConfigPluginSet::instance()->preAnalyzeNode(
            $this->code_base,
            $context,
            $node
        );

        \assert(!empty($context), 'Context cannot be null');

        // With a context that is inside of the node passed
        // to this method, we analyze all children of the
        // node.
        foreach ($node->children as $child_node) {
            // Skip any non Node children.
            if (!($child_node instanceof Node)) {
                continue;
            }

            // Step into each child node and get an
            // updated context for the node
            $context = $this->analyzeAndGetUpdatedContext($context, $node, $child_node);
        }

        return $this->postOrderAnalyze($context, $node);
    }

    /**
     * This is an abstraction for getting a new, updated context for a child node.
     *
     * Effectively the same as (new BlockAnalysisVisitor(..., $context, $node, ...)child_node))
     * but is much less repetitive and verbose, and slightly more efficient.
     *
     * @param Context $context - The original context for $node, before analyzing $child_node
     *
     * @param Node $node - The parent node of $child_node
     *
     * @param Node $child_node - The node which will be analyzed to create the updated context.
     *
     * @return Context (The unmodified $context, or a different Context instance with modifications)
     */
    private function analyzeAndGetUpdatedContext(Context $context, Node $node, Node $child_node) : Context
    {
        // Modify the original object instead of creating a new BlockAnalysisVisitor.
        // this is slightly more efficient, especially if a large number of unchanged parameters would exist.
        $old_context = $this->context;
        $this->context = $context;
        $this->parent_node_list[] = $node;
        try {
            return $this->{Element::VISIT_LOOKUP_TABLE[$child_node->kind] ?? 'handleMissingNodeKind'}($child_node);
        } finally {
            $this->context = $old_context;
            \array_pop($this->parent_node_list);
        }
    }

    /**
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitFor(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        $init_node = $node->children['init'];
        if ($init_node instanceof Node) {
            $context = $this->analyzeAndGetUpdatedContext(
                $context->withLineNumberStart($init_node->lineno ?? 0),
                $node,
                $init_node
            );
        }
        $context = $this->preOrderAnalyze($context, $node);
        \assert(!empty($context), 'Context cannot be null');

        $condition_node = $node->children['cond'];
        if ($condition_node instanceof Node) {
            // The typical case is `for (init; $x; loop) {}`
            // But `for (init; $x; loop) {}` is rare but possible, which requires evaluating those in order.
            // Evaluate the list of cond expressions in order.
            \assert($condition_node->kind === \ast\AST_EXPR_LIST);
            foreach ($condition_node->children as $condition_subnode) {
                if ($condition_subnode instanceof Node) {
                    $context = $this->analyzeAndGetUpdatedContext(
                        $context->withLineNumberStart($condition_subnode->lineno ?? 0),
                        $node,  // TODO: condition_node?
                        $condition_subnode
                    );
                }
            }
        }

        if ($stmts_node = $node->children['stmts']) {
            if ($stmts_node instanceof Node) {
                $context = $this->analyzeAndGetUpdatedContext(
                    $context->withScope(
                        new BranchScope($context->getScope())
                    )->withLineNumberStart($stmts_node->lineno ?? 0),
                    $node,
                    $stmts_node
                );
            }
        }
        // Analyze the loop after analyzing the statements, in case it uses variables defined within the statements.
        $loop_node = $node->children['loop'];
        if ($loop_node instanceof Node) {
            $context = $this->analyzeAndGetUpdatedContext(
                $context->withLineNumberStart($loop_node->lineno ?? 0),
                $node,
                $loop_node
            );
        }

        // Now that we know all about our context (like what
        // 'self' means), we can analyze statements like
        // assignments and method calls.
        $context = $this->postOrderAnalyze($context, $node);

        // When coming out of a scoped element, we pop the
        // context to be the incoming context. Otherwise,
        // we pass our new context up to our parent
        return $context;
    }

    /**
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitWhile(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        $context = $this->preOrderAnalyze($context, $node);

        \assert(!empty($context), 'Context cannot be null');

        $condition_node = $node->children['cond'];
        if ($condition_node instanceof Node) {
            // The typical case is `for (init; $x; loop) {}`
            // But `for (init; $x; loop) {}` is rare but possible, which requires evaluating those in order.
            // Evaluate the list of cond expressions in order.
            $context = $this->analyzeAndGetUpdatedContext(
                $context->withLineNumberStart($condition_node->lineno ?? 0),
                $node,
                $condition_node
            );
        }

        if ($stmts_node = $node->children['stmts']) {
            if ($stmts_node instanceof Node) {
                $context = $this->analyzeAndGetUpdatedContext(
                    $context->withScope(
                        new BranchScope($context->getScope())
                    )->withLineNumberStart($stmts_node->lineno ?? 0),
                    $node,
                    $stmts_node
                );
            }
        }

        // Now that we know all about our context (like what
        // 'self' means), we can analyze statements like
        // assignments and method calls.
        $context = $this->postOrderAnalyze($context, $node);

        // When coming out of a scoped element, we pop the
        // context to be the incoming context. Otherwise,
        // we pass our new context up to our parent
        return $context;
    }

    /**
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitIfElem(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        // NOTE: This is different from other analysis visitors because analyzing 'cond' with `||` has side effects
        // after supporting visitAnd() and visitOr() in BlockAnalysisVisitor
        // TODO: Calling analyzeAndGetUpdatedContext before preOrderAnalyze is a hack.

        // TODO: This is redundant and has worse knowledge of the specific types of blocks than ConditionVisitor does.
        // TODO: Implement a hybrid BlockAnalysisVisitor+ConditionVisitor that will do a better job of inferences and reducing false positives? (and reduce the redundant work)

        // E.g. the below code would update the context of BlockAnalysisVisitor in BlockAnalysisVisitor->visitOr()
        //
        //     if (!(is_string($x) || $x === null)) {}
        //
        // But we want to let BlockAnalysisVisitor modify the context for cases such as the below:
        //
        // $result = !($x instanceof User) || $x->meetsCondition()
        $condition_node = $node->children['cond'];
        if ($condition_node instanceof Node) {
            $context = $this->analyzeAndGetUpdatedContext(
                $context->withLineNumberStart($condition_node->lineno ?? 0),
                $node,
                $condition_node
            );
        }

        $context = $this->preOrderAnalyze($context, $node);

        \assert(!empty($context), 'Context cannot be null');

        if ($stmts_node = $node->children['stmts']) {
            if ($stmts_node instanceof Node) {
                $context = $this->analyzeAndGetUpdatedContext(
                    $context->withScope(
                        new BranchScope($context->getScope())
                    )->withLineNumberStart($stmts_node->lineno ?? 0),
                    $node,
                    $stmts_node
                );
            }
        }

        // Now that we know all about our context (like what
        // 'self' means), we can analyze statements like
        // assignments and method calls.
        $context = $this->postOrderAnalyze($context, $node);

        // When coming out of a scoped element, we pop the
        // context to be the incoming context. Otherwise,
        // we pass our new context up to our parent
        return $context;
    }

    /**
     * For 'closed context' items (classes, methods, functions,
     * closures), we analyze children in the parent context, but
     * then return the parent context itself unmodified by the
     * children.
     *
     *           │
     *           ▼
     *        ┌──●────┐
     *        │       │
     *        ●──●──● │
     *           ┌────┘
     *           ●
     *           │
     *           ▼
     *
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitClosedContext(Node $node) : Context
    {
        // Make a copy of the internal context so that we don't
        // leak any changes within the closed context to the
        // outer scope
        $context = clone($this->context->withLineNumberStart(
            $node->lineno ?? 0
        ));

        $context = $this->preOrderAnalyze($context, $node);

        \assert(!empty($context), 'Context cannot be null');

        // We collect all child context so that the
        // PostOrderAnalysisVisitor can optionally operate on
        // them
        $child_context_list = [];

        $child_context = $context;

        // With a context that is inside of the node passed
        // to this method, we analyze all children of the
        // node.
        foreach ($node->children as $child_node) {
            // Skip any non Node children.
            if (!($child_node instanceof Node)) {
                continue;
            }

            // Step into each child node and get an
            // updated context for the node
            $child_context = $this->analyzeAndGetUpdatedContext($child_context, $node, $child_node);

            $child_context_list[] = $child_context;
        }

        // For if statements, we need to merge the contexts
        // of all child context into a single scope based
        // on any possible branching structure
        $context = (new ContextMergeVisitor(
            $context,
            $child_context_list
        ))->__invoke($node);

        $unused_final_context = $this->postOrderAnalyze($context, $node);

        // Return the initial context as we exit
        return $this->context;
    }

    /**
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitSwitchList(Node $node) : Context
    {
        // Make a copy of the internal context so that we don't
        // leak any changes within the closed context to the
        // outer scope
        $context = clone($this->context->withLineNumberStart(
            $node->lineno ?? 0
        ));

        $context = $this->preOrderAnalyze($context, $node);
        $scope = $context->getScope();
        $child_context_list = [];

        // TODO: Improve inferences in switch statements?
        // TODO: Behave differently if switch lists don't cover every case (e.g. if there is no default)
        $has_default = false;
        foreach ($node->children as $i => $child_node) {
            // Step into each child node and get an
            // updated context for the node
            $child_context = $context->withScope(new BranchScope($scope));
            $child_context->withLineNumberStart($child_node->lineno);
            $child_context = $this->analyzeAndGetUpdatedContext($child_context, $node, $child_node);

            if ($child_node->children['cond'] === null) {
                $has_default = true;
            }
            // We can improve analysis of `case` blocks by using
            // a BlockExitStatusChecker to avoid propogating invalid inferences.
            $stmts_node = $child_node->children['stmts'];
            if (!BlockExitStatusChecker::willUnconditionallyThrowOrReturn($stmts_node)) {
                // Skip over empty case statements (incomplete heuristic), TODO: test
                if (\count($stmts_node->children ?? []) !== 0 || $i === \count($node->children) - 1) {
                    $child_context_list[] = $child_context;
                }
            }
        }

        if (\count($child_context_list) > 0) {
            if (!$has_default) {
                $child_context_list[] = $context;
            }
            if (\count($child_context_list) >= 2) {
                // For case statements, we need to merge the contexts
                // of all child context into a single scope based
                // on any possible branching structure
                $context = (new ContextMergeVisitor(
                    $context,
                    $child_context_list
                ))->combineChildContextList();
            } else {
                $context = $child_context_list[0];
            }
        }

        return $this->postOrderAnalyze($context, $node);
    }

    /**
     * @param Node $node
     * An AST node we'd like to analyze the statements
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitIf(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        $context = $this->preOrderAnalyze($context, $node);

        \assert(!empty($context), 'Context cannot be null');

        // We collect all child context so that the
        // PostOrderAnalysisVisitor can optionally operate on
        // them
        $child_context_list = [];

        $scope = $context->getScope();
        if ($scope instanceof GlobalScope) {
            $fallthrough_context = $context->withScope(new BranchScope($scope));
        } else {
            $fallthrough_context = $context;
        }

        $child_nodes = $node->children;
        $excluded_elem_count = 0;

        // With a context that is inside of the node passed
        // to this method, we analyze all children of the
        // node.
        foreach ($child_nodes as $child_node) {
            // The conditions need to communicate to the outter
            // scope for things like assigning veriables.
            $child_context = clone($fallthrough_context);

            assert($child_node->kind === \ast\AST_IF_ELEM);

            $child_context->withLineNumberStart(
                $child_node->lineno ?? 0
            );

            // Step into each child node and get an
            // updated context for the node
            $child_context = $this->analyzeAndGetUpdatedContext($child_context, $node, $child_node);

            // Issue #406: We can improve analysis of `if` blocks by using
            // a BlockExitStatusChecker to avoid propogating invalid inferences.
            // TODO: we may wish to check for a try block between this line's scope
            // and the parent function's (or global) scope,
            // to reduce false positives.
            // (Variables will be available in `catch` and `finally`)
            // This is mitigated by finally and catch blocks being unaware of new variables from try{} blocks.
            if (BlockExitStatusChecker::willUnconditionallySkipRemainingStatements($child_node->children['stmts'])) {
                // e.g. "if (!is_string($x)) { return; }"
                $excluded_elem_count++;
            } else {
                $child_context_list[] = $child_context;
            }

            $cond_node = $child_node->children['cond'];
            if ($cond_node instanceof Node) {
                $fallthrough_context = (new NegatedConditionVisitor($this->code_base, $fallthrough_context))($cond_node);
            }
            // If cond_node was null, it would be an else statement.
        }

        if ($excluded_elem_count === count($child_nodes)) {
            // If all of the AST_IF_ELEM bodies would unconditionally throw or return,
            // then analyze the remaining statements with the negation of all of the conditions.
            $context = $fallthrough_context;
        } else {
            // For if statements, we need to merge the contexts
            // of all child context into a single scope based
            // on any possible branching structure

            // ContextMergeVisitor will include the incoming scope($context) if the if elements aren't comprehensive
            $context = (new ContextMergeVisitor(
                $fallthrough_context,  // e.g. "if (!is_string($x)) { $x = ''; }" should result in inferring $x is a string.
                $child_context_list
            ))->visitIf($node);
        }

        $context = $this->postOrderAnalyze($context, $node);

        // When coming out of a scoped element, we pop the
        // context to be the incoming context. Otherwise,
        // we pass our new context up to our parent
        return $context;
    }

    /**
     * TODO: Diagram similar to visit() for this and other visitors handling branches?
     *
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitTry(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        $context = $this->preOrderAnalyze($context, $node);

        \assert(!empty($context), 'Context cannot be null');

        // With a context that is inside of the node passed
        // to this method, we analyze all children of the
        // node.

        $try_node = $node->children['try'];
        // Skip any non Node children.
        assert($try_node instanceof Node);

        // The conditions need to communicate to the outer
        // scope for things like assigning veriables.
        $try_context = $context->withScope(
            new BranchScope($context->getScope())
        );

        $try_context->withLineNumberStart(
            $try_node->lineno ?? 0
        );

        // Step into each try node and get an
        // updated context for the node
        $try_context = $this->analyzeAndGetUpdatedContext($try_context, $node, $try_node);

        // Analyze the catch blocks and finally blocks with a mix of the types
        // from the try block and the catch blocks.
        // NOTE: when strict_mode = 1, variables that are only defined in some Contexts
        // but not others will be treated as absent.
        // TODO: Improve in future releases
        // NOTE: We let ContextMergeVisitor->visitTry decide if the block exit status is valid.
        $context = (new ContextMergeVisitor(
            $context,
            [$try_context]
        ))->mergeTryContext($node);

        // We collect all child context so that the
        // PostOrderAnalysisVisitor can optionally operate on
        // them
        $catch_context_list = [$try_context];

        $catch_nodes = $node->children['catches']->children ?? [];

        foreach ($catch_nodes as $catch_node) {
            // Note: ContextMergeVisitor expects to get each individual catch
            assert($catch_node instanceof Node);

            // The conditions need to communicate to the outer
            // scope for things like assigning veriables.
            $catch_context = $context->withScope(
                new BranchScope($context->getScope())
            );

            $catch_context->withLineNumberStart(
                $catch_node->lineno ?? 0
            );

            // Step into each catch node and get an
            // updated context for the node
            $catch_context = $this->analyzeAndGetUpdatedContext($catch_context, $node, $catch_node);
            // NOTE: We let ContextMergeVisitor->mergeCatchContext decide if the block exit status is valid.
            $catch_context_list[] = $catch_context;
        }

        $this->checkUnreachableCatch($catch_nodes, $context);

        // first context is the try. If there's a second context, it's a catch.
        if (count($catch_context_list) >= 2) {
            // For switch/try statements, we need to merge the contexts
            // of all child context into a single scope based
            // on any possible branching structure
            $context = (new ContextMergeVisitor(
                $context,
                $catch_context_list
            ))->mergeCatchContext($node);
        }

        $finally_node = $node->children['finally'] ?? null;
        if ($finally_node) {
            assert($finally_node instanceof Node);
            // Because finally is always executed, we reuse $context

            // The conditions need to communicate to the outer
            // scope for things like assigning veriables.
            $context = $context->withScope(
                new BranchScope($context->getScope())
            );

            $context->withLineNumberStart(
                $finally_node->lineno ?? 0
            );

            // Step into each finally node and get an
            // updated context for the node.
            // Don't bother checking if finally unconditionally returns here
            // If it does, dead code detection would also warn.
            $context = $this->analyzeAndGetUpdatedContext($context, $node, $finally_node);
        }

        $context = $this->postOrderAnalyze($context, $node);

        // When coming out of a scoped element, we pop the
        // context to be the incoming context. Otherwise,
        // we pass our new context up to our parent
        return $context;
    }

    /**
     * @param array<int,Node> $catch_nodes
     * @param Context $context
     * @return void
     * @suppress PhanPluginUnusedVariable
     */
    private function checkUnreachableCatch(array $catch_nodes, Context $context)
    {
        if (count($catch_nodes) <= 1) {
            return;
        }
        $caught_union_types = [];
        $code_base = $this->code_base;

        foreach ($catch_nodes as $catch_node) {
            $union_type = UnionTypeVisitor::unionTypeFromClassNode(
                $code_base,
                $context,
                $catch_node->children['class']
            )->objectTypesWithKnownFQSENs();

            $catch_line = $catch_node->lineno ?? 0;

            foreach ($union_type->getTypeSet() as $type) {
                foreach ($type->asExpandedTypes($code_base)->getTypeSet() as $ancestor_type) {
                    // Check if any of the ancestors were already caught by a previous catch statement
                    $line = $caught_union_types[\spl_object_id($ancestor_type)] ?? null;

                    if ($line !== null) {
                        Issue::maybeEmit(
                            $code_base,
                            $context,
                            Issue::UnreachableCatch,
                            $catch_line,
                            (string)$type,
                            $line,
                            (string)$ancestor_type
                        );
                        break;
                    }
                }
            }
            foreach ($union_type->getTypeSet() as $type) {
                // Track where this ancestor type was thrown
                $caught_union_types[\spl_object_id($type)] = $catch_line;
            }
        }
    }

    /**
     * @param Node $node
     * A node to parse
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitBinaryOp(Node $node) : Context
    {
        $flags = $node->flags;
        if ($flags === \ast\flags\BINARY_BOOL_AND) {
            return $this->visitAnd($node);
        } elseif ($flags === \ast\flags\BINARY_BOOL_OR) {
            return $this->visitOr($node);
        }
        return $this->visit($node);
    }

    /**
     * @param Node $node
     * A node to parse (for `&&` or `and` operator)
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitAnd(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        ConfigPluginSet::instance()->preAnalyzeNode(
            $this->code_base,
            $context,
            $node
        );

        $left_node = $node->children['left'];
        $right_node = $node->children['right'];

        // With (left) && (right)
        // 1. Update context with any side effects of left
        // 2. Create a context to analyze the right hand side with any inferences possible from left (e.g. ($x instanceof MyClass) && $x->foo()
        // 3. Analyze the right hand side
        // 4. Merge the possibly evaluated $right_context for the right hand side into the original context. (The left_node is guaranteed to have been evaluated, so it becomes $context)

        // TODO: Warn about non-node, they're guaranteed to be always false or true
        if ($left_node instanceof Node) {
            $context = $this->analyzeAndGetUpdatedContext($context, $node, $left_node);

            $base_context = $context;
            $base_context_scope = $base_context->getScope();
            if ($base_context_scope instanceof GlobalScope) {
                $base_context = $context->withScope(new BranchScope($base_context_scope));
            }
            $context_with_left_condition = (new ConditionVisitor(
                $this->code_base,
                $base_context
            ))($left_node);
        } else {
            $context_with_left_condition = $context;
        }

        if ($right_node instanceof Node) {
            $right_context = $this->analyzeAndGetUpdatedContext($context_with_left_condition, $node, $right_node);
            $context = (new ContextMergeVisitor(
                $context,
                [$context, $context_with_left_condition, $right_context]
            ))($node);
        }

        $context = $this->postOrderAnalyze($context, $node);

        return $context;
    }

    /**
     * @param Node $node
     * A node to parse (for `||` or `or` operator)
     *
     * @return Context
     * A new or an unchanged context resulting from
     * parsing the node
     */
    public function visitOr(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        ConfigPluginSet::instance()->preAnalyzeNode(
            $this->code_base,
            $context,
            $node
        );

        $left_node = $node->children['left'];
        $right_node = $node->children['right'];

        // With (left) || (right)
        // 1. Update context with any side effects of left
        // 2. Create a context to analyze the right hand side with any inferences possible from left (e.g. (!($x instanceof MyClass)) || $x->foo()
        // 3. Analyze the right hand side
        // 4. Merge the possibly evaluated $right_context for the right hand side into the original context. (The left_node is guaranteed to have been evaluated, so it becomes $context)

        // TODO: Warn about non-node, they're guaranteed to be always false or true
        if ($left_node instanceof Node) {
            $context = $this->analyzeAndGetUpdatedContext($context, $node, $left_node);

            $base_context = $context;
            $base_context_scope = $base_context->getScope();
            if ($base_context_scope instanceof GlobalScope) {
                $base_context = $context->withScope(new BranchScope($base_context_scope));
            }
            $context_with_false_left_condition = (new NegatedConditionVisitor(
                $this->code_base,
                $base_context
            ))($left_node);
            $context_with_true_left_condition = (new ConditionVisitor(
                $this->code_base,
                $base_context
            ))($left_node);
        } else {
            $context_with_false_left_condition = $context;
            $context_with_true_left_condition = $context;
        }

        if ($right_node instanceof Node) {
            $right_context = $this->analyzeAndGetUpdatedContext($context_with_false_left_condition, $node, $right_node);
            $context = (new ContextMergeVisitor(
                $context,
                [$context, $context_with_true_left_condition, $right_context]
            ))->combineChildContextList();
        }

        $context = $this->postOrderAnalyze($context, $node);

        return $context;
    }

    public function visitConditional(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        // Visit the given node populating the code base
        // with anything we learn and get a new context
        // indicating the state of the world within the
        // given node
        // NOTE: unused for AST_CONDITIONAL
        // $context = (new PreOrderAnalysisVisitor(
        //     $this->code_base, $context
        // ))($node);

        // Let any configured plugins do a pre-order
        // analysis of the node.
        ConfigPluginSet::instance()->preAnalyzeNode(
            $this->code_base,
            $context,
            $node
        );

        \assert(!empty($context), 'Context cannot be null');

        $true_node = $node->children['true'] ?? null;
        $false_node = $node->children['false'] ?? null;

        $cond_node = $node->children['cond'];
        if ($cond_node instanceof Node) {
            // Step into each child node and get an
            // updated context for the node
            // (e.g. there may be assignments such as '($x = foo()) ? $a : $b)
            $context = $this->analyzeAndGetUpdatedContext($context, $node, $cond_node);

            // TODO: Use different contexts and merge those, in case there were assignments or assignments by reference in both sides of the conditional?
            // Reuse the BranchScope (sort of unintuitive). The ConditionVisitor returns a clone and doesn't modify the original.
            $base_context = $context;
            $base_context_scope = $base_context->getScope();
            if ($base_context_scope instanceof GlobalScope) {
                $base_context = $context->withScope(new BranchScope($base_context_scope));
            }
            $true_context = (new ConditionVisitor(
                $this->code_base,
                isset($true_node) ? $base_context : $context  // special case: (($d = foo()) ?: 'fallback')
            ))($cond_node);
            $false_context = (new NegatedConditionVisitor(
                $this->code_base,
                $base_context
            ))($cond_node);
        } else {
            $true_context = $context;
            $false_context = $context;
        }

        $child_context_list = [];
        // In the long form, there's a $true_node, but in the short form (?:),
        // $cond_node is the (already processed) value for truthy.
        if ($true_node instanceof Node) {
            $child_context = $this->analyzeAndGetUpdatedContext($true_context, $node, $true_node);
            $child_context_list[] = $child_context;
        }

        if ($false_node instanceof Node) {
            $child_context = $this->analyzeAndGetUpdatedContext($false_context, $node, $false_node);
            $child_context_list[] = $child_context;
        }
        if (\count($child_context_list) >= 1) {
            if (\count($child_context_list) < 2) {
                $child_context_list[] = $context;
            }
            $context = (new ContextMergeVisitor(
                $context,
                $child_context_list
            ))->combineChildContextList();
        }

        $context = $this->postOrderAnalyze($context, $node);

        return $context;
    }

    /**
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitClass(Node $node) : Context
    {
        return $this->visitClosedContext($node);
    }

    /**
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitMethod(Node $node) : Context
    {
        return $this->visitClosedContext($node);
    }

    /**
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitFuncDecl(Node $node) : Context
    {
        return $this->visitClosedContext($node);
    }

    /**
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitClosure(Node $node) : Context
    {
        return $this->visitClosedContext($node);
    }

    /**
     * Common options for pre-order analysis phase of a Node.
     * Run pre-order analysis steps, then run plugins.
     *
     * @param Context $context - The context before pre-order analysis
     *
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after pre-order analysis of the node
     */
    private function preOrderAnalyze(Context $context, Node $node) : Context
    {
        // Visit the given node populating the code base
        // with anything we learn and get a new context
        // indicating the state of the world within the
        // given node
        // Equivalent to (new PostOrderAnalysisVisitor(...)($node)) but faster than using __invoke()
        $context = (new PreOrderAnalysisVisitor(
            $this->code_base,
            $context
        ))->{Element::VISIT_LOOKUP_TABLE[$node->kind] ?? 'handleMissingNodeKind'}($node);

        // Let any configured plugins do a pre-order
        // analysis of the node.
        ConfigPluginSet::instance()->preAnalyzeNode(
            $this->code_base,
            $context,
            $node
        );
        return $context;
    }

    /**
     * Common options for post-order analysis phase of a Node.
     * Run analysis steps and run plugins.
     *
     * @param Context $context - The context before post-order analysis
     *
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     *
     * @return Context
     * The updated context after post-order analysis of the node
     */
    private function postOrderAnalyze(Context $context, Node $node) : Context
    {
        // Now that we know all about our context (like what
        // 'self' means), we can analyze statements like
        // assignments and method calls.
        // Equivalent to (new PostOrderAnalysisVisitor(...)($node)) but faster than using __invoke()
        $context = (new PostOrderAnalysisVisitor(
            $this->code_base,
            $context->withLineNumberStart($node->lineno ?? 0),
            $this->parent_node_list
        ))->{Element::VISIT_LOOKUP_TABLE[$node->kind] ?? 'handleMissingNodeKind'}($node);

        // let any configured plugins analyze the node
        ConfigPluginSet::instance()->postAnalyzeNode(
            $this->code_base,
            $context,
            $node,
            $this->parent_node_list
        );
        return $context;
    }

    /**
     * Analyzes a node of type \ast\AST_GROUP_USE
     * This is the same as visit(), but does not recurse into the child nodes.
     *
     * If this function override didn't exist,
     * then visit() would recurse into \ast\AST_USE,
     * which would lack part of the namespace.
     * (E.g. for use \NS\{const X, const Y}, we don't want to analyze const X or const Y
     * without the preceding \NS\)
     */
    public function visitGroupUse(Node $node) : Context
    {
        $context = $this->context->withLineNumberStart(
            $node->lineno ?? 0
        );

        // Visit the given node populating the code base
        // with anything we learn and get a new context
        // indicating the state of the world within the
        // given node
        $context = (new PreOrderAnalysisVisitor(
            $this->code_base,
            $context
        ))->{Element::VISIT_LOOKUP_TABLE[$node->kind] ?? 'handleMissingNodeKind'}($node);

        // Let any configured plugins do a pre-order
        // analysis of the node.
        ConfigPluginSet::instance()->preAnalyzeNode(
            $this->code_base,
            $context,
            $node
        );

        \assert(!empty($context), 'Context cannot be null');

        $context = $this->postOrderAnalyze($context, $node);

        return $context;
    }

    /**
     * @param Node $node
     * An AST node we'd like to analyze the statements for
     * @see $this->visit() - This is similar to visit() except that the check if $is_static requires parent_node,
     * so PreOrderAnalysisVisitor can't be used to modify the Context.
     *
     * @return Context
     * The updated context after visiting the node
     */
    public function visitPropElem(Node $node) : Context
    {
        $prop_name = (string)$node->children['name'];

        $context = $this->context;
        $class = $context->getClassInScope($this->code_base);

        $context = $this->context->withScope(new PropertyScope(
            $context->getScope(),
            FullyQualifiedPropertyName::make($class->getFQSEN(), $prop_name)
        ))->withLineNumberStart(
            $node->lineno ?? 0
        );

        \assert(!empty($context), 'Context cannot be null');

        // Don't bother calling PreOrderAnalysisVisitor, it does nothing

        // Let any configured plugins do a pre-order
        // analysis of the node.
        ConfigPluginSet::instance()->preAnalyzeNode(
            $this->code_base,
            $context,
            $node
        );

        // With a context that is inside of the node passed
        // to this method, we analyze all children of the
        // node.
        $default = $node->children['default'] ?? null;
        if ($default instanceof Node) {
            // Step into each child node and get an
            // updated context for the node
            $context = $this->analyzeAndGetUpdatedContext($context, $node, $default);
        }

        $context = $this->postOrderAnalyze($context, $node);

        return $context;
    }
}
<?php declare(strict_types = 1);
namespace Phan\Output;

interface BufferedPrinterInterface extends IssuePrinterInterface
{
    /** flush printer buffer */
    public function flush();
}
<?php declare(strict_types = 1);
namespace Phan\Output;

use Phan\Output\Printer\CheckstylePrinter;
use Phan\Output\Printer\CodeClimatePrinter;
use Phan\Output\Printer\CSVPrinter;
use Phan\Output\Printer\JSONPrinter;
use Phan\Output\Printer\PlainTextPrinter;
use Phan\Output\Printer\PylintPrinter;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class PrinterFactory
 * Subject of future refactoring to be a bit more extensible
 */
class PrinterFactory
{

    /**
     * @return array<int,string>
     */
    public function getTypes():array
    {
        return ['text', 'json', 'csv', 'codeclimate', 'checkstyle', 'pylint'];
    }

    public function getPrinter($type, OutputInterface $output):IssuePrinterInterface
    {
        switch ($type) {
            case 'codeclimate':
                $printer = new CodeClimatePrinter();
                break;
            case 'json':
                $printer = new JSONPrinter();
                break;
            case 'checkstyle':
                $printer = new CheckstylePrinter();
                break;
            case 'csv':
                $printer = new CSVPrinter();
                break;
            case 'pylint':
                $printer = new PylintPrinter();
                break;
            case 'text':
            default:
                $printer = new PlainTextPrinter();
                break;
        }

        $printer->configureOutput($output);

        return $printer;
    }
}
<?php declare(strict_types = 1);
namespace Phan\Output;

use Phan\IssueInstance;

interface IssueCollectorInterface
{

    /**
     * Collect issue
     * @param IssueInstance $issue
     */
    public function collectIssue(IssueInstance $issue);

    /**
     * @return array<int,IssueInstance>
     */
    public function getCollectedIssues():array;

    /**
     * Remove all collected issues (from the parse phase) for the given file paths.
     * Called from daemon mode.
     *
     * @param array<int,string> $files - the relative paths to those files
     * @return void
     */
    public function removeIssuesForFiles(array $files);

    /**
     * Remove all collected issues.
     */
    public function reset();
}
<?php declare(strict_types = 1);
namespace Phan\Output;

interface IgnoredFilesFilterInterface
{

    /**
     * @param string $filename
     * @return bool True if filename is ignored during analysis
     */
    public function isFilenameIgnored(string $filename):bool;
}
<?php declare(strict_types=1);
namespace Phan\Output\Filter;

use Phan\IssueInstance;
use Phan\Output\IgnoredFilesFilterInterface;
use Phan\Output\IssueFilterInterface;

final class FileIssueFilter implements IssueFilterInterface
{

    /** @var IgnoredFilesFilterInterface */
    private $ignoredFilesFilter;

    /**
     * FileIssueFilter constructor.
     *
     * @param IgnoredFilesFilterInterface $ignoredFilesFilter
     */
    public function __construct(
        IgnoredFilesFilterInterface $ignoredFilesFilter
    ) {
        $this->ignoredFilesFilter = $ignoredFilesFilter;
    }

    /**
     * @param IssueInstance $issue
     * @return bool
     */
    public function supports(IssueInstance $issue):bool
    {
        return !$this->ignoredFilesFilter->isFilenameIgnored($issue->getFile());
    }
}
<?php declare(strict_types=1);
namespace Phan\Output\Filter;

use Phan\IssueInstance;
use Phan\Output\IssueFilterInterface;

final class AnyFilter implements IssueFilterInterface
{

    /**
     * @param IssueInstance $issue (@phan-unused-param)
     * @return bool
     */
    public function supports(IssueInstance $issue):bool
    {
        return true;
    }
}
<?php declare(strict_types=1);
namespace Phan\Output\Filter;

use Phan\Issue;
use Phan\IssueInstance;
use Phan\Output\IssueFilterInterface;

final class MinimumSeverityFilter implements IssueFilterInterface
{

    /** @var int */
    private $minimumSeverity;

    /**
     * MinimumSeverityFilter constructor.
     * @param $minimumSeverity
     */
    public function __construct(int $minimumSeverity = Issue::SEVERITY_LOW)
    {
        $this->minimumSeverity = $minimumSeverity;
    }


    /**
     * @param IssueInstance $issue
     * @return bool
     */
    public function supports(IssueInstance $issue):bool
    {
        return $issue->getIssue()->getSeverity() >= $this->minimumSeverity;
    }
}
<?php

namespace Phan\Output\Filter;

use Phan\IssueInstance;
use Phan\Output\IssueFilterInterface;

final class CategoryIssueFilter implements IssueFilterInterface
{
    /** @var  int */
    private $mask;

    /**
     * CategoryIssueFilter constructor.
     * @param int $mask
     */
    public function __construct(int $mask = -1)
    {
        $this->mask = $mask;
    }

    /**
     * @param IssueInstance $issue
     * @return bool
     */
    public function supports(IssueInstance $issue) : bool
    {
        return (bool)($issue->getIssue()->getCategory() & $this->mask);
    }
}
<?php declare(strict_types=1);
namespace Phan\Output\Filter;

use Phan\IssueInstance;
use Phan\Output\IssueFilterInterface;

final class ChainedIssueFilter implements IssueFilterInterface
{

    /** @var IssueFilterInterface[] */
    private $filters = [];

    /**
     * ChainedIssueFilter constructor.
     *
     * @param IssueFilterInterface[] $filters
     */
    public function __construct(array $filters)
    {
        $this->filters = $filters;
    }

    /**
     * @param IssueInstance $issue
     * @return bool
     */
    public function supports(IssueInstance $issue):bool
    {
        foreach ($this->filters as $filter) {
            if (!$filter->supports($issue)) {
                return false;
            }
        }

        return true;
    }
}
<?php declare(strict_types = 1);
namespace Phan\Output\Printer;

use Phan\Issue;
use Phan\IssueInstance;
use Phan\Output\BufferedPrinterInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class CSVPrinter implements BufferedPrinterInterface
{

    /** @var  OutputInterface */
    private $output;

    /** @var resource */
    private $stream;

    /** @param IssueInstance $instance */
    public function print(IssueInstance $instance)
    {
        fputcsv($this->stream, [
            $instance->getFile(),
            $instance->getLine(),
            $instance->getIssue()->getSeverity(),
            $instance->getIssue()->getSeverityName(),
            Issue::getNameForCategory($instance->getIssue()->getCategory()),
            $instance->getIssue()->getType(),
            $instance->getMessage(),
        ]);
    }

    /** flush printer buffer */
    public function flush()
    {
        fseek($this->stream, 0);
        $this->output->write(stream_get_contents($this->stream));
        fclose($this->stream);
        $this->initStream();
    }

    /**
     * @param OutputInterface $output
     */
    public function configureOutput(OutputInterface $output)
    {
        $this->output = $output;
        $this->initStream();
    }

    private function initStream()
    {
        // Because fputcsv works on file pointers we need to do a bit
        // of dancing around with a memory stream.
        $stream = fopen("php://memory", "rw");
        \assert(\is_resource($stream), 'php://memory should always be openable');
        $this->stream = $stream;
        fputcsv($this->stream, [
            "filename", "line", "severity_ord", "severity_name",
            "category", "check_name", "message"
        ]);
    }
}
<?php declare(strict_types = 1);
namespace Phan\Output\Printer;

use Phan\Issue;
use Phan\IssueInstance;
use Phan\Output\BufferedPrinterInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class JSONPrinter implements BufferedPrinterInterface
{

    /** @var  OutputInterface */
    private $output;

    /** @var array<int,array> */
    private $messages = [];

    /** @param IssueInstance $instance */
    public function print(IssueInstance $instance)
    {
        $issue = $instance->getIssue();
        $this->messages[] = [
            'type' => 'issue',
            'type_id' => $issue->getTypeId(),
            'check_name' => $issue->getType(),
            'description' =>
                Issue::getNameForCategory($issue->getCategory()) . ' ' .
                $issue->getType() . ' ' .
                $instance->getMessage(),
            'severity' => $issue->getSeverity(),
            'location' => [
                'path' => preg_replace('/^\/code\//', '', $instance->getFile()),
                'lines' => [
                    'begin' => $instance->getLine(),
                    'end' => $instance->getLine(),
                ],
            ],
        ];
    }

    /** flush printer buffer */
    public function flush()
    {
        // NOTE: Need to use OUTPUT_RAW for JSON.
        // Otherwise, error messages such as "...Unexpected << (T_SL)" don't get formatted properly (They get escaped into unparseable JSON)
        $encodedMessage = json_encode($this->messages, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $this->output->write($encodedMessage, false, OutputInterface::OUTPUT_RAW);
        $this->messages = [];
    }

    /**
     * @param OutputInterface $output
     */
    public function configureOutput(OutputInterface $output)
    {
        $this->output = $output;
    }
}
<?php declare(strict_types = 1);
namespace Phan\Output\Printer;

use Phan\Config;
use Phan\Issue;
use Phan\IssueInstance;
use Phan\Output\IssuePrinterInterface;
use Phan\Output\Colorizing;
use Symfony\Component\Console\Output\OutputInterface;

final class PlainTextPrinter implements IssuePrinterInterface
{

    /** @var OutputInterface */
    private $output;

    /**
     * @param IssueInstance $instance
     * @return void
     */
    public function print(IssueInstance $instance)
    {
        $file    = $instance->getFile();
        $line    = $instance->getLine();
        $issue   = $instance->getIssue();
        $type    = $issue->getType();
        $message = $instance->getMessage();
        if (Config::getValue('color_issue_messages')) {
            switch ($issue->getSeverity()) {
                case Issue::SEVERITY_CRITICAL:
                    $issue_type_template = '{ISSUETYPE_CRITICAL}';
                    break;
                case Issue::SEVERITY_NORMAL:
                    $issue_type_template = '{ISSUETYPE_NORMAL}';
                    break;
                default:
                    $issue_type_template = '{ISSUETYPE}';
                    break;
            }
            $issue = Colorizing::colorizeTemplate("{FILE}:{LINE} $issue_type_template %s", [
                $file,
                $line,
                $type,
                $message
            ]);
        } else {
            $issue = sprintf(
                '%s:%d %s %s',
                $file,
                $line,
                $type,
                $message
            );
        }

        $this->output->writeln($issue);
    }

    /**
     * @param OutputInterface $output
     * @return void
     */
    public function configureOutput(OutputInterface $output)
    {
        $this->output = $output;
    }
}
<?php declare(strict_types = 1);
namespace Phan\Output\Printer;

use Phan\IssueInstance;
use Phan\Output\BufferedPrinterInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class CheckstylePrinter implements BufferedPrinterInterface
{

    /** @var OutputInterface */
    private $output;

    /** @var array<string,array<int,array>> */
    private $files = [];

    /** @param IssueInstance $instance */
    public function print(IssueInstance $instance)
    {
        if (empty($this->files[$instance->getFile()])) {
            $this->files[$instance->getFile()] = [];
        }

        // Group issues by file
        $this->files[$instance->getFile()][] = [
            'line' => $instance->getLine(),
            'source' => $instance->getIssue()->getType(),
            'message' => $instance->getMessage(),
            'severity' => $instance->getIssue()->getSeverityName(),
        ];
    }

    /** flush printer buffer */
    public function flush()
    {
        $document = new \DOMDocument('1.0', 'ISO-8859-15');

        $checkstyle = new \DOMElement('checkstyle');
        $document->appendChild($checkstyle);
        $checkstyle->appendChild(new \DOMAttr('version', '6.5'));

        // Write each file to the DOM
        foreach ($this->files as $file_name => $error_list) {
            $file = new \DOMElement('file');
            $checkstyle->appendChild($file);
            $file->appendChild(new \DOMAttr('name', $file_name));

            // Write each error to the file
            foreach ($error_list as $error_map) {
                $error = new \DOMElement('error');
                $file->appendChild($error);

                // Write each element of the error as an attribute
                // of the error
                $error->appendChild(
                    new \DOMAttr('line', htmlspecialchars((string)$error_map['line'], ENT_NOQUOTES, 'UTF-8'))
                );

                // Map phan severity to Jenkins/Checkstyle severity levels
                switch ($error_map['severity']) {
                    case 'low':
                        $level = 'info';
                        break;
                    case 'critical':
                        $level = 'error';
                        break;
                    case 'normal':
                    default:
                        $level = 'warning';
                        break;
                }

                $error->appendChild(
                    new \DOMAttr('severity', htmlspecialchars((string)$level, ENT_NOQUOTES, 'UTF-8'))
                );

                $error->appendChild(
                    new \DOMAttr('message', htmlspecialchars((string)$error_map['message'], ENT_NOQUOTES, 'UTF-8'))
                );

                $error->appendChild(
                    new \DOMAttr('source', htmlspecialchars((string)$error_map['source'], ENT_NOQUOTES, 'UTF-8'))
                );
            }
        }

        $document->formatOutput = true;
        $this->output->write($document->saveXML());
        $this->files = [];
    }

    /**
     * @param OutputInterface $output
     */
    public function configureOutput(OutputInterface $output)
    {
        $this->output = $output;
    }
}
<?php declare(strict_types = 1);
namespace Phan\Output\Printer;

use Phan\Issue;
use Phan\IssueInstance;
use Phan\Output\BufferedPrinterInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class CodeClimatePrinter implements BufferedPrinterInterface
{

    const CODECLIMATE_SEVERITY_INFO = 'info';
    const CODECLIMATE_SEVERITY_CRITICAL = 'critical';
    const CODECLIMATE_SEVERITY_NORMAL = 'normal';

    /** @var  OutputInterface */
    private $output;

    /** @var array<int,array> */
    private $messages = [];

    /** @param IssueInstance $instance */
    public function print(IssueInstance $instance)
    {
        $this->messages[] = [
            'type' => 'issue',
            'check_name' => $instance->getIssue()->getType(),
            'description' => $instance->getMessage(),
            'categories' => ['Bug Risk'],
            'severity' => self::mapSeverity($instance->getIssue()->getSeverity()),
            'location' => [
                'path' => preg_replace('/^\/code\//', '', $instance->getFile()),
                'lines' => [
                    'begin' => $instance->getLine(),
                    'end' => $instance->getLine(),
                ],
            ],
        ];
    }

    /**
     * @param int $rawSeverity
     * @return string
     */
    private static function mapSeverity(int $rawSeverity):string
    {
        $severity = self::CODECLIMATE_SEVERITY_INFO;
        switch ($rawSeverity) {
            case Issue::SEVERITY_CRITICAL:
                $severity = self::CODECLIMATE_SEVERITY_CRITICAL;
                break;
            case Issue::SEVERITY_NORMAL:
                $severity = self::CODECLIMATE_SEVERITY_NORMAL;
                break;
        }

        return $severity;
    }

    /** flush printer buffer */
    public function flush()
    {

        // See https://github.com/codeclimate/spec/blob/master/SPEC.md#output
        // for details on the CodeClimate output format
        foreach ($this->messages as $message) {
            $encodedMessage = json_encode($message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\0";
            $this->output->write($encodedMessage, false, OutputInterface::OUTPUT_RAW);
        }
        $this->messages = [];
    }

    /**
     * @param OutputInterface $output
     */
    public function configureOutput(OutputInterface $output)
    {
        $this->output = $output;
    }
}
<?php declare(strict_types = 1);
namespace Phan\Output\Printer;

use Phan\Issue;
use Phan\IssueInstance;
use Phan\Output\IssuePrinterInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class PylintPrinter implements IssuePrinterInterface
{
    /** @var OutputInterface */
    private $output;

    /** @param IssueInstance $instance */
    public function print(IssueInstance $instance)
    {
        $message = sprintf(
            "%s: %s",
            $instance->getIssue()->getType(),
            $instance->getMessage()
        );
        $line = sprintf(
            "%s:%d: [%s] %s",
            $instance->getFile(),
            $instance->getLine(),
            self::getSeverityCode($instance),
            $message
        );

        $this->output->writeln($line);
    }

    public static function getSeverityCode(IssueInstance $instance) : string
    {
        $issue = $instance->getIssue();
        $categoryId = $issue->getTypeId();
        switch ($issue->getSeverity()) {
            case Issue::SEVERITY_LOW:
                return 'C' . $categoryId;
            case Issue::SEVERITY_NORMAL:
                return 'W' . $categoryId;
            case Issue::SEVERITY_CRITICAL:
                return 'E' . $categoryId;
            default:
                throw new \AssertionError("Unrecognized severity for " . __METHOD__ . ": " . $issue->getSeverity());
        }
    }

    /**
     * @param OutputInterface $output
     */
    public function configureOutput(OutputInterface $output)
    {
        $this->output = $output;
    }
}
<?php

namespace Phan\Output;

use Phan\Issue;
use Phan\Config;
use Phan\Language\FQSEN;
use Phan\Language\Type;
use Phan\Language\UnionType;

// Colorizing codes are based on https://github.com/kevinlebrun/colors.php/
class Colorizing
{
    const STYLES = [
        'none'             => '0',  // Alias of 'reset'
        'reset'            => '0',  // Use 'reset' for the absence of color.
        'bold'             => '1',
        'dark'             => '2',
        'italic'           => '3',
        'underline'        => '4',
        'blink'            => '5',
        'reverse'          => '7',
        'concealed'        => '8',
        'default'          => '39',
        'black'            => '30',
        'red'              => '31',
        'green'            => '32',
        'yellow'           => '33',
        'blue'             => '34',
        'magenta'          => '35',
        'cyan'             => '36',
        'light_gray'       => '37',
        'dark_gray'        => '90',
        'light_red'        => '91',
        'light_green'      => '92',
        'light_yellow'     => '93',
        'light_blue'       => '94',
        'light_magenta'    => '95',
        'light_cyan'       => '96',
        'white'            => '97',
        'bg_default'       => '49',
        'bg_black'         => '40',
        'bg_red'           => '41',
        'bg_green'         => '42',
        'bg_yellow'        => '43',
        'bg_blue'          => '44',
        'bg_magenta'       => '45',
        'bg_cyan'          => '46',
        'bg_light_gray'    => '47',
        'bg_dark_gray'     => '100',
        'bg_light_red'     => '101',
        'bg_light_green'   => '102',
        'bg_light_yellow'  => '103',
        'bg_light_blue'    => '104',
        'bg_light_magenta' => '105',
        'bg_light_cyan'    => '106',
        'bg_white'         => '107',
    ];

    const ESC_PATTERN = "\033[%sm";
    const ESC_RESET = "\033[0m";

    // NOTE: Keep sorted and in sync with Issue::uncolored_format_string_for_template
    // By using 'color_scheme' in .phan/config.php, these settings can be overridden
    const DEFAULT_COLOR_FOR_TEMPLATE = [
        'CLASS'         => 'green',
        'CLASSLIKE'     => 'green',
        'COMMENT'       => 'light_green',
        'CONST'         => 'light_red',
        'COUNT'         => 'light_magenta',
        'DETAILS'       => 'light_green',
        'FILE'          => 'light_cyan',
        'FUNCTIONLIKE'  => 'light_yellow',
        'FUNCTION'      => 'light_yellow',
        'INDEX'         => 'light_magenta',
        'INTERFACE'     => 'green',
        'ISSUETYPE'     => 'light_yellow',  // used by Phan\Output\Printer, for minor issues
        'ISSUETYPE_CRITICAL' => 'red',  // for critical issues, e.g. "PhanUndeclaredMethod"
        'ISSUETYPE_NORMAL' => 'light_red',  // for normal issues
        'LINE'          => 'light_gray',
        'METHOD'        => 'light_yellow',
        'NAMESPACE'     => 'green',
        'OPERATOR'      => 'red',
        'PARAMETER'     => 'cyan',
        'PROPERTY'      => 'cyan',
        'SCALAR'        => 'light_magenta',
        'STRING_LITERAL' => 'light_magenta',
        'TYPE'          => 'light_gray',
        'TRAIT'         => 'green',
        'VARIABLE'      => 'light_cyan',
    ];

    /**
     * @var array|null - Lazily initialized from Config
     */
    private static $color_scheme = null;

    /**
     * @param string $template
     * @param array<int,int|string|FQSEN|Type|UnionType> $template_parameters
     */
    public static function colorizeTemplate(
        string $template,
        array $template_parameters
    ) : string {
        $i = 0;
        /** @param array<int,string> $matches */
        return preg_replace_callback('/{([A-Z_]+)}|%[sdf]/', function (array $matches) use ($template, $template_parameters, &$i) : string {
            $j = $i++;
            if ($j >= count($template_parameters)) {
                error_log("Missing argument for colorized output ($template), offset $j");
                return '(MISSING)';
            }
            $arg = $template_parameters[$j];
            $format_str = $matches[0];
            if ($format_str[0] === '%') {
                return sprintf($format_str, $arg);
            }
            $template = $matches[1];
            return self::colorizeField($template, $arg);
        }, $template);
    }

    /**
     * @param string $template_type (A key of _uncolored_format_string_for_template, e.g. "FILE")
     * @param int|string|FQSEN|Type|UnionType $arg (Argument for format string, e.g. a type name, method fqsen, line number, etc.)
     * @return string - Colorized for unix terminals.
     */
    public static function colorizeField(string $template_type, $arg) : string
    {
        $fmt_directive = Issue::uncolored_format_string_for_template[$template_type] ?? null;
        if ($fmt_directive === null) {
            error_log(sprintf(
                "Unknown template type '%s'. Known template types: %s",
                $template_type,
                implode(', ', array_keys(Issue::uncolored_format_string_for_template))
            ));
            return (string)$arg;
        }
        // TODO: Add more complicated color coding, e.g. MyClass::method should have the option for multiple colors.
        // TODO: Allow choosing color schemes via .phan/config.php
        $arg_str = sprintf($fmt_directive, (string)$arg);
        $color = self::colorForTemplate($template_type);
        if ($color === null) {
            error_log("No color information for template type $template_type");
            return $arg_str;
        }
        // TODO: Could extend this to support background colors.
        $color_code = self::STYLES[$color] ?? null;
        if ($color_code === null) {
            error_log("Invalid color name ($color) for template type $template_type");
            return $arg_str;
        }
        if ($color_code == '0') {
            return $arg_str;
        }
        return sprintf(self::ESC_PATTERN, $color_code) . ((string) $arg) . self::ESC_RESET;
    }

    /**
     * @return ?string - null if there is no valid color
     */
    private static function colorForTemplate(string $template_type)
    {
        if (self::$color_scheme === null) {
            self::initColorScheme();
        }
        return self::$color_scheme[$template_type] ?? null;
    }

    /**
     * Initialize the color scheme, merging it with Config::color_scheme
     */
    private static function initColorScheme()
    {
        self::$color_scheme = self::DEFAULT_COLOR_FOR_TEMPLATE;
        foreach (Config::getValue('color_scheme') ?? [] as $template_type => $color_name) {
            if (!\is_string($color_name) || !\array_key_exists($color_name, self::STYLES)) {
                error_log("Invalid color name ($color_name)");
                continue;
            }
            if (!\array_key_exists($template_type, Colorizing::DEFAULT_COLOR_FOR_TEMPLATE)) {
                error_log("Unknown template_type ($template_type)");
                continue;
            }
            self::$color_scheme[$template_type] = $color_name;
        }
    }
}
<?php declare(strict_types = 1);
namespace Phan\Output;

use Phan\IssueInstance;
use Symfony\Component\Console\Output\OutputInterface;

interface IssuePrinterInterface
{

    /** @param IssueInstance $instance */
    public function print(IssueInstance $instance);

    /**
     * @param OutputInterface $output
     */
    public function configureOutput(OutputInterface $output);
}
<?php declare(strict_types=1);
namespace Phan\Output\Collector;

use Phan\IssueInstance;
use Phan\Output\IssueCollectorInterface;

/**
 * A ParallelChildCollector will collect issues as normal,
 * but will send them all to a message queue for collection
 * by a ParallelParentCollector instead of holding on to
 * them itself.
 */
class ParallelChildCollector implements IssueCollectorInterface
{
    /**
     * @var resource
     */
    private $message_queue_resource;

    /**
     * Create a ParallelChildCollector that will collect
     * issues as normal, but emit them to a message queue
     * for collection by a
     * \Phan\Output\Collector\ParallelParentCollector.
     */
    public function __construct()
    {
        \assert(
            extension_loaded('sysvsem'),
            'PHP must be compiled with --enable-sysvsem in order to use -j(>=2).'
        );

        \assert(
            extension_loaded('sysvmsg'),
            'PHP must be compiled with --enable-sysvmsg in order to use -j(>=2).'
        );

        // Create a message queue for this process group
        $message_queue_key = posix_getpgid(posix_getpid());
        $this->message_queue_resource =
            msg_get_queue($message_queue_key);
    }

    /**
     * Collect issue
     * @param IssueInstance $issue
     */
    public function collectIssue(IssueInstance $issue)
    {
        $error_code = 0;

        // Send messages along to the message queue
        // that is hopefully being listened to by a
        // ParallelParentCollector.
        $success = msg_send(
            $this->message_queue_resource,
            ParallelParentCollector::MESSAGE_TYPE_ISSUE,
            $issue,
            true,
            true,
            $error_code
        );

        // Send a signal to the parent process that we
        // sent a message and it may wish to collect it
        // posix_kill(posix_getppid(), SIGUSR1);
        // pcntl_signal_dispatch();

        // Make sure that the message was successfully
        // sent
        \assert(
            $success,
            "msg_send failed with error code '$error_code'"
        );
    }

    /**
     * @return array<int,IssueInstance>
     */
    public function getCollectedIssues():array
    {
        // This collector should not be used for collecting
        // issues. Instead, it proxies all messages on to a
        // message queue.
        return [];
    }

    /**
     * Remove all collected issues (from the parse phase) for the given file paths.
     * Called from daemon mode.
     *
     * @param string[] $files @phan-unused-param - the relative paths to those files
     * @return void
     *
     * @override
     */
    public function removeIssuesForFiles(array $files)
    {
        return;  // Never going to be called - daemon mode isn't combined with parallel execution.
    }

    /**
     * This method has not effect on a ParallelChildCollector.
     */
    public function reset()
    {
    }
}
<?php declare(strict_types = 1);
namespace Phan\Output\Collector;

use Phan\IssueInstance;
use Phan\Output\Filter\AnyFilter;
use Phan\Output\IssueCollectorInterface;
use Phan\Output\IssueFilterInterface;

final class BufferingCollector implements IssueCollectorInterface
{

    /** @var array<string,IssueInstance> */
    private $issues = [];

    /** @var IssueFilterInterface|null */
    private $filter;

    /**
     * BufferingCollector constructor.
     * @param ?IssueFilterInterface $filter
     */
    public function __construct(IssueFilterInterface $filter = null)
    {
        $this->filter = $filter ?? (new AnyFilter());
    }

    /**
     * Collect issue
     * @param IssueInstance $issue
     */
    public function collectIssue(IssueInstance $issue)
    {
        if (!$this->filter->supports($issue)) {
            return;
        }

        $this->issues[$this->formatSortableKey($issue)] = $issue;
    }

    /**
     * @param IssueInstance $issue
     * @return string
     */
    private function formatSortableKey(IssueInstance $issue)
    {

        // This needs to be a sortable key so that output
        // is in the expected order
        return implode('|', [
            $issue->getFile(),
            str_pad((string)$issue->getLine(), 5, '0', STR_PAD_LEFT),
            $issue->getIssue()->getType(),
            $issue->getMessage()
        ]);
    }

    /**
     * @return array<string,IssueInstance>
     */
    public function getCollectedIssues():array
    {
        ksort($this->issues);
        return array_values($this->issues);
    }

    /**
     * @return void
     */
    public function flush()
    {
        $this->issues = [];
    }

    /**
     * Remove all collected issues (from the parse phase) for the given file paths.
     * Called from daemon mode.
     *
     * @param array<int,string> $files - the relative paths to those files
     * @return void
     */
    public function removeIssuesForFiles(array $files)
    {
        $file_set = array_flip($files);
        foreach ($this->issues as $key => $issue) {
            if (\array_key_exists($issue->getFile(), $file_set)) {
                unset($this->issues[$key]);
            }
        }
    }

    /**
     * Removes all collected issues.
     */
    public function reset()
    {
        $this->issues = [];
    }
}
<?php declare(strict_types=1);
namespace Phan\Output\Collector;

use Phan\IssueInstance;
use Phan\Output\IssueCollectorInterface;

/**
 * A ParallelParentCollector collects issues as normal proxying
 * them on to a given base collector, but will also listen to
 * a message queue for issues emitted by other processes (via
 * a ParallelChildCollector).
 */
class ParallelParentCollector implements IssueCollectorInterface
{
    const MESSAGE_TYPE_ISSUE = 1;

    /**
     * @var IssueCollectorInterface
     * All issues will be proxied to the base collector for
     * filtering and output.
     */
    private $base_collector;

    /**
     * @var Resource
     * A message queue that will be listened to for incoming
     * messages
     */
    private $message_queue_resource;

    /**
     * Create a ParallelParentCollector that will collect
     * issues via a message queue. You'll want to do the
     * real collection via
     * \Phan\Output\Collector\ParallelChildCollector.
     *
     * @param IssueCollectorInterface $base_collector
     * A collector must be given to which collected issues
     * will be passed
     */
    public function __construct(
        IssueCollectorInterface $base_collector
    ) {
        \assert(
            extension_loaded('sysvsem'),
            'PHP must be compiled with --enable-sysvsem in order to use -j(>=2).'
        );

        \assert(
            extension_loaded('sysvmsg'),
            'PHP must be compiled with --enable-sysvmsg in order to use -j(>=2).'
        );

        $this->base_collector = $base_collector;

        // Create a message queue for this process group
        $message_queue_key = posix_getpgid(posix_getpid());
        $this->message_queue_resource =
            msg_get_queue($message_queue_key);

        // Listen for ALARMS that indicate we should flush
        // the queue
        pcntl_sigprocmask(SIG_UNBLOCK, array(SIGUSR1), $old);
        pcntl_signal(SIGUSR1, function () {
            $this->readQueuedIssues();
        });
    }

    public function __destruct()
    {
        // Shut down and remove the queue
        $success = msg_remove_queue(
            $this->message_queue_resource
        );

        \assert(
            $success,
            "Failed to remove queue with ID {$this->message_queue_resource}"
        );
    }

    /**
     * Collect issue
     * @param IssueInstance $issue
     */
    public function collectIssue(IssueInstance $issue)
    {
        $this->base_collector->collectIssue($issue);
    }

    /**
     *
     */
    public function readQueuedIssues()
    {
        // Read the entire queue and write all issues to the
        // base collector

        // Get the status of the queue
        $status = msg_stat_queue(
            $this->message_queue_resource
        );

        // Read messages while there are still messages on
        // the queue
        while ($status['msg_qnum'] > 0) {
            $message = null;
            $message_type = 0;

            // Receive the message, populating $message by
            // reference
            if (false !== msg_receive(
                $this->message_queue_resource,
                self::MESSAGE_TYPE_ISSUE,
                $message_type,
                2048,
                $message,
                true
            )) {
                \assert(
                    $message instanceof IssueInstance,
                    "Messages must be of type IssueInstance."
                );

                // Cast the message to an IssueInstance
                if ($message instanceof IssueInstance) {
                    $this->collectIssue($message);
                }
            } else {
                break;
            }

            $status = msg_stat_queue(
                $this->message_queue_resource
            );
        }
    }

    /**
     * Remove all collected issues (from the parse phase) for the given file paths.
     * Called from daemon mode.
     *
     * @param string[] $files - the relative paths to those files (@phan-unused-param)
     * @return void
     */
    public function removeIssuesForFiles(array $files)
    {
        return;  // Never going to be called - daemon mode isn't combined with parallel execution.
    }

    /**
     * @return array<int,IssueInstance>
     */
    public function getCollectedIssues() : array
    {
        // Read any available issues waiting on the
        // message queue
        $this->readQueuedIssues();

        // Return everything on the base collector
        return $this->base_collector->getCollectedIssues();
    }

    /**
     * This method has not effect on a ParallelParentCollector.
     */
    public function reset()
    {
    }
}
<?php declare(strict_types=1);
namespace Phan\Output;

use Phan\IssueInstance;

interface IssueFilterInterface
{

    /**
     * @param IssueInstance $issue
     * @return bool
     */
    public function supports(IssueInstance $issue):bool;
}
<?php declare(strict_types=1);
namespace Phan;

/**
 * Fork off to n-processes and divide up tasks between
 * each process.
 */
class ForkPool
{

    /** @var array<int,int> */
    private $child_pid_list = [];

    /** @var array<int,resource> */
    private $read_streams = [];

    /** @var bool */
    private $did_have_error = false;

    /**
     * @param array $process_task_data_iterator
     * An array of task data items to be divided up among the
     * workers. The size of this is the number of forked processes.
     *
     * @param \Closure $startup_closure
     * A closure to execute upon starting a child
     *
     * @param \Closure $task_closure
     * A method to execute on each task data.
     * This closure must return an array (to be gathered).
     *
     * @param \Closure $shutdown_closure
     * A closure to execute upon shutting down a child
     */
    public function __construct(
        array $process_task_data_iterator,
        \Closure $startup_closure,
        \Closure $task_closure,
        \Closure $shutdown_closure
    ) {

        $pool_size = count($process_task_data_iterator);

        \assert(
            $pool_size > 1,
            'The pool size must be >= 2 to use the fork pool.'
        );

        \assert(
            extension_loaded('pcntl'),
            'The pcntl extension must be loaded in order for Phan to be able to fork.'
        );

        // We'll keep track of if this is the parent process
        // so that we can tell who will be doing the waiting
        $is_parent = false;

        // Fork as many times as requested to get the given
        // pool size
        for ($proc_id = 0; $proc_id < $pool_size; $proc_id++) {
            // Create an IPC socket pair.
            $sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
            if (!$sockets) {
                error_log("unable to create stream socket pair");
                exit(EXIT_FAILURE);
            }

            // Fork
            $pid = 0;
            if (($pid = pcntl_fork()) < 0) {
                error_log(posix_strerror(posix_get_last_error()));
                exit(EXIT_FAILURE);
            }

            // Parent
            if ($pid > 0) {
                $is_parent = true;
                $this->child_pid_list[] = $pid;
                $this->read_streams[] = self::streamForParent($sockets);
                continue;
            }

            // Child
            if ($pid === 0) {
                $is_parent = false;
                break;
            }
        }

        // If we're the parent, return
        if ($is_parent) {
            return;
        }

        // Get the write stream for the child.
        $write_stream = self::streamForChild($sockets);

        // Execute anything the children wanted to execute upon
        // starting up
        $startup_closure();

        // Get the work for this process
        $task_data_iterator = array_values($process_task_data_iterator)[$proc_id];
        foreach ($task_data_iterator as $i => $task_data) {
            $task_closure($i, $task_data);
        }

        // Execute each child's shutdown closure before
        // exiting the process
        $results = $shutdown_closure();

        // Serialize this child's produced results and send them to the parent.
        fwrite($write_stream, serialize($results ?: []));

        fclose($write_stream);

        // Children exit after completing their work
        exit(EXIT_SUCCESS);
    }

    /**
     * Prepare the socket pair to be used in a parent process and
     * return the stream the parent will use to read results.
     *
     * @param array<int,resource> $sockets the socket pair for IPC
     * @return resource
     */
    private static function streamForParent(array $sockets)
    {
        list($for_read, $for_write) = $sockets;

        // The parent will not use the write channel, so it
        // must be closed to prevent deadlock.
        fclose($for_write);

        // stream_select will be used to read multiple streams, so these
        // must be set to non-blocking mode.
        if (!stream_set_blocking($for_read, false)) {
            error_log('unable to set read stream to non-blocking');
            exit(EXIT_FAILURE);
        }

        return $for_read;
    }

    /**
     * Prepare the socket pair to be used in a child process and return
     * the stream the child will use to write results.
     *
     * @param array<int,resource> $sockets the socket pair for IPC.
     * @return resource
     */
    private static function streamForChild(array $sockets)
    {
        list($for_read, $for_write) = $sockets;

        // The while will not use the read channel, so it must
        // be closed to prevent deadlock.
        fclose($for_read);
        return $for_write;
    }

    /**
     * Read the results that each child process has serialized on their write streams.
     * The results are returned in an array, one for each worker. The order of the results
     * is not maintained.
     *
     * @return array
     */
    private function readResultsFromChildren()
    {
        // Create an array of all active streams, indexed by
        // resource id.
        $streams = [];
        foreach ($this->read_streams as $stream) {
            $streams[intval($stream)] = $stream;
        }

        // Create an array for the content received on each stream,
        // indexed by resource id.
        $content = array_fill_keys(array_keys($streams), '');

        // Read the data off of all the stream.
        while (count($streams) >0) {
            $needs_read = array_values($streams);
            $needs_write = null;
            $needs_except = null;

            // Wait for data on at least one stream.
            $num = stream_select($needs_read, $needs_write, $needs_except, null /* no timeout */);
            if ($num === false) {
                error_log("unable to select on read stream");
                exit(EXIT_FAILURE);
            }

            // For each stream that was ready, read the content.
            foreach ($needs_read as $file) {
                $buffer = fread($file, 1024);
                if (strlen($buffer) > 0) {
                    $content[intval($file)] .= $buffer;
                }

                // If the stream has closed, stop trying to select on it.
                if (feof($file)) {
                    fclose($file);
                    unset($streams[intval($file)]);
                }
            }
        }

        // Unmarshal the content into its original form.
        return array_values(array_map(function ($data) {
            $result = unserialize($data);
            if (!\is_array($result)) {
                error_log("Child terminated without returning a serialized array (threw or crashed - not enough memory?): response type=" . gettype($result));
                $this->did_have_error = true;
            }
            return $result;
        }, $content));
    }

    /**
     * Wait for all child processes to complete
     */
    public function wait()
    {

        // Read all the streams from child processes into an array.
        $content = $this->readResultsFromChildren();

        // Wait for all children to return
        foreach ($this->child_pid_list as $child_pid) {
            if (pcntl_waitpid($child_pid, $status) < 0) {
                error_log(posix_strerror(posix_get_last_error()));
            }

            // Check to see if the child died a graceful death
            $status = 0;
            if (pcntl_wifsignaled($status)) {
                $return_code = pcntl_wexitstatus($status);
                $term_sig = pcntl_wtermsig($status);
                $this->did_have_error = true;
                error_log("Child terminated with return code $return_code and signal $term_sig");
            }
        }

        return $content;
    }

    /**
     * Returns true if this had an error, e.g. due to memory limits or due to a child process crashing.
     */
    public function didHaveError() : bool
    {
        return $this->did_have_error;
    }
}
<?php declare(strict_types=1);
namespace Phan\CodeBase;

use Phan\Language\Element\ClassConstant;
use Phan\Language\Element\Method;
use Phan\Language\Element\Property;

/**
 * Maps for elements associated with an individual class
 */
class ClassMap
{
    /**
     * @var array<string,ClassConstant>
     * A map from name to ClassConstant
     */
    private $class_constant_map = [];

    /**
     * @var array<string,Property>
     * A map from name to Property
     */
    private $property_map = [];

    /**
     * @var array<string,Method>
     * A map from name to Method
     */
    private $method_map = [];

    /**
     * @return void
     */
    public function addClassConstant(ClassConstant $constant)
    {
        $this->class_constant_map[
            $constant->getFQSEN()->getNameWithAlternateId()
        ] = $constant;
    }

    /**
     * @return bool
     */
    public function hasClassConstantWithName(string $name) : bool
    {
        return !empty($this->class_constant_map[$name]);
    }

    public function getClassConstantByName(string $name) : ClassConstant
    {
        return $this->class_constant_map[$name];
    }

    /**
     * @return array<string,ClassConstant>
     */
    public function getClassConstantMap() : array
    {
        return $this->class_constant_map;
    }

    /**
     * @return void
     */
    public function addProperty(Property $property)
    {
        $this->property_map[
            $property->getFQSEN()->getNameWithAlternateId()
        ] = $property;
    }

    /**
     * @return bool
     */
    public function hasPropertyWithName(string $name) : bool
    {
        return !empty($this->property_map[$name]);
    }

    /**
     * @return Property
     */
    public function getPropertyByName(string $name) : Property
    {
        return $this->property_map[$name];
    }

    /**
     * @return array<string,Property>
     */
    public function getPropertyMap() : array
    {
        return $this->property_map;
    }

    /**
     * @return void
     */
    public function addMethod(Method $method)
    {
        $this->method_map[\strtolower(
            $method->getFQSEN()->getNameWithAlternateId()
        )] = $method;
    }

    /**
     * @return bool
     */
    public function hasMethodWithName(string $name) : bool
    {
        return isset($this->method_map[\strtolower($name)]);
    }

    /**
     * @return Method
     */
    public function getMethodByName(string $name) : Method
    {
        return $this->method_map[\strtolower($name)];
    }

    /**
     * @return array<string,Method>
     */
    public function getMethodMap() : array
    {
        return $this->method_map;
    }
}
<?php declare(strict_types=1);
namespace Phan\CodeBase;

use Phan\CodeBase;
use Phan\Daemon;
use Phan\Phan;

/**
 * UndoTracker maps a file path to a list of operations(e.g. Closures) that must be executed to
 * remove all traces of a file from the CodeBase, etc. if a file was removed or edited.
 * This is done to support running phan in daemon mode.
 * - Files will have to be re-parsed to get the new function signatures, check for new parse/analysis errors,
 *   and to update the class/function/method/property/constant/etc. definitions that would have to be created.
 *
 * If a file is edited, its contributions are undone, then it is parsed yet again.
 *
 * (We don't garbage collect reference cycles, so this attempts to work in a way that avoids cycles.
 *  Haven't verified that it does that as expected, yet)
 */
class UndoTracker
{

    /**
     * @var ?string absolute path to currently parsed file, when in parse phase.
     * TODO: Does the Context->getFile() make keeping this redundant?
     */
    private $current_parsed_file;

    /**
     * @var array<string,array<int,Closure>> operations to undo for a current path
     * @phan-var array<string,array<int,Closure(CodeBase)>>
     */
    private $undoOperationsForPath = [];

    /**
     * @var array<string,string> Maps file paths to the modification dates and file size of the paths. - On ext4, milliseconds are available, but php APIs all return seconds.
     */
    private $fileModificationState = [];

    public function __construct()
    {
    }

    /**
     * @return array<int,string> - The list of files which are successfully parsed.
     * This changes whenever the file list is reloaded from disk.
     * This also includes files which don't declare classes or functions or globals,
     * because those files use classes/functions/constants.
     *
     * (This is the list prior to any analysis exclusion or whitelisting steps)
     */
    public function getParsedFilePathList() : array
    {
        return array_keys($this->fileModificationState);
    }

    /**
     * @return int The size of $this->getParsedFilePathList()
     */
    public function getParsedFilePathCount() : int
    {
        return count($this->fileModificationState);
    }

    /**
     * @param ?string $current_parsed_file
     * @return void
     */
    public function setCurrentParsedFile($current_parsed_file)
    {
        if (\is_string($current_parsed_file)) {
            Daemon::debugf("Recording file modification state for %s", $current_parsed_file);
            $this->fileModificationState[$current_parsed_file] = self::getFileState($current_parsed_file);
        }
        $this->current_parsed_file = $current_parsed_file;
    }


    /**
     * @return ?string - This string should change when the file is modified. Returns null if the file somehow doesn't exist
     */
    public static function getFileState(string $path)
    {
        clearstatcache(true, $path);  // TODO: does this work properly with symlinks? seems to.
        $real = realpath($path);
        if (!$real) {
            return null;
        }
        if (!file_exists($real)) {
            return null;
        }
        $stat = @stat($real);  // Double check: suppress to prevent phan's error_handler from terminating on error.
        if (!$stat) {
            return null;  // It was missing or unreadable.
        }
        return sprintf('%d_%d', $stat['mtime'], $stat['size']);
    }

    /**
     * Called when a file is unparseable.
     * Removes the classes and functions, etc. from an older version of the file, if one exists.
     * @return void
     */
    public function recordUnparseableFile(CodeBase $code_base, string $current_parsed_file)
    {
        Daemon::debugf("%s was unparseable, had a syntax error", $current_parsed_file);
        Phan::getIssueCollector()->removeIssuesForFiles([$current_parsed_file]);
        $this->undoFileChanges($code_base, $current_parsed_file);
        unset($this->fileModificationState[$current_parsed_file]);
    }

    /**
     * Undoes all of the changes for the relative path at $path
     * @return void
     */
    private function undoFileChanges(CodeBase $code_base, string $path)
    {
        Daemon::debugf("Undoing file changes for $path");
        foreach ($this->undoOperationsForPath[$path] ?? [] as $undo_operation) {
            $undo_operation($code_base);
        }
        unset($this->undoOperationsForPath[$path]);
    }

    /**
     * @param \Closure $undo_operation - a closure expecting 1 param - inner. It undoes a change caused by a parsed file.
     * Ideally, this would extend to all changes. (e.g. including dead code detection)
     *
     * @return void
     */
    public function recordUndo(\Closure $undo_operation)
    {
        $file = $this->current_parsed_file;
        if (!\is_string($file)) {
            throw new \Error("Called recordUndo in CodeBaseMutable, but not parsing a file");
        }
        if (!isset($this->undoOperationsForPath[$file])) {
            $this->undoOperationsForPath[$file] = [];
        }
        $this->undoOperationsForPath[$file][] = $undo_operation;
    }

    /**
     * @param CodeBase $code_base - code base owning this tracker
     * @param array<int,string> $new_file_list
     * @param array<string,string> $file_mapping_contents
     * @return array<int,string> - Subset of $new_file_list which changed on disk and has to be parsed again. Automatically unparses the old versions of files which were modified.
     */
    public function updateFileList(CodeBase $code_base, array $new_file_list, array $file_mapping_contents)
    {
        $new_file_set = [];
        foreach ($new_file_list as $path) {
            $new_file_set[$path] = true;
        }
        foreach ($file_mapping_contents as $path => $_) {
            $new_file_set[$path] = true;
        }
        unset($new_file_list);
        $removed_file_list = [];
        $changed_or_added_file_list = [];
        foreach ($new_file_set as $path => $_) {
            if (!isset($this->fileModificationState[$path])) {
                $changed_or_added_file_list[] = $path;
            }
        }
        foreach ($this->fileModificationState as $path => $state) {
            if (!isset($new_file_set[$path])) {
                $this->undoFileChanges($code_base, $path);
                $removed_file_list[] = $path;
                unset($this->fileModificationState[$path]);
                continue;
            }
            if (isset($file_mapping_contents[$path])) {
                // TODO: Move updateFileList to be called before fork()?
                $newState = 'daemon:' . sha1($file_mapping_contents[$path]);
            } else {
                $newState = self::getFileState($path);
            }
            if ($newState !== $state) {
                $removed_file_list[] = $path;
                $this->undoFileChanges($code_base, $path);
                // TODO: This will call stat() twice as much as necessary for the modified files. Not important.
                unset($this->fileModificationState[$path]);
                if ($newState !== null) {
                    $changed_or_added_file_list[] = $path;
                }
            }
        }
        if (count($removed_file_list) > 0) {
            Phan::getIssueCollector()->removeIssuesForFiles($removed_file_list);
        }
        return $changed_or_added_file_list;
    }

    /**
     * @param CodeBase $code_base - code base owning this tracker
     * @param string $file_path
     * @return bool - true if the file existed
     */
    public function beforeReplaceFileContents(CodeBase $code_base, string $file_path)
    {
        if (!isset($this->fileModificationState[$file_path])) {
            Daemon::debugf("Tried to replace contents of '$file_path', but that path does not yet exist");
            return false;
        }
        Phan::getIssueCollector()->removeIssuesForFiles([$file_path]);
        $this->undoFileChanges($code_base, $file_path);
        unset($this->fileModificationState[$file_path]);
        return true;
    }
}
<?php declare(strict_types=1);

// Phan does a ton of GC and this offers a major speed
// improvement if your system can handle it (which it
// should be able to)
gc_disable();

// Check the environment to make sure Phan can run successfully
require_once __DIR__ . '/requirements.php';
require_once __DIR__ . '/Phan/Bootstrap.php';

use Phan\CLI;
use Phan\Prep;

// Create our CLI interface and load arguments
$cli = new CLI();

$file_list = $cli->getFileList();

// This is an example visitor. Do whatever you like here
// to scan all nodes.
$visit_node = function (\ast\Node $node, string $file_path) {

    // Take a look at Phan\AST\Visitor\Element to see all
    // of the kinds of nodes
    if ($node->kind == \ast\AST_CLASS_CONST) {
        // Debug::printNode($node);

        if (\is_string($node->children['const'])) {
            $name = $node->children['const'];
            if (preg_match('/.*SEARCH.*/', $name)) {
                print "$file_path:{$node->lineno} $name\n";
            }
        }
    }
};

// Apply the closure to every single node in the
// code base
Prep::scanFileList($file_list, $visit_node);
<?php declare(strict_types = 1);

// Grab these before we define our own classes
$internal_class_name_list = get_declared_classes();
$internal_interface_name_list = get_declared_interfaces();
$internal_trait_name_list = get_declared_traits();
$internal_function_name_list = get_defined_functions()['internal'];

if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
  // This is the normal path when Phan is installed only in the scope of a project.
    require_once __DIR__ . '/../vendor/autoload.php';
} else {
  // This is the path to autoload.php when Phan is installed globally.
    require_once __DIR__ . '/../../../autoload.php';
}

use Composer\XdebugHandler\XdebugHandler;
use Phan\CodeBase;
use Phan\Library\StderrLogger;

// Automatically restart if xdebug is loaded
if (extension_loaded('xdebug')) {
    // Restart if xdebug is loading, unless the environment variable PHAN_ALLOW_XDEBUG is set.
    $handler = new XdebugHandler('phan');
    if (!getenv('PHAN_DISABLE_XDEBUG_WARN')) {
        fwrite(STDERR, <<<EOT
[info] Disabling xdebug: Phan is around five times as slow when xdebug is enabled (xdebug only makes sense when debugging Phan itself)
[info] To run Phan with xdebug, set the environment variable PHAN_ALLOW_XDEBUG to 1.
[info] To disable this warning, set the environment variable PHAN_DISABLE_XDEBUG_WARN to 1.
[info] To include function signatures of xdebug, see .phan/internal_stubs/xdebug.phan_php

EOT
        );
        $handler->setLogger(new StderrLogger());
    }

    $handler->check();
}

return new CodeBase(
    $internal_class_name_list,
    $internal_interface_name_list,
    $internal_trait_name_list,
    CodeBase::getPHPInternalConstantNameList(),
    $internal_function_name_list
);
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Mbstring as p;

if (!function_exists('mb_strlen')) {
    define('MB_CASE_UPPER', 0);
    define('MB_CASE_LOWER', 1);
    define('MB_CASE_TITLE', 2);

    function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); }
    function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); }
    function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); }
    function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); }
    function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); }
    function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); }
    function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); }
    function mb_language($lang = null) { return p\Mbstring::mb_language($lang); }
    function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
    function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
    function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); }
    function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); }
    function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); }
    function mb_parse_str($s, &$result = array()) { parse_str($s, $result); }
    function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); }
    function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); }
    function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); }
    function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); }
    function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); }
    function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); }
    function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); }
    function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); }
    function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); }
    function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); }
    function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); }
    function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); }
    function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); }
    function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
    function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); }
    function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); }
    function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); }
    function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); }
    function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); }
    function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); }
}
if (!function_exists('mb_chr')) {
    function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); }
    function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); }
    function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); }
}
<?php

static $data = array (
  'a' => 'A',
  'b' => 'B',
  'c' => 'C',
  'd' => 'D',
  'e' => 'E',
  'f' => 'F',
  'g' => 'G',
  'h' => 'H',
  'i' => 'I',
  'j' => 'J',
  'k' => 'K',
  'l' => 'L',
  'm' => 'M',
  'n' => 'N',
  'o' => 'O',
  'p' => 'P',
  'q' => 'Q',
  'r' => 'R',
  's' => 'S',
  't' => 'T',
  'u' => 'U',
  'v' => 'V',
  'w' => 'W',
  'x' => 'X',
  'y' => 'Y',
  'z' => 'Z',
  'µ' => 'Μ',
  'à' => 'À',
  'á' => 'Á',
  'â' => 'Â',
  'ã' => 'Ã',
  'ä' => 'Ä',
  'å' => 'Å',
  'æ' => 'Æ',
  'ç' => 'Ç',
  'è' => 'È',
  'é' => 'É',
  'ê' => 'Ê',
  'ë' => 'Ë',
  'ì' => 'Ì',
  'í' => 'Í',
  'î' => 'Î',
  'ï' => 'Ï',
  'ð' => 'Ð',
  'ñ' => 'Ñ',
  'ò' => 'Ò',
  'ó' => 'Ó',
  'ô' => 'Ô',
  'õ' => 'Õ',
  'ö' => 'Ö',
  'ø' => 'Ø',
  'ù' => 'Ù',
  'ú' => 'Ú',
  'û' => 'Û',
  'ü' => 'Ü',
  'ý' => 'Ý',
  'þ' => 'Þ',
  'ÿ' => 'Ÿ',
  'ā' => 'Ā',
  'ă' => 'Ă',
  'ą' => 'Ą',
  'ć' => 'Ć',
  'ĉ' => 'Ĉ',
  'ċ' => 'Ċ',
  'č' => 'Č',
  'ď' => 'Ď',
  'đ' => 'Đ',
  'ē' => 'Ē',
  'ĕ' => 'Ĕ',
  'ė' => 'Ė',
  'ę' => 'Ę',
  'ě' => 'Ě',
  'ĝ' => 'Ĝ',
  'ğ' => 'Ğ',
  'ġ' => 'Ġ',
  'ģ' => 'Ģ',
  'ĥ' => 'Ĥ',
  'ħ' => 'Ħ',
  'ĩ' => 'Ĩ',
  'ī' => 'Ī',
  'ĭ' => 'Ĭ',
  'į' => 'Į',
  'ı' => 'I',
  'ij' => 'IJ',
  'ĵ' => 'Ĵ',
  'ķ' => 'Ķ',
  'ĺ' => 'Ĺ',
  'ļ' => 'Ļ',
  'ľ' => 'Ľ',
  'ŀ' => 'Ŀ',
  'ł' => 'Ł',
  'ń' => 'Ń',
  'ņ' => 'Ņ',
  'ň' => 'Ň',
  'ŋ' => 'Ŋ',
  'ō' => 'Ō',
  'ŏ' => 'Ŏ',
  'ő' => 'Ő',
  'œ' => 'Œ',
  'ŕ' => 'Ŕ',
  'ŗ' => 'Ŗ',
  'ř' => 'Ř',
  'ś' => 'Ś',
  'ŝ' => 'Ŝ',
  'ş' => 'Ş',
  'š' => 'Š',
  'ţ' => 'Ţ',
  'ť' => 'Ť',
  'ŧ' => 'Ŧ',
  'ũ' => 'Ũ',
  'ū' => 'Ū',
  'ŭ' => 'Ŭ',
  'ů' => 'Ů',
  'ű' => 'Ű',
  'ų' => 'Ų',
  'ŵ' => 'Ŵ',
  'ŷ' => 'Ŷ',
  'ź' => 'Ź',
  'ż' => 'Ż',
  'ž' => 'Ž',
  'ſ' => 'S',
  'ƀ' => 'Ƀ',
  'ƃ' => 'Ƃ',
  'ƅ' => 'Ƅ',
  'ƈ' => 'Ƈ',
  'ƌ' => 'Ƌ',
  'ƒ' => 'Ƒ',
  'ƕ' => 'Ƕ',
  'ƙ' => 'Ƙ',
  'ƚ' => 'Ƚ',
  'ƞ' => 'Ƞ',
  'ơ' => 'Ơ',
  'ƣ' => 'Ƣ',
  'ƥ' => 'Ƥ',
  'ƨ' => 'Ƨ',
  'ƭ' => 'Ƭ',
  'ư' => 'Ư',
  'ƴ' => 'Ƴ',
  'ƶ' => 'Ƶ',
  'ƹ' => 'Ƹ',
  'ƽ' => 'Ƽ',
  'ƿ' => 'Ƿ',
  'Dž' => 'DŽ',
  'dž' => 'DŽ',
  'Lj' => 'LJ',
  'lj' => 'LJ',
  'Nj' => 'NJ',
  'nj' => 'NJ',
  'ǎ' => 'Ǎ',
  'ǐ' => 'Ǐ',
  'ǒ' => 'Ǒ',
  'ǔ' => 'Ǔ',
  'ǖ' => 'Ǖ',
  'ǘ' => 'Ǘ',
  'ǚ' => 'Ǚ',
  'ǜ' => 'Ǜ',
  'ǝ' => 'Ǝ',
  'ǟ' => 'Ǟ',
  'ǡ' => 'Ǡ',
  'ǣ' => 'Ǣ',
  'ǥ' => 'Ǥ',
  'ǧ' => 'Ǧ',
  'ǩ' => 'Ǩ',
  'ǫ' => 'Ǫ',
  'ǭ' => 'Ǭ',
  'ǯ' => 'Ǯ',
  'Dz' => 'DZ',
  'dz' => 'DZ',
  'ǵ' => 'Ǵ',
  'ǹ' => 'Ǹ',
  'ǻ' => 'Ǻ',
  'ǽ' => 'Ǽ',
  'ǿ' => 'Ǿ',
  'ȁ' => 'Ȁ',
  'ȃ' => 'Ȃ',
  'ȅ' => 'Ȅ',
  'ȇ' => 'Ȇ',
  'ȉ' => 'Ȉ',
  'ȋ' => 'Ȋ',
  'ȍ' => 'Ȍ',
  'ȏ' => 'Ȏ',
  'ȑ' => 'Ȑ',
  'ȓ' => 'Ȓ',
  'ȕ' => 'Ȕ',
  'ȗ' => 'Ȗ',
  'ș' => 'Ș',
  'ț' => 'Ț',
  'ȝ' => 'Ȝ',
  'ȟ' => 'Ȟ',
  'ȣ' => 'Ȣ',
  'ȥ' => 'Ȥ',
  'ȧ' => 'Ȧ',
  'ȩ' => 'Ȩ',
  'ȫ' => 'Ȫ',
  'ȭ' => 'Ȭ',
  'ȯ' => 'Ȯ',
  'ȱ' => 'Ȱ',
  'ȳ' => 'Ȳ',
  'ȼ' => 'Ȼ',
  'ȿ' => 'Ȿ',
  'ɀ' => 'Ɀ',
  'ɂ' => 'Ɂ',
  'ɇ' => 'Ɇ',
  'ɉ' => 'Ɉ',
  'ɋ' => 'Ɋ',
  'ɍ' => 'Ɍ',
  'ɏ' => 'Ɏ',
  'ɐ' => 'Ɐ',
  'ɑ' => 'Ɑ',
  'ɒ' => 'Ɒ',
  'ɓ' => 'Ɓ',
  'ɔ' => 'Ɔ',
  'ɖ' => 'Ɖ',
  'ɗ' => 'Ɗ',
  'ə' => 'Ə',
  'ɛ' => 'Ɛ',
  'ɜ' => 'Ɜ',
  'ɠ' => 'Ɠ',
  'ɡ' => 'Ɡ',
  'ɣ' => 'Ɣ',
  'ɥ' => 'Ɥ',
  'ɦ' => 'Ɦ',
  'ɨ' => 'Ɨ',
  'ɩ' => 'Ɩ',
  'ɫ' => 'Ɫ',
  'ɬ' => 'Ɬ',
  'ɯ' => 'Ɯ',
  'ɱ' => 'Ɱ',
  'ɲ' => 'Ɲ',
  'ɵ' => 'Ɵ',
  'ɽ' => 'Ɽ',
  'ʀ' => 'Ʀ',
  'ʃ' => 'Ʃ',
  'ʇ' => 'Ʇ',
  'ʈ' => 'Ʈ',
  'ʉ' => 'Ʉ',
  'ʊ' => 'Ʊ',
  'ʋ' => 'Ʋ',
  'ʌ' => 'Ʌ',
  'ʒ' => 'Ʒ',
  'ʞ' => 'Ʞ',
  'ͅ' => 'Ι',
  'ͱ' => 'Ͱ',
  'ͳ' => 'Ͳ',
  'ͷ' => 'Ͷ',
  'ͻ' => 'Ͻ',
  'ͼ' => 'Ͼ',
  'ͽ' => 'Ͽ',
  'ά' => 'Ά',
  'έ' => 'Έ',
  'ή' => 'Ή',
  'ί' => 'Ί',
  'α' => 'Α',
  'β' => 'Β',
  'γ' => 'Γ',
  'δ' => 'Δ',
  'ε' => 'Ε',
  'ζ' => 'Ζ',
  'η' => 'Η',
  'θ' => 'Θ',
  'ι' => 'Ι',
  'κ' => 'Κ',
  'λ' => 'Λ',
  'μ' => 'Μ',
  'ν' => 'Ν',
  'ξ' => 'Ξ',
  'ο' => 'Ο',
  'π' => 'Π',
  'ρ' => 'Ρ',
  'ς' => 'Σ',
  'σ' => 'Σ',
  'τ' => 'Τ',
  'υ' => 'Υ',
  'φ' => 'Φ',
  'χ' => 'Χ',
  'ψ' => 'Ψ',
  'ω' => 'Ω',
  'ϊ' => 'Ϊ',
  'ϋ' => 'Ϋ',
  'ό' => 'Ό',
  'ύ' => 'Ύ',
  'ώ' => 'Ώ',
  'ϐ' => 'Β',
  'ϑ' => 'Θ',
  'ϕ' => 'Φ',
  'ϖ' => 'Π',
  'ϗ' => 'Ϗ',
  'ϙ' => 'Ϙ',
  'ϛ' => 'Ϛ',
  'ϝ' => 'Ϝ',
  'ϟ' => 'Ϟ',
  'ϡ' => 'Ϡ',
  'ϣ' => 'Ϣ',
  'ϥ' => 'Ϥ',
  'ϧ' => 'Ϧ',
  'ϩ' => 'Ϩ',
  'ϫ' => 'Ϫ',
  'ϭ' => 'Ϭ',
  'ϯ' => 'Ϯ',
  'ϰ' => 'Κ',
  'ϱ' => 'Ρ',
  'ϲ' => 'Ϲ',
  'ϳ' => 'Ϳ',
  'ϵ' => 'Ε',
  'ϸ' => 'Ϸ',
  'ϻ' => 'Ϻ',
  'а' => 'А',
  'б' => 'Б',
  'в' => 'В',
  'г' => 'Г',
  'д' => 'Д',
  'е' => 'Е',
  'ж' => 'Ж',
  'з' => 'З',
  'и' => 'И',
  'й' => 'Й',
  'к' => 'К',
  'л' => 'Л',
  'м' => 'М',
  'н' => 'Н',
  'о' => 'О',
  'п' => 'П',
  'р' => 'Р',
  'с' => 'С',
  'т' => 'Т',
  'у' => 'У',
  'ф' => 'Ф',
  'х' => 'Х',
  'ц' => 'Ц',
  'ч' => 'Ч',
  'ш' => 'Ш',
  'щ' => 'Щ',
  'ъ' => 'Ъ',
  'ы' => 'Ы',
  'ь' => 'Ь',
  'э' => 'Э',
  'ю' => 'Ю',
  'я' => 'Я',
  'ѐ' => 'Ѐ',
  'ё' => 'Ё',
  'ђ' => 'Ђ',
  'ѓ' => 'Ѓ',
  'є' => 'Є',
  'ѕ' => 'Ѕ',
  'і' => 'І',
  'ї' => 'Ї',
  'ј' => 'Ј',
  'љ' => 'Љ',
  'њ' => 'Њ',
  'ћ' => 'Ћ',
  'ќ' => 'Ќ',
  'ѝ' => 'Ѝ',
  'ў' => 'Ў',
  'џ' => 'Џ',
  'ѡ' => 'Ѡ',
  'ѣ' => 'Ѣ',
  'ѥ' => 'Ѥ',
  'ѧ' => 'Ѧ',
  'ѩ' => 'Ѩ',
  'ѫ' => 'Ѫ',
  'ѭ' => 'Ѭ',
  'ѯ' => 'Ѯ',
  'ѱ' => 'Ѱ',
  'ѳ' => 'Ѳ',
  'ѵ' => 'Ѵ',
  'ѷ' => 'Ѷ',
  'ѹ' => 'Ѹ',
  'ѻ' => 'Ѻ',
  'ѽ' => 'Ѽ',
  'ѿ' => 'Ѿ',
  'ҁ' => 'Ҁ',
  'ҋ' => 'Ҋ',
  'ҍ' => 'Ҍ',
  'ҏ' => 'Ҏ',
  'ґ' => 'Ґ',
  'ғ' => 'Ғ',
  'ҕ' => 'Ҕ',
  'җ' => 'Җ',
  'ҙ' => 'Ҙ',
  'қ' => 'Қ',
  'ҝ' => 'Ҝ',
  'ҟ' => 'Ҟ',
  'ҡ' => 'Ҡ',
  'ң' => 'Ң',
  'ҥ' => 'Ҥ',
  'ҧ' => 'Ҧ',
  'ҩ' => 'Ҩ',
  'ҫ' => 'Ҫ',
  'ҭ' => 'Ҭ',
  'ү' => 'Ү',
  'ұ' => 'Ұ',
  'ҳ' => 'Ҳ',
  'ҵ' => 'Ҵ',
  'ҷ' => 'Ҷ',
  'ҹ' => 'Ҹ',
  'һ' => 'Һ',
  'ҽ' => 'Ҽ',
  'ҿ' => 'Ҿ',
  'ӂ' => 'Ӂ',
  'ӄ' => 'Ӄ',
  'ӆ' => 'Ӆ',
  'ӈ' => 'Ӈ',
  'ӊ' => 'Ӊ',
  'ӌ' => 'Ӌ',
  'ӎ' => 'Ӎ',
  'ӏ' => 'Ӏ',
  'ӑ' => 'Ӑ',
  'ӓ' => 'Ӓ',
  'ӕ' => 'Ӕ',
  'ӗ' => 'Ӗ',
  'ә' => 'Ә',
  'ӛ' => 'Ӛ',
  'ӝ' => 'Ӝ',
  'ӟ' => 'Ӟ',
  'ӡ' => 'Ӡ',
  'ӣ' => 'Ӣ',
  'ӥ' => 'Ӥ',
  'ӧ' => 'Ӧ',
  'ө' => 'Ө',
  'ӫ' => 'Ӫ',
  'ӭ' => 'Ӭ',
  'ӯ' => 'Ӯ',
  'ӱ' => 'Ӱ',
  'ӳ' => 'Ӳ',
  'ӵ' => 'Ӵ',
  'ӷ' => 'Ӷ',
  'ӹ' => 'Ӹ',
  'ӻ' => 'Ӻ',
  'ӽ' => 'Ӽ',
  'ӿ' => 'Ӿ',
  'ԁ' => 'Ԁ',
  'ԃ' => 'Ԃ',
  'ԅ' => 'Ԅ',
  'ԇ' => 'Ԇ',
  'ԉ' => 'Ԉ',
  'ԋ' => 'Ԋ',
  'ԍ' => 'Ԍ',
  'ԏ' => 'Ԏ',
  'ԑ' => 'Ԑ',
  'ԓ' => 'Ԓ',
  'ԕ' => 'Ԕ',
  'ԗ' => 'Ԗ',
  'ԙ' => 'Ԙ',
  'ԛ' => 'Ԛ',
  'ԝ' => 'Ԝ',
  'ԟ' => 'Ԟ',
  'ԡ' => 'Ԡ',
  'ԣ' => 'Ԣ',
  'ԥ' => 'Ԥ',
  'ԧ' => 'Ԧ',
  'ԩ' => 'Ԩ',
  'ԫ' => 'Ԫ',
  'ԭ' => 'Ԭ',
  'ԯ' => 'Ԯ',
  'ա' => 'Ա',
  'բ' => 'Բ',
  'գ' => 'Գ',
  'դ' => 'Դ',
  'ե' => 'Ե',
  'զ' => 'Զ',
  'է' => 'Է',
  'ը' => 'Ը',
  'թ' => 'Թ',
  'ժ' => 'Ժ',
  'ի' => 'Ի',
  'լ' => 'Լ',
  'խ' => 'Խ',
  'ծ' => 'Ծ',
  'կ' => 'Կ',
  'հ' => 'Հ',
  'ձ' => 'Ձ',
  'ղ' => 'Ղ',
  'ճ' => 'Ճ',
  'մ' => 'Մ',
  'յ' => 'Յ',
  'ն' => 'Ն',
  'շ' => 'Շ',
  'ո' => 'Ո',
  'չ' => 'Չ',
  'պ' => 'Պ',
  'ջ' => 'Ջ',
  'ռ' => 'Ռ',
  'ս' => 'Ս',
  'վ' => 'Վ',
  'տ' => 'Տ',
  'ր' => 'Ր',
  'ց' => 'Ց',
  'ւ' => 'Ւ',
  'փ' => 'Փ',
  'ք' => 'Ք',
  'օ' => 'Օ',
  'ֆ' => 'Ֆ',
  'ᵹ' => 'Ᵹ',
  'ᵽ' => 'Ᵽ',
  'ḁ' => 'Ḁ',
  'ḃ' => 'Ḃ',
  'ḅ' => 'Ḅ',
  'ḇ' => 'Ḇ',
  'ḉ' => 'Ḉ',
  'ḋ' => 'Ḋ',
  'ḍ' => 'Ḍ',
  'ḏ' => 'Ḏ',
  'ḑ' => 'Ḑ',
  'ḓ' => 'Ḓ',
  'ḕ' => 'Ḕ',
  'ḗ' => 'Ḗ',
  'ḙ' => 'Ḙ',
  'ḛ' => 'Ḛ',
  'ḝ' => 'Ḝ',
  'ḟ' => 'Ḟ',
  'ḡ' => 'Ḡ',
  'ḣ' => 'Ḣ',
  'ḥ' => 'Ḥ',
  'ḧ' => 'Ḧ',
  'ḩ' => 'Ḩ',
  'ḫ' => 'Ḫ',
  'ḭ' => 'Ḭ',
  'ḯ' => 'Ḯ',
  'ḱ' => 'Ḱ',
  'ḳ' => 'Ḳ',
  'ḵ' => 'Ḵ',
  'ḷ' => 'Ḷ',
  'ḹ' => 'Ḹ',
  'ḻ' => 'Ḻ',
  'ḽ' => 'Ḽ',
  'ḿ' => 'Ḿ',
  'ṁ' => 'Ṁ',
  'ṃ' => 'Ṃ',
  'ṅ' => 'Ṅ',
  'ṇ' => 'Ṇ',
  'ṉ' => 'Ṉ',
  'ṋ' => 'Ṋ',
  'ṍ' => 'Ṍ',
  'ṏ' => 'Ṏ',
  'ṑ' => 'Ṑ',
  'ṓ' => 'Ṓ',
  'ṕ' => 'Ṕ',
  'ṗ' => 'Ṗ',
  'ṙ' => 'Ṙ',
  'ṛ' => 'Ṛ',
  'ṝ' => 'Ṝ',
  'ṟ' => 'Ṟ',
  'ṡ' => 'Ṡ',
  'ṣ' => 'Ṣ',
  'ṥ' => 'Ṥ',
  'ṧ' => 'Ṧ',
  'ṩ' => 'Ṩ',
  'ṫ' => 'Ṫ',
  'ṭ' => 'Ṭ',
  'ṯ' => 'Ṯ',
  'ṱ' => 'Ṱ',
  'ṳ' => 'Ṳ',
  'ṵ' => 'Ṵ',
  'ṷ' => 'Ṷ',
  'ṹ' => 'Ṹ',
  'ṻ' => 'Ṻ',
  'ṽ' => 'Ṽ',
  'ṿ' => 'Ṿ',
  'ẁ' => 'Ẁ',
  'ẃ' => 'Ẃ',
  'ẅ' => 'Ẅ',
  'ẇ' => 'Ẇ',
  'ẉ' => 'Ẉ',
  'ẋ' => 'Ẋ',
  'ẍ' => 'Ẍ',
  'ẏ' => 'Ẏ',
  'ẑ' => 'Ẑ',
  'ẓ' => 'Ẓ',
  'ẕ' => 'Ẕ',
  'ẛ' => 'Ṡ',
  'ạ' => 'Ạ',
  'ả' => 'Ả',
  'ấ' => 'Ấ',
  'ầ' => 'Ầ',
  'ẩ' => 'Ẩ',
  'ẫ' => 'Ẫ',
  'ậ' => 'Ậ',
  'ắ' => 'Ắ',
  'ằ' => 'Ằ',
  'ẳ' => 'Ẳ',
  'ẵ' => 'Ẵ',
  'ặ' => 'Ặ',
  'ẹ' => 'Ẹ',
  'ẻ' => 'Ẻ',
  'ẽ' => 'Ẽ',
  'ế' => 'Ế',
  'ề' => 'Ề',
  'ể' => 'Ể',
  'ễ' => 'Ễ',
  'ệ' => 'Ệ',
  'ỉ' => 'Ỉ',
  'ị' => 'Ị',
  'ọ' => 'Ọ',
  'ỏ' => 'Ỏ',
  'ố' => 'Ố',
  'ồ' => 'Ồ',
  'ổ' => 'Ổ',
  'ỗ' => 'Ỗ',
  'ộ' => 'Ộ',
  'ớ' => 'Ớ',
  'ờ' => 'Ờ',
  'ở' => 'Ở',
  'ỡ' => 'Ỡ',
  'ợ' => 'Ợ',
  'ụ' => 'Ụ',
  'ủ' => 'Ủ',
  'ứ' => 'Ứ',
  'ừ' => 'Ừ',
  'ử' => 'Ử',
  'ữ' => 'Ữ',
  'ự' => 'Ự',
  'ỳ' => 'Ỳ',
  'ỵ' => 'Ỵ',
  'ỷ' => 'Ỷ',
  'ỹ' => 'Ỹ',
  'ỻ' => 'Ỻ',
  'ỽ' => 'Ỽ',
  'ỿ' => 'Ỿ',
  'ἀ' => 'Ἀ',
  'ἁ' => 'Ἁ',
  'ἂ' => 'Ἂ',
  'ἃ' => 'Ἃ',
  'ἄ' => 'Ἄ',
  'ἅ' => 'Ἅ',
  'ἆ' => 'Ἆ',
  'ἇ' => 'Ἇ',
  'ἐ' => 'Ἐ',
  'ἑ' => 'Ἑ',
  'ἒ' => 'Ἒ',
  'ἓ' => 'Ἓ',
  'ἔ' => 'Ἔ',
  'ἕ' => 'Ἕ',
  'ἠ' => 'Ἠ',
  'ἡ' => 'Ἡ',
  'ἢ' => 'Ἢ',
  'ἣ' => 'Ἣ',
  'ἤ' => 'Ἤ',
  'ἥ' => 'Ἥ',
  'ἦ' => 'Ἦ',
  'ἧ' => 'Ἧ',
  'ἰ' => 'Ἰ',
  'ἱ' => 'Ἱ',
  'ἲ' => 'Ἲ',
  'ἳ' => 'Ἳ',
  'ἴ' => 'Ἴ',
  'ἵ' => 'Ἵ',
  'ἶ' => 'Ἶ',
  'ἷ' => 'Ἷ',
  'ὀ' => 'Ὀ',
  'ὁ' => 'Ὁ',
  'ὂ' => 'Ὂ',
  'ὃ' => 'Ὃ',
  'ὄ' => 'Ὄ',
  'ὅ' => 'Ὅ',
  'ὑ' => 'Ὑ',
  'ὓ' => 'Ὓ',
  'ὕ' => 'Ὕ',
  'ὗ' => 'Ὗ',
  'ὠ' => 'Ὠ',
  'ὡ' => 'Ὡ',
  'ὢ' => 'Ὢ',
  'ὣ' => 'Ὣ',
  'ὤ' => 'Ὤ',
  'ὥ' => 'Ὥ',
  'ὦ' => 'Ὦ',
  'ὧ' => 'Ὧ',
  'ὰ' => 'Ὰ',
  'ά' => 'Ά',
  'ὲ' => 'Ὲ',
  'έ' => 'Έ',
  'ὴ' => 'Ὴ',
  'ή' => 'Ή',
  'ὶ' => 'Ὶ',
  'ί' => 'Ί',
  'ὸ' => 'Ὸ',
  'ό' => 'Ό',
  'ὺ' => 'Ὺ',
  'ύ' => 'Ύ',
  'ὼ' => 'Ὼ',
  'ώ' => 'Ώ',
  'ᾀ' => 'ᾈ',
  'ᾁ' => 'ᾉ',
  'ᾂ' => 'ᾊ',
  'ᾃ' => 'ᾋ',
  'ᾄ' => 'ᾌ',
  'ᾅ' => 'ᾍ',
  'ᾆ' => 'ᾎ',
  'ᾇ' => 'ᾏ',
  'ᾐ' => 'ᾘ',
  'ᾑ' => 'ᾙ',
  'ᾒ' => 'ᾚ',
  'ᾓ' => 'ᾛ',
  'ᾔ' => 'ᾜ',
  'ᾕ' => 'ᾝ',
  'ᾖ' => 'ᾞ',
  'ᾗ' => 'ᾟ',
  'ᾠ' => 'ᾨ',
  'ᾡ' => 'ᾩ',
  'ᾢ' => 'ᾪ',
  'ᾣ' => 'ᾫ',
  'ᾤ' => 'ᾬ',
  'ᾥ' => 'ᾭ',
  'ᾦ' => 'ᾮ',
  'ᾧ' => 'ᾯ',
  'ᾰ' => 'Ᾰ',
  'ᾱ' => 'Ᾱ',
  'ᾳ' => 'ᾼ',
  'ι' => 'Ι',
  'ῃ' => 'ῌ',
  'ῐ' => 'Ῐ',
  'ῑ' => 'Ῑ',
  'ῠ' => 'Ῠ',
  'ῡ' => 'Ῡ',
  'ῥ' => 'Ῥ',
  'ῳ' => 'ῼ',
  'ⅎ' => 'Ⅎ',
  'ⅰ' => 'Ⅰ',
  'ⅱ' => 'Ⅱ',
  'ⅲ' => 'Ⅲ',
  'ⅳ' => 'Ⅳ',
  'ⅴ' => 'Ⅴ',
  'ⅵ' => 'Ⅵ',
  'ⅶ' => 'Ⅶ',
  'ⅷ' => 'Ⅷ',
  'ⅸ' => 'Ⅸ',
  'ⅹ' => 'Ⅹ',
  'ⅺ' => 'Ⅺ',
  'ⅻ' => 'Ⅻ',
  'ⅼ' => 'Ⅼ',
  'ⅽ' => 'Ⅽ',
  'ⅾ' => 'Ⅾ',
  'ⅿ' => 'Ⅿ',
  'ↄ' => 'Ↄ',
  'ⓐ' => 'Ⓐ',
  'ⓑ' => 'Ⓑ',
  'ⓒ' => 'Ⓒ',
  'ⓓ' => 'Ⓓ',
  'ⓔ' => 'Ⓔ',
  'ⓕ' => 'Ⓕ',
  'ⓖ' => 'Ⓖ',
  'ⓗ' => 'Ⓗ',
  'ⓘ' => 'Ⓘ',
  'ⓙ' => 'Ⓙ',
  'ⓚ' => 'Ⓚ',
  'ⓛ' => 'Ⓛ',
  'ⓜ' => 'Ⓜ',
  'ⓝ' => 'Ⓝ',
  'ⓞ' => 'Ⓞ',
  'ⓟ' => 'Ⓟ',
  'ⓠ' => 'Ⓠ',
  'ⓡ' => 'Ⓡ',
  'ⓢ' => 'Ⓢ',
  'ⓣ' => 'Ⓣ',
  'ⓤ' => 'Ⓤ',
  'ⓥ' => 'Ⓥ',
  'ⓦ' => 'Ⓦ',
  'ⓧ' => 'Ⓧ',
  'ⓨ' => 'Ⓨ',
  'ⓩ' => 'Ⓩ',
  'ⰰ' => 'Ⰰ',
  'ⰱ' => 'Ⰱ',
  'ⰲ' => 'Ⰲ',
  'ⰳ' => 'Ⰳ',
  'ⰴ' => 'Ⰴ',
  'ⰵ' => 'Ⰵ',
  'ⰶ' => 'Ⰶ',
  'ⰷ' => 'Ⰷ',
  'ⰸ' => 'Ⰸ',
  'ⰹ' => 'Ⰹ',
  'ⰺ' => 'Ⰺ',
  'ⰻ' => 'Ⰻ',
  'ⰼ' => 'Ⰼ',
  'ⰽ' => 'Ⰽ',
  'ⰾ' => 'Ⰾ',
  'ⰿ' => 'Ⰿ',
  'ⱀ' => 'Ⱀ',
  'ⱁ' => 'Ⱁ',
  'ⱂ' => 'Ⱂ',
  'ⱃ' => 'Ⱃ',
  'ⱄ' => 'Ⱄ',
  'ⱅ' => 'Ⱅ',
  'ⱆ' => 'Ⱆ',
  'ⱇ' => 'Ⱇ',
  'ⱈ' => 'Ⱈ',
  'ⱉ' => 'Ⱉ',
  'ⱊ' => 'Ⱊ',
  'ⱋ' => 'Ⱋ',
  'ⱌ' => 'Ⱌ',
  'ⱍ' => 'Ⱍ',
  'ⱎ' => 'Ⱎ',
  'ⱏ' => 'Ⱏ',
  'ⱐ' => 'Ⱐ',
  'ⱑ' => 'Ⱑ',
  'ⱒ' => 'Ⱒ',
  'ⱓ' => 'Ⱓ',
  'ⱔ' => 'Ⱔ',
  'ⱕ' => 'Ⱕ',
  'ⱖ' => 'Ⱖ',
  'ⱗ' => 'Ⱗ',
  'ⱘ' => 'Ⱘ',
  'ⱙ' => 'Ⱙ',
  'ⱚ' => 'Ⱚ',
  'ⱛ' => 'Ⱛ',
  'ⱜ' => 'Ⱜ',
  'ⱝ' => 'Ⱝ',
  'ⱞ' => 'Ⱞ',
  'ⱡ' => 'Ⱡ',
  'ⱥ' => 'Ⱥ',
  'ⱦ' => 'Ⱦ',
  'ⱨ' => 'Ⱨ',
  'ⱪ' => 'Ⱪ',
  'ⱬ' => 'Ⱬ',
  'ⱳ' => 'Ⱳ',
  'ⱶ' => 'Ⱶ',
  'ⲁ' => 'Ⲁ',
  'ⲃ' => 'Ⲃ',
  'ⲅ' => 'Ⲅ',
  'ⲇ' => 'Ⲇ',
  'ⲉ' => 'Ⲉ',
  'ⲋ' => 'Ⲋ',
  'ⲍ' => 'Ⲍ',
  'ⲏ' => 'Ⲏ',
  'ⲑ' => 'Ⲑ',
  'ⲓ' => 'Ⲓ',
  'ⲕ' => 'Ⲕ',
  'ⲗ' => 'Ⲗ',
  'ⲙ' => 'Ⲙ',
  'ⲛ' => 'Ⲛ',
  'ⲝ' => 'Ⲝ',
  'ⲟ' => 'Ⲟ',
  'ⲡ' => 'Ⲡ',
  'ⲣ' => 'Ⲣ',
  'ⲥ' => 'Ⲥ',
  'ⲧ' => 'Ⲧ',
  'ⲩ' => 'Ⲩ',
  'ⲫ' => 'Ⲫ',
  'ⲭ' => 'Ⲭ',
  'ⲯ' => 'Ⲯ',
  'ⲱ' => 'Ⲱ',
  'ⲳ' => 'Ⲳ',
  'ⲵ' => 'Ⲵ',
  'ⲷ' => 'Ⲷ',
  'ⲹ' => 'Ⲹ',
  'ⲻ' => 'Ⲻ',
  'ⲽ' => 'Ⲽ',
  'ⲿ' => 'Ⲿ',
  'ⳁ' => 'Ⳁ',
  'ⳃ' => 'Ⳃ',
  'ⳅ' => 'Ⳅ',
  'ⳇ' => 'Ⳇ',
  'ⳉ' => 'Ⳉ',
  'ⳋ' => 'Ⳋ',
  'ⳍ' => 'Ⳍ',
  'ⳏ' => 'Ⳏ',
  'ⳑ' => 'Ⳑ',
  'ⳓ' => 'Ⳓ',
  'ⳕ' => 'Ⳕ',
  'ⳗ' => 'Ⳗ',
  'ⳙ' => 'Ⳙ',
  'ⳛ' => 'Ⳛ',
  'ⳝ' => 'Ⳝ',
  'ⳟ' => 'Ⳟ',
  'ⳡ' => 'Ⳡ',
  'ⳣ' => 'Ⳣ',
  'ⳬ' => 'Ⳬ',
  'ⳮ' => 'Ⳮ',
  'ⳳ' => 'Ⳳ',
  'ⴀ' => 'Ⴀ',
  'ⴁ' => 'Ⴁ',
  'ⴂ' => 'Ⴂ',
  'ⴃ' => 'Ⴃ',
  'ⴄ' => 'Ⴄ',
  'ⴅ' => 'Ⴅ',
  'ⴆ' => 'Ⴆ',
  'ⴇ' => 'Ⴇ',
  'ⴈ' => 'Ⴈ',
  'ⴉ' => 'Ⴉ',
  'ⴊ' => 'Ⴊ',
  'ⴋ' => 'Ⴋ',
  'ⴌ' => 'Ⴌ',
  'ⴍ' => 'Ⴍ',
  'ⴎ' => 'Ⴎ',
  'ⴏ' => 'Ⴏ',
  'ⴐ' => 'Ⴐ',
  'ⴑ' => 'Ⴑ',
  'ⴒ' => 'Ⴒ',
  'ⴓ' => 'Ⴓ',
  'ⴔ' => 'Ⴔ',
  'ⴕ' => 'Ⴕ',
  'ⴖ' => 'Ⴖ',
  'ⴗ' => 'Ⴗ',
  'ⴘ' => 'Ⴘ',
  'ⴙ' => 'Ⴙ',
  'ⴚ' => 'Ⴚ',
  'ⴛ' => 'Ⴛ',
  'ⴜ' => 'Ⴜ',
  'ⴝ' => 'Ⴝ',
  'ⴞ' => 'Ⴞ',
  'ⴟ' => 'Ⴟ',
  'ⴠ' => 'Ⴠ',
  'ⴡ' => 'Ⴡ',
  'ⴢ' => 'Ⴢ',
  'ⴣ' => 'Ⴣ',
  'ⴤ' => 'Ⴤ',
  'ⴥ' => 'Ⴥ',
  'ⴧ' => 'Ⴧ',
  'ⴭ' => 'Ⴭ',
  'ꙁ' => 'Ꙁ',
  'ꙃ' => 'Ꙃ',
  'ꙅ' => 'Ꙅ',
  'ꙇ' => 'Ꙇ',
  'ꙉ' => 'Ꙉ',
  'ꙋ' => 'Ꙋ',
  'ꙍ' => 'Ꙍ',
  'ꙏ' => 'Ꙏ',
  'ꙑ' => 'Ꙑ',
  'ꙓ' => 'Ꙓ',
  'ꙕ' => 'Ꙕ',
  'ꙗ' => 'Ꙗ',
  'ꙙ' => 'Ꙙ',
  'ꙛ' => 'Ꙛ',
  'ꙝ' => 'Ꙝ',
  'ꙟ' => 'Ꙟ',
  'ꙡ' => 'Ꙡ',
  'ꙣ' => 'Ꙣ',
  'ꙥ' => 'Ꙥ',
  'ꙧ' => 'Ꙧ',
  'ꙩ' => 'Ꙩ',
  'ꙫ' => 'Ꙫ',
  'ꙭ' => 'Ꙭ',
  'ꚁ' => 'Ꚁ',
  'ꚃ' => 'Ꚃ',
  'ꚅ' => 'Ꚅ',
  'ꚇ' => 'Ꚇ',
  'ꚉ' => 'Ꚉ',
  'ꚋ' => 'Ꚋ',
  'ꚍ' => 'Ꚍ',
  'ꚏ' => 'Ꚏ',
  'ꚑ' => 'Ꚑ',
  'ꚓ' => 'Ꚓ',
  'ꚕ' => 'Ꚕ',
  'ꚗ' => 'Ꚗ',
  'ꚙ' => 'Ꚙ',
  'ꚛ' => 'Ꚛ',
  'ꜣ' => 'Ꜣ',
  'ꜥ' => 'Ꜥ',
  'ꜧ' => 'Ꜧ',
  'ꜩ' => 'Ꜩ',
  'ꜫ' => 'Ꜫ',
  'ꜭ' => 'Ꜭ',
  'ꜯ' => 'Ꜯ',
  'ꜳ' => 'Ꜳ',
  'ꜵ' => 'Ꜵ',
  'ꜷ' => 'Ꜷ',
  'ꜹ' => 'Ꜹ',
  'ꜻ' => 'Ꜻ',
  'ꜽ' => 'Ꜽ',
  'ꜿ' => 'Ꜿ',
  'ꝁ' => 'Ꝁ',
  'ꝃ' => 'Ꝃ',
  'ꝅ' => 'Ꝅ',
  'ꝇ' => 'Ꝇ',
  'ꝉ' => 'Ꝉ',
  'ꝋ' => 'Ꝋ',
  'ꝍ' => 'Ꝍ',
  'ꝏ' => 'Ꝏ',
  'ꝑ' => 'Ꝑ',
  'ꝓ' => 'Ꝓ',
  'ꝕ' => 'Ꝕ',
  'ꝗ' => 'Ꝗ',
  'ꝙ' => 'Ꝙ',
  'ꝛ' => 'Ꝛ',
  'ꝝ' => 'Ꝝ',
  'ꝟ' => 'Ꝟ',
  'ꝡ' => 'Ꝡ',
  'ꝣ' => 'Ꝣ',
  'ꝥ' => 'Ꝥ',
  'ꝧ' => 'Ꝧ',
  'ꝩ' => 'Ꝩ',
  'ꝫ' => 'Ꝫ',
  'ꝭ' => 'Ꝭ',
  'ꝯ' => 'Ꝯ',
  'ꝺ' => 'Ꝺ',
  'ꝼ' => 'Ꝼ',
  'ꝿ' => 'Ꝿ',
  'ꞁ' => 'Ꞁ',
  'ꞃ' => 'Ꞃ',
  'ꞅ' => 'Ꞅ',
  'ꞇ' => 'Ꞇ',
  'ꞌ' => 'Ꞌ',
  'ꞑ' => 'Ꞑ',
  'ꞓ' => 'Ꞓ',
  'ꞗ' => 'Ꞗ',
  'ꞙ' => 'Ꞙ',
  'ꞛ' => 'Ꞛ',
  'ꞝ' => 'Ꞝ',
  'ꞟ' => 'Ꞟ',
  'ꞡ' => 'Ꞡ',
  'ꞣ' => 'Ꞣ',
  'ꞥ' => 'Ꞥ',
  'ꞧ' => 'Ꞧ',
  'ꞩ' => 'Ꞩ',
  'a' => 'A',
  'b' => 'B',
  'c' => 'C',
  'd' => 'D',
  'e' => 'E',
  'f' => 'F',
  'g' => 'G',
  'h' => 'H',
  'i' => 'I',
  'j' => 'J',
  'k' => 'K',
  'l' => 'L',
  'm' => 'M',
  'n' => 'N',
  'o' => 'O',
  'p' => 'P',
  'q' => 'Q',
  'r' => 'R',
  's' => 'S',
  't' => 'T',
  'u' => 'U',
  'v' => 'V',
  'w' => 'W',
  'x' => 'X',
  'y' => 'Y',
  'z' => 'Z',
  '𐐨' => '𐐀',
  '𐐩' => '𐐁',
  '𐐪' => '𐐂',
  '𐐫' => '𐐃',
  '𐐬' => '𐐄',
  '𐐭' => '𐐅',
  '𐐮' => '𐐆',
  '𐐯' => '𐐇',
  '𐐰' => '𐐈',
  '𐐱' => '𐐉',
  '𐐲' => '𐐊',
  '𐐳' => '𐐋',
  '𐐴' => '𐐌',
  '𐐵' => '𐐍',
  '𐐶' => '𐐎',
  '𐐷' => '𐐏',
  '𐐸' => '𐐐',
  '𐐹' => '𐐑',
  '𐐺' => '𐐒',
  '𐐻' => '𐐓',
  '𐐼' => '𐐔',
  '𐐽' => '𐐕',
  '𐐾' => '𐐖',
  '𐐿' => '𐐗',
  '𐑀' => '𐐘',
  '𐑁' => '𐐙',
  '𐑂' => '𐐚',
  '𐑃' => '𐐛',
  '𐑄' => '𐐜',
  '𐑅' => '𐐝',
  '𐑆' => '𐐞',
  '𐑇' => '𐐟',
  '𐑈' => '𐐠',
  '𐑉' => '𐐡',
  '𐑊' => '𐐢',
  '𐑋' => '𐐣',
  '𐑌' => '𐐤',
  '𐑍' => '𐐥',
  '𐑎' => '𐐦',
  '𐑏' => '𐐧',
  '𑣀' => '𑢠',
  '𑣁' => '𑢡',
  '𑣂' => '𑢢',
  '𑣃' => '𑢣',
  '𑣄' => '𑢤',
  '𑣅' => '𑢥',
  '𑣆' => '𑢦',
  '𑣇' => '𑢧',
  '𑣈' => '𑢨',
  '𑣉' => '𑢩',
  '𑣊' => '𑢪',
  '𑣋' => '𑢫',
  '𑣌' => '𑢬',
  '𑣍' => '𑢭',
  '𑣎' => '𑢮',
  '𑣏' => '𑢯',
  '𑣐' => '𑢰',
  '𑣑' => '𑢱',
  '𑣒' => '𑢲',
  '𑣓' => '𑢳',
  '𑣔' => '𑢴',
  '𑣕' => '𑢵',
  '𑣖' => '𑢶',
  '𑣗' => '𑢷',
  '𑣘' => '𑢸',
  '𑣙' => '𑢹',
  '𑣚' => '𑢺',
  '𑣛' => '𑢻',
  '𑣜' => '𑢼',
  '𑣝' => '𑢽',
  '𑣞' => '𑢾',
  '𑣟' => '𑢿',
);

$result =& $data;
unset($data);

return $result;
<?php

static $data = array (
  'A' => 'a',
  'B' => 'b',
  'C' => 'c',
  'D' => 'd',
  'E' => 'e',
  'F' => 'f',
  'G' => 'g',
  'H' => 'h',
  'I' => 'i',
  'J' => 'j',
  'K' => 'k',
  'L' => 'l',
  'M' => 'm',
  'N' => 'n',
  'O' => 'o',
  'P' => 'p',
  'Q' => 'q',
  'R' => 'r',
  'S' => 's',
  'T' => 't',
  'U' => 'u',
  'V' => 'v',
  'W' => 'w',
  'X' => 'x',
  'Y' => 'y',
  'Z' => 'z',
  'À' => 'à',
  'Á' => 'á',
  'Â' => 'â',
  'Ã' => 'ã',
  'Ä' => 'ä',
  'Å' => 'å',
  'Æ' => 'æ',
  'Ç' => 'ç',
  'È' => 'è',
  'É' => 'é',
  'Ê' => 'ê',
  'Ë' => 'ë',
  'Ì' => 'ì',
  'Í' => 'í',
  'Î' => 'î',
  'Ï' => 'ï',
  'Ð' => 'ð',
  'Ñ' => 'ñ',
  'Ò' => 'ò',
  'Ó' => 'ó',
  'Ô' => 'ô',
  'Õ' => 'õ',
  'Ö' => 'ö',
  'Ø' => 'ø',
  'Ù' => 'ù',
  'Ú' => 'ú',
  'Û' => 'û',
  'Ü' => 'ü',
  'Ý' => 'ý',
  'Þ' => 'þ',
  'Ā' => 'ā',
  'Ă' => 'ă',
  'Ą' => 'ą',
  'Ć' => 'ć',
  'Ĉ' => 'ĉ',
  'Ċ' => 'ċ',
  'Č' => 'č',
  'Ď' => 'ď',
  'Đ' => 'đ',
  'Ē' => 'ē',
  'Ĕ' => 'ĕ',
  'Ė' => 'ė',
  'Ę' => 'ę',
  'Ě' => 'ě',
  'Ĝ' => 'ĝ',
  'Ğ' => 'ğ',
  'Ġ' => 'ġ',
  'Ģ' => 'ģ',
  'Ĥ' => 'ĥ',
  'Ħ' => 'ħ',
  'Ĩ' => 'ĩ',
  'Ī' => 'ī',
  'Ĭ' => 'ĭ',
  'Į' => 'į',
  'İ' => 'i',
  'IJ' => 'ij',
  'Ĵ' => 'ĵ',
  'Ķ' => 'ķ',
  'Ĺ' => 'ĺ',
  'Ļ' => 'ļ',
  'Ľ' => 'ľ',
  'Ŀ' => 'ŀ',
  'Ł' => 'ł',
  'Ń' => 'ń',
  'Ņ' => 'ņ',
  'Ň' => 'ň',
  'Ŋ' => 'ŋ',
  'Ō' => 'ō',
  'Ŏ' => 'ŏ',
  'Ő' => 'ő',
  'Œ' => 'œ',
  'Ŕ' => 'ŕ',
  'Ŗ' => 'ŗ',
  'Ř' => 'ř',
  'Ś' => 'ś',
  'Ŝ' => 'ŝ',
  'Ş' => 'ş',
  'Š' => 'š',
  'Ţ' => 'ţ',
  'Ť' => 'ť',
  'Ŧ' => 'ŧ',
  'Ũ' => 'ũ',
  'Ū' => 'ū',
  'Ŭ' => 'ŭ',
  'Ů' => 'ů',
  'Ű' => 'ű',
  'Ų' => 'ų',
  'Ŵ' => 'ŵ',
  'Ŷ' => 'ŷ',
  'Ÿ' => 'ÿ',
  'Ź' => 'ź',
  'Ż' => 'ż',
  'Ž' => 'ž',
  'Ɓ' => 'ɓ',
  'Ƃ' => 'ƃ',
  'Ƅ' => 'ƅ',
  'Ɔ' => 'ɔ',
  'Ƈ' => 'ƈ',
  'Ɖ' => 'ɖ',
  'Ɗ' => 'ɗ',
  'Ƌ' => 'ƌ',
  'Ǝ' => 'ǝ',
  'Ə' => 'ə',
  'Ɛ' => 'ɛ',
  'Ƒ' => 'ƒ',
  'Ɠ' => 'ɠ',
  'Ɣ' => 'ɣ',
  'Ɩ' => 'ɩ',
  'Ɨ' => 'ɨ',
  'Ƙ' => 'ƙ',
  'Ɯ' => 'ɯ',
  'Ɲ' => 'ɲ',
  'Ɵ' => 'ɵ',
  'Ơ' => 'ơ',
  'Ƣ' => 'ƣ',
  'Ƥ' => 'ƥ',
  'Ʀ' => 'ʀ',
  'Ƨ' => 'ƨ',
  'Ʃ' => 'ʃ',
  'Ƭ' => 'ƭ',
  'Ʈ' => 'ʈ',
  'Ư' => 'ư',
  'Ʊ' => 'ʊ',
  'Ʋ' => 'ʋ',
  'Ƴ' => 'ƴ',
  'Ƶ' => 'ƶ',
  'Ʒ' => 'ʒ',
  'Ƹ' => 'ƹ',
  'Ƽ' => 'ƽ',
  'DŽ' => 'dž',
  'Dž' => 'dž',
  'LJ' => 'lj',
  'Lj' => 'lj',
  'NJ' => 'nj',
  'Nj' => 'nj',
  'Ǎ' => 'ǎ',
  'Ǐ' => 'ǐ',
  'Ǒ' => 'ǒ',
  'Ǔ' => 'ǔ',
  'Ǖ' => 'ǖ',
  'Ǘ' => 'ǘ',
  'Ǚ' => 'ǚ',
  'Ǜ' => 'ǜ',
  'Ǟ' => 'ǟ',
  'Ǡ' => 'ǡ',
  'Ǣ' => 'ǣ',
  'Ǥ' => 'ǥ',
  'Ǧ' => 'ǧ',
  'Ǩ' => 'ǩ',
  'Ǫ' => 'ǫ',
  'Ǭ' => 'ǭ',
  'Ǯ' => 'ǯ',
  'DZ' => 'dz',
  'Dz' => 'dz',
  'Ǵ' => 'ǵ',
  'Ƕ' => 'ƕ',
  'Ƿ' => 'ƿ',
  'Ǹ' => 'ǹ',
  'Ǻ' => 'ǻ',
  'Ǽ' => 'ǽ',
  'Ǿ' => 'ǿ',
  'Ȁ' => 'ȁ',
  'Ȃ' => 'ȃ',
  'Ȅ' => 'ȅ',
  'Ȇ' => 'ȇ',
  'Ȉ' => 'ȉ',
  'Ȋ' => 'ȋ',
  'Ȍ' => 'ȍ',
  'Ȏ' => 'ȏ',
  'Ȑ' => 'ȑ',
  'Ȓ' => 'ȓ',
  'Ȕ' => 'ȕ',
  'Ȗ' => 'ȗ',
  'Ș' => 'ș',
  'Ț' => 'ț',
  'Ȝ' => 'ȝ',
  'Ȟ' => 'ȟ',
  'Ƞ' => 'ƞ',
  'Ȣ' => 'ȣ',
  'Ȥ' => 'ȥ',
  'Ȧ' => 'ȧ',
  'Ȩ' => 'ȩ',
  'Ȫ' => 'ȫ',
  'Ȭ' => 'ȭ',
  'Ȯ' => 'ȯ',
  'Ȱ' => 'ȱ',
  'Ȳ' => 'ȳ',
  'Ⱥ' => 'ⱥ',
  'Ȼ' => 'ȼ',
  'Ƚ' => 'ƚ',
  'Ⱦ' => 'ⱦ',
  'Ɂ' => 'ɂ',
  'Ƀ' => 'ƀ',
  'Ʉ' => 'ʉ',
  'Ʌ' => 'ʌ',
  'Ɇ' => 'ɇ',
  'Ɉ' => 'ɉ',
  'Ɋ' => 'ɋ',
  'Ɍ' => 'ɍ',
  'Ɏ' => 'ɏ',
  'Ͱ' => 'ͱ',
  'Ͳ' => 'ͳ',
  'Ͷ' => 'ͷ',
  'Ϳ' => 'ϳ',
  'Ά' => 'ά',
  'Έ' => 'έ',
  'Ή' => 'ή',
  'Ί' => 'ί',
  'Ό' => 'ό',
  'Ύ' => 'ύ',
  'Ώ' => 'ώ',
  'Α' => 'α',
  'Β' => 'β',
  'Γ' => 'γ',
  'Δ' => 'δ',
  'Ε' => 'ε',
  'Ζ' => 'ζ',
  'Η' => 'η',
  'Θ' => 'θ',
  'Ι' => 'ι',
  'Κ' => 'κ',
  'Λ' => 'λ',
  'Μ' => 'μ',
  'Ν' => 'ν',
  'Ξ' => 'ξ',
  'Ο' => 'ο',
  'Π' => 'π',
  'Ρ' => 'ρ',
  'Σ' => 'σ',
  'Τ' => 'τ',
  'Υ' => 'υ',
  'Φ' => 'φ',
  'Χ' => 'χ',
  'Ψ' => 'ψ',
  'Ω' => 'ω',
  'Ϊ' => 'ϊ',
  'Ϋ' => 'ϋ',
  'Ϗ' => 'ϗ',
  'Ϙ' => 'ϙ',
  'Ϛ' => 'ϛ',
  'Ϝ' => 'ϝ',
  'Ϟ' => 'ϟ',
  'Ϡ' => 'ϡ',
  'Ϣ' => 'ϣ',
  'Ϥ' => 'ϥ',
  'Ϧ' => 'ϧ',
  'Ϩ' => 'ϩ',
  'Ϫ' => 'ϫ',
  'Ϭ' => 'ϭ',
  'Ϯ' => 'ϯ',
  'ϴ' => 'θ',
  'Ϸ' => 'ϸ',
  'Ϲ' => 'ϲ',
  'Ϻ' => 'ϻ',
  'Ͻ' => 'ͻ',
  'Ͼ' => 'ͼ',
  'Ͽ' => 'ͽ',
  'Ѐ' => 'ѐ',
  'Ё' => 'ё',
  'Ђ' => 'ђ',
  'Ѓ' => 'ѓ',
  'Є' => 'є',
  'Ѕ' => 'ѕ',
  'І' => 'і',
  'Ї' => 'ї',
  'Ј' => 'ј',
  'Љ' => 'љ',
  'Њ' => 'њ',
  'Ћ' => 'ћ',
  'Ќ' => 'ќ',
  'Ѝ' => 'ѝ',
  'Ў' => 'ў',
  'Џ' => 'џ',
  'А' => 'а',
  'Б' => 'б',
  'В' => 'в',
  'Г' => 'г',
  'Д' => 'д',
  'Е' => 'е',
  'Ж' => 'ж',
  'З' => 'з',
  'И' => 'и',
  'Й' => 'й',
  'К' => 'к',
  'Л' => 'л',
  'М' => 'м',
  'Н' => 'н',
  'О' => 'о',
  'П' => 'п',
  'Р' => 'р',
  'С' => 'с',
  'Т' => 'т',
  'У' => 'у',
  'Ф' => 'ф',
  'Х' => 'х',
  'Ц' => 'ц',
  'Ч' => 'ч',
  'Ш' => 'ш',
  'Щ' => 'щ',
  'Ъ' => 'ъ',
  'Ы' => 'ы',
  'Ь' => 'ь',
  'Э' => 'э',
  'Ю' => 'ю',
  'Я' => 'я',
  'Ѡ' => 'ѡ',
  'Ѣ' => 'ѣ',
  'Ѥ' => 'ѥ',
  'Ѧ' => 'ѧ',
  'Ѩ' => 'ѩ',
  'Ѫ' => 'ѫ',
  'Ѭ' => 'ѭ',
  'Ѯ' => 'ѯ',
  'Ѱ' => 'ѱ',
  'Ѳ' => 'ѳ',
  'Ѵ' => 'ѵ',
  'Ѷ' => 'ѷ',
  'Ѹ' => 'ѹ',
  'Ѻ' => 'ѻ',
  'Ѽ' => 'ѽ',
  'Ѿ' => 'ѿ',
  'Ҁ' => 'ҁ',
  'Ҋ' => 'ҋ',
  'Ҍ' => 'ҍ',
  'Ҏ' => 'ҏ',
  'Ґ' => 'ґ',
  'Ғ' => 'ғ',
  'Ҕ' => 'ҕ',
  'Җ' => 'җ',
  'Ҙ' => 'ҙ',
  'Қ' => 'қ',
  'Ҝ' => 'ҝ',
  'Ҟ' => 'ҟ',
  'Ҡ' => 'ҡ',
  'Ң' => 'ң',
  'Ҥ' => 'ҥ',
  'Ҧ' => 'ҧ',
  'Ҩ' => 'ҩ',
  'Ҫ' => 'ҫ',
  'Ҭ' => 'ҭ',
  'Ү' => 'ү',
  'Ұ' => 'ұ',
  'Ҳ' => 'ҳ',
  'Ҵ' => 'ҵ',
  'Ҷ' => 'ҷ',
  'Ҹ' => 'ҹ',
  'Һ' => 'һ',
  'Ҽ' => 'ҽ',
  'Ҿ' => 'ҿ',
  'Ӏ' => 'ӏ',
  'Ӂ' => 'ӂ',
  'Ӄ' => 'ӄ',
  'Ӆ' => 'ӆ',
  'Ӈ' => 'ӈ',
  'Ӊ' => 'ӊ',
  'Ӌ' => 'ӌ',
  'Ӎ' => 'ӎ',
  'Ӑ' => 'ӑ',
  'Ӓ' => 'ӓ',
  'Ӕ' => 'ӕ',
  'Ӗ' => 'ӗ',
  'Ә' => 'ә',
  'Ӛ' => 'ӛ',
  'Ӝ' => 'ӝ',
  'Ӟ' => 'ӟ',
  'Ӡ' => 'ӡ',
  'Ӣ' => 'ӣ',
  'Ӥ' => 'ӥ',
  'Ӧ' => 'ӧ',
  'Ө' => 'ө',
  'Ӫ' => 'ӫ',
  'Ӭ' => 'ӭ',
  'Ӯ' => 'ӯ',
  'Ӱ' => 'ӱ',
  'Ӳ' => 'ӳ',
  'Ӵ' => 'ӵ',
  'Ӷ' => 'ӷ',
  'Ӹ' => 'ӹ',
  'Ӻ' => 'ӻ',
  'Ӽ' => 'ӽ',
  'Ӿ' => 'ӿ',
  'Ԁ' => 'ԁ',
  'Ԃ' => 'ԃ',
  'Ԅ' => 'ԅ',
  'Ԇ' => 'ԇ',
  'Ԉ' => 'ԉ',
  'Ԋ' => 'ԋ',
  'Ԍ' => 'ԍ',
  'Ԏ' => 'ԏ',
  'Ԑ' => 'ԑ',
  'Ԓ' => 'ԓ',
  'Ԕ' => 'ԕ',
  'Ԗ' => 'ԗ',
  'Ԙ' => 'ԙ',
  'Ԛ' => 'ԛ',
  'Ԝ' => 'ԝ',
  'Ԟ' => 'ԟ',
  'Ԡ' => 'ԡ',
  'Ԣ' => 'ԣ',
  'Ԥ' => 'ԥ',
  'Ԧ' => 'ԧ',
  'Ԩ' => 'ԩ',
  'Ԫ' => 'ԫ',
  'Ԭ' => 'ԭ',
  'Ԯ' => 'ԯ',
  'Ա' => 'ա',
  'Բ' => 'բ',
  'Գ' => 'գ',
  'Դ' => 'դ',
  'Ե' => 'ե',
  'Զ' => 'զ',
  'Է' => 'է',
  'Ը' => 'ը',
  'Թ' => 'թ',
  'Ժ' => 'ժ',
  'Ի' => 'ի',
  'Լ' => 'լ',
  'Խ' => 'խ',
  'Ծ' => 'ծ',
  'Կ' => 'կ',
  'Հ' => 'հ',
  'Ձ' => 'ձ',
  'Ղ' => 'ղ',
  'Ճ' => 'ճ',
  'Մ' => 'մ',
  'Յ' => 'յ',
  'Ն' => 'ն',
  'Շ' => 'շ',
  'Ո' => 'ո',
  'Չ' => 'չ',
  'Պ' => 'պ',
  'Ջ' => 'ջ',
  'Ռ' => 'ռ',
  'Ս' => 'ս',
  'Վ' => 'վ',
  'Տ' => 'տ',
  'Ր' => 'ր',
  'Ց' => 'ց',
  'Ւ' => 'ւ',
  'Փ' => 'փ',
  'Ք' => 'ք',
  'Օ' => 'օ',
  'Ֆ' => 'ֆ',
  'Ⴀ' => 'ⴀ',
  'Ⴁ' => 'ⴁ',
  'Ⴂ' => 'ⴂ',
  'Ⴃ' => 'ⴃ',
  'Ⴄ' => 'ⴄ',
  'Ⴅ' => 'ⴅ',
  'Ⴆ' => 'ⴆ',
  'Ⴇ' => 'ⴇ',
  'Ⴈ' => 'ⴈ',
  'Ⴉ' => 'ⴉ',
  'Ⴊ' => 'ⴊ',
  'Ⴋ' => 'ⴋ',
  'Ⴌ' => 'ⴌ',
  'Ⴍ' => 'ⴍ',
  'Ⴎ' => 'ⴎ',
  'Ⴏ' => 'ⴏ',
  'Ⴐ' => 'ⴐ',
  'Ⴑ' => 'ⴑ',
  'Ⴒ' => 'ⴒ',
  'Ⴓ' => 'ⴓ',
  'Ⴔ' => 'ⴔ',
  'Ⴕ' => 'ⴕ',
  'Ⴖ' => 'ⴖ',
  'Ⴗ' => 'ⴗ',
  'Ⴘ' => 'ⴘ',
  'Ⴙ' => 'ⴙ',
  'Ⴚ' => 'ⴚ',
  'Ⴛ' => 'ⴛ',
  'Ⴜ' => 'ⴜ',
  'Ⴝ' => 'ⴝ',
  'Ⴞ' => 'ⴞ',
  'Ⴟ' => 'ⴟ',
  'Ⴠ' => 'ⴠ',
  'Ⴡ' => 'ⴡ',
  'Ⴢ' => 'ⴢ',
  'Ⴣ' => 'ⴣ',
  'Ⴤ' => 'ⴤ',
  'Ⴥ' => 'ⴥ',
  'Ⴧ' => 'ⴧ',
  'Ⴭ' => 'ⴭ',
  'Ḁ' => 'ḁ',
  'Ḃ' => 'ḃ',
  'Ḅ' => 'ḅ',
  'Ḇ' => 'ḇ',
  'Ḉ' => 'ḉ',
  'Ḋ' => 'ḋ',
  'Ḍ' => 'ḍ',
  'Ḏ' => 'ḏ',
  'Ḑ' => 'ḑ',
  'Ḓ' => 'ḓ',
  'Ḕ' => 'ḕ',
  'Ḗ' => 'ḗ',
  'Ḙ' => 'ḙ',
  'Ḛ' => 'ḛ',
  'Ḝ' => 'ḝ',
  'Ḟ' => 'ḟ',
  'Ḡ' => 'ḡ',
  'Ḣ' => 'ḣ',
  'Ḥ' => 'ḥ',
  'Ḧ' => 'ḧ',
  'Ḩ' => 'ḩ',
  'Ḫ' => 'ḫ',
  'Ḭ' => 'ḭ',
  'Ḯ' => 'ḯ',
  'Ḱ' => 'ḱ',
  'Ḳ' => 'ḳ',
  'Ḵ' => 'ḵ',
  'Ḷ' => 'ḷ',
  'Ḹ' => 'ḹ',
  'Ḻ' => 'ḻ',
  'Ḽ' => 'ḽ',
  'Ḿ' => 'ḿ',
  'Ṁ' => 'ṁ',
  'Ṃ' => 'ṃ',
  'Ṅ' => 'ṅ',
  'Ṇ' => 'ṇ',
  'Ṉ' => 'ṉ',
  'Ṋ' => 'ṋ',
  'Ṍ' => 'ṍ',
  'Ṏ' => 'ṏ',
  'Ṑ' => 'ṑ',
  'Ṓ' => 'ṓ',
  'Ṕ' => 'ṕ',
  'Ṗ' => 'ṗ',
  'Ṙ' => 'ṙ',
  'Ṛ' => 'ṛ',
  'Ṝ' => 'ṝ',
  'Ṟ' => 'ṟ',
  'Ṡ' => 'ṡ',
  'Ṣ' => 'ṣ',
  'Ṥ' => 'ṥ',
  'Ṧ' => 'ṧ',
  'Ṩ' => 'ṩ',
  'Ṫ' => 'ṫ',
  'Ṭ' => 'ṭ',
  'Ṯ' => 'ṯ',
  'Ṱ' => 'ṱ',
  'Ṳ' => 'ṳ',
  'Ṵ' => 'ṵ',
  'Ṷ' => 'ṷ',
  'Ṹ' => 'ṹ',
  'Ṻ' => 'ṻ',
  'Ṽ' => 'ṽ',
  'Ṿ' => 'ṿ',
  'Ẁ' => 'ẁ',
  'Ẃ' => 'ẃ',
  'Ẅ' => 'ẅ',
  'Ẇ' => 'ẇ',
  'Ẉ' => 'ẉ',
  'Ẋ' => 'ẋ',
  'Ẍ' => 'ẍ',
  'Ẏ' => 'ẏ',
  'Ẑ' => 'ẑ',
  'Ẓ' => 'ẓ',
  'Ẕ' => 'ẕ',
  'ẞ' => 'ß',
  'Ạ' => 'ạ',
  'Ả' => 'ả',
  'Ấ' => 'ấ',
  'Ầ' => 'ầ',
  'Ẩ' => 'ẩ',
  'Ẫ' => 'ẫ',
  'Ậ' => 'ậ',
  'Ắ' => 'ắ',
  'Ằ' => 'ằ',
  'Ẳ' => 'ẳ',
  'Ẵ' => 'ẵ',
  'Ặ' => 'ặ',
  'Ẹ' => 'ẹ',
  'Ẻ' => 'ẻ',
  'Ẽ' => 'ẽ',
  'Ế' => 'ế',
  'Ề' => 'ề',
  'Ể' => 'ể',
  'Ễ' => 'ễ',
  'Ệ' => 'ệ',
  'Ỉ' => 'ỉ',
  'Ị' => 'ị',
  'Ọ' => 'ọ',
  'Ỏ' => 'ỏ',
  'Ố' => 'ố',
  'Ồ' => 'ồ',
  'Ổ' => 'ổ',
  'Ỗ' => 'ỗ',
  'Ộ' => 'ộ',
  'Ớ' => 'ớ',
  'Ờ' => 'ờ',
  'Ở' => 'ở',
  'Ỡ' => 'ỡ',
  'Ợ' => 'ợ',
  'Ụ' => 'ụ',
  'Ủ' => 'ủ',
  'Ứ' => 'ứ',
  'Ừ' => 'ừ',
  'Ử' => 'ử',
  'Ữ' => 'ữ',
  'Ự' => 'ự',
  'Ỳ' => 'ỳ',
  'Ỵ' => 'ỵ',
  'Ỷ' => 'ỷ',
  'Ỹ' => 'ỹ',
  'Ỻ' => 'ỻ',
  'Ỽ' => 'ỽ',
  'Ỿ' => 'ỿ',
  'Ἀ' => 'ἀ',
  'Ἁ' => 'ἁ',
  'Ἂ' => 'ἂ',
  'Ἃ' => 'ἃ',
  'Ἄ' => 'ἄ',
  'Ἅ' => 'ἅ',
  'Ἆ' => 'ἆ',
  'Ἇ' => 'ἇ',
  'Ἐ' => 'ἐ',
  'Ἑ' => 'ἑ',
  'Ἒ' => 'ἒ',
  'Ἓ' => 'ἓ',
  'Ἔ' => 'ἔ',
  'Ἕ' => 'ἕ',
  'Ἠ' => 'ἠ',
  'Ἡ' => 'ἡ',
  'Ἢ' => 'ἢ',
  'Ἣ' => 'ἣ',
  'Ἤ' => 'ἤ',
  'Ἥ' => 'ἥ',
  'Ἦ' => 'ἦ',
  'Ἧ' => 'ἧ',
  'Ἰ' => 'ἰ',
  'Ἱ' => 'ἱ',
  'Ἲ' => 'ἲ',
  'Ἳ' => 'ἳ',
  'Ἴ' => 'ἴ',
  'Ἵ' => 'ἵ',
  'Ἶ' => 'ἶ',
  'Ἷ' => 'ἷ',
  'Ὀ' => 'ὀ',
  'Ὁ' => 'ὁ',
  'Ὂ' => 'ὂ',
  'Ὃ' => 'ὃ',
  'Ὄ' => 'ὄ',
  'Ὅ' => 'ὅ',
  'Ὑ' => 'ὑ',
  'Ὓ' => 'ὓ',
  'Ὕ' => 'ὕ',
  'Ὗ' => 'ὗ',
  'Ὠ' => 'ὠ',
  'Ὡ' => 'ὡ',
  'Ὢ' => 'ὢ',
  'Ὣ' => 'ὣ',
  'Ὤ' => 'ὤ',
  'Ὥ' => 'ὥ',
  'Ὦ' => 'ὦ',
  'Ὧ' => 'ὧ',
  'ᾈ' => 'ᾀ',
  'ᾉ' => 'ᾁ',
  'ᾊ' => 'ᾂ',
  'ᾋ' => 'ᾃ',
  'ᾌ' => 'ᾄ',
  'ᾍ' => 'ᾅ',
  'ᾎ' => 'ᾆ',
  'ᾏ' => 'ᾇ',
  'ᾘ' => 'ᾐ',
  'ᾙ' => 'ᾑ',
  'ᾚ' => 'ᾒ',
  'ᾛ' => 'ᾓ',
  'ᾜ' => 'ᾔ',
  'ᾝ' => 'ᾕ',
  'ᾞ' => 'ᾖ',
  'ᾟ' => 'ᾗ',
  'ᾨ' => 'ᾠ',
  'ᾩ' => 'ᾡ',
  'ᾪ' => 'ᾢ',
  'ᾫ' => 'ᾣ',
  'ᾬ' => 'ᾤ',
  'ᾭ' => 'ᾥ',
  'ᾮ' => 'ᾦ',
  'ᾯ' => 'ᾧ',
  'Ᾰ' => 'ᾰ',
  'Ᾱ' => 'ᾱ',
  'Ὰ' => 'ὰ',
  'Ά' => 'ά',
  'ᾼ' => 'ᾳ',
  'Ὲ' => 'ὲ',
  'Έ' => 'έ',
  'Ὴ' => 'ὴ',
  'Ή' => 'ή',
  'ῌ' => 'ῃ',
  'Ῐ' => 'ῐ',
  'Ῑ' => 'ῑ',
  'Ὶ' => 'ὶ',
  'Ί' => 'ί',
  'Ῠ' => 'ῠ',
  'Ῡ' => 'ῡ',
  'Ὺ' => 'ὺ',
  'Ύ' => 'ύ',
  'Ῥ' => 'ῥ',
  'Ὸ' => 'ὸ',
  'Ό' => 'ό',
  'Ὼ' => 'ὼ',
  'Ώ' => 'ώ',
  'ῼ' => 'ῳ',
  'Ω' => 'ω',
  'K' => 'k',
  'Å' => 'å',
  'Ⅎ' => 'ⅎ',
  'Ⅰ' => 'ⅰ',
  'Ⅱ' => 'ⅱ',
  'Ⅲ' => 'ⅲ',
  'Ⅳ' => 'ⅳ',
  'Ⅴ' => 'ⅴ',
  'Ⅵ' => 'ⅵ',
  'Ⅶ' => 'ⅶ',
  'Ⅷ' => 'ⅷ',
  'Ⅸ' => 'ⅸ',
  'Ⅹ' => 'ⅹ',
  'Ⅺ' => 'ⅺ',
  'Ⅻ' => 'ⅻ',
  'Ⅼ' => 'ⅼ',
  'Ⅽ' => 'ⅽ',
  'Ⅾ' => 'ⅾ',
  'Ⅿ' => 'ⅿ',
  'Ↄ' => 'ↄ',
  'Ⓐ' => 'ⓐ',
  'Ⓑ' => 'ⓑ',
  'Ⓒ' => 'ⓒ',
  'Ⓓ' => 'ⓓ',
  'Ⓔ' => 'ⓔ',
  'Ⓕ' => 'ⓕ',
  'Ⓖ' => 'ⓖ',
  'Ⓗ' => 'ⓗ',
  'Ⓘ' => 'ⓘ',
  'Ⓙ' => 'ⓙ',
  'Ⓚ' => 'ⓚ',
  'Ⓛ' => 'ⓛ',
  'Ⓜ' => 'ⓜ',
  'Ⓝ' => 'ⓝ',
  'Ⓞ' => 'ⓞ',
  'Ⓟ' => 'ⓟ',
  'Ⓠ' => 'ⓠ',
  'Ⓡ' => 'ⓡ',
  'Ⓢ' => 'ⓢ',
  'Ⓣ' => 'ⓣ',
  'Ⓤ' => 'ⓤ',
  'Ⓥ' => 'ⓥ',
  'Ⓦ' => 'ⓦ',
  'Ⓧ' => 'ⓧ',
  'Ⓨ' => 'ⓨ',
  'Ⓩ' => 'ⓩ',
  'Ⰰ' => 'ⰰ',
  'Ⰱ' => 'ⰱ',
  'Ⰲ' => 'ⰲ',
  'Ⰳ' => 'ⰳ',
  'Ⰴ' => 'ⰴ',
  'Ⰵ' => 'ⰵ',
  'Ⰶ' => 'ⰶ',
  'Ⰷ' => 'ⰷ',
  'Ⰸ' => 'ⰸ',
  'Ⰹ' => 'ⰹ',
  'Ⰺ' => 'ⰺ',
  'Ⰻ' => 'ⰻ',
  'Ⰼ' => 'ⰼ',
  'Ⰽ' => 'ⰽ',
  'Ⰾ' => 'ⰾ',
  'Ⰿ' => 'ⰿ',
  'Ⱀ' => 'ⱀ',
  'Ⱁ' => 'ⱁ',
  'Ⱂ' => 'ⱂ',
  'Ⱃ' => 'ⱃ',
  'Ⱄ' => 'ⱄ',
  'Ⱅ' => 'ⱅ',
  'Ⱆ' => 'ⱆ',
  'Ⱇ' => 'ⱇ',
  'Ⱈ' => 'ⱈ',
  'Ⱉ' => 'ⱉ',
  'Ⱊ' => 'ⱊ',
  'Ⱋ' => 'ⱋ',
  'Ⱌ' => 'ⱌ',
  'Ⱍ' => 'ⱍ',
  'Ⱎ' => 'ⱎ',
  'Ⱏ' => 'ⱏ',
  'Ⱐ' => 'ⱐ',
  'Ⱑ' => 'ⱑ',
  'Ⱒ' => 'ⱒ',
  'Ⱓ' => 'ⱓ',
  'Ⱔ' => 'ⱔ',
  'Ⱕ' => 'ⱕ',
  'Ⱖ' => 'ⱖ',
  'Ⱗ' => 'ⱗ',
  'Ⱘ' => 'ⱘ',
  'Ⱙ' => 'ⱙ',
  'Ⱚ' => 'ⱚ',
  'Ⱛ' => 'ⱛ',
  'Ⱜ' => 'ⱜ',
  'Ⱝ' => 'ⱝ',
  'Ⱞ' => 'ⱞ',
  'Ⱡ' => 'ⱡ',
  'Ɫ' => 'ɫ',
  'Ᵽ' => 'ᵽ',
  'Ɽ' => 'ɽ',
  'Ⱨ' => 'ⱨ',
  'Ⱪ' => 'ⱪ',
  'Ⱬ' => 'ⱬ',
  'Ɑ' => 'ɑ',
  'Ɱ' => 'ɱ',
  'Ɐ' => 'ɐ',
  'Ɒ' => 'ɒ',
  'Ⱳ' => 'ⱳ',
  'Ⱶ' => 'ⱶ',
  'Ȿ' => 'ȿ',
  'Ɀ' => 'ɀ',
  'Ⲁ' => 'ⲁ',
  'Ⲃ' => 'ⲃ',
  'Ⲅ' => 'ⲅ',
  'Ⲇ' => 'ⲇ',
  'Ⲉ' => 'ⲉ',
  'Ⲋ' => 'ⲋ',
  'Ⲍ' => 'ⲍ',
  'Ⲏ' => 'ⲏ',
  'Ⲑ' => 'ⲑ',
  'Ⲓ' => 'ⲓ',
  'Ⲕ' => 'ⲕ',
  'Ⲗ' => 'ⲗ',
  'Ⲙ' => 'ⲙ',
  'Ⲛ' => 'ⲛ',
  'Ⲝ' => 'ⲝ',
  'Ⲟ' => 'ⲟ',
  'Ⲡ' => 'ⲡ',
  'Ⲣ' => 'ⲣ',
  'Ⲥ' => 'ⲥ',
  'Ⲧ' => 'ⲧ',
  'Ⲩ' => 'ⲩ',
  'Ⲫ' => 'ⲫ',
  'Ⲭ' => 'ⲭ',
  'Ⲯ' => 'ⲯ',
  'Ⲱ' => 'ⲱ',
  'Ⲳ' => 'ⲳ',
  'Ⲵ' => 'ⲵ',
  'Ⲷ' => 'ⲷ',
  'Ⲹ' => 'ⲹ',
  'Ⲻ' => 'ⲻ',
  'Ⲽ' => 'ⲽ',
  'Ⲿ' => 'ⲿ',
  'Ⳁ' => 'ⳁ',
  'Ⳃ' => 'ⳃ',
  'Ⳅ' => 'ⳅ',
  'Ⳇ' => 'ⳇ',
  'Ⳉ' => 'ⳉ',
  'Ⳋ' => 'ⳋ',
  'Ⳍ' => 'ⳍ',
  'Ⳏ' => 'ⳏ',
  'Ⳑ' => 'ⳑ',
  'Ⳓ' => 'ⳓ',
  'Ⳕ' => 'ⳕ',
  'Ⳗ' => 'ⳗ',
  'Ⳙ' => 'ⳙ',
  'Ⳛ' => 'ⳛ',
  'Ⳝ' => 'ⳝ',
  'Ⳟ' => 'ⳟ',
  'Ⳡ' => 'ⳡ',
  'Ⳣ' => 'ⳣ',
  'Ⳬ' => 'ⳬ',
  'Ⳮ' => 'ⳮ',
  'Ⳳ' => 'ⳳ',
  'Ꙁ' => 'ꙁ',
  'Ꙃ' => 'ꙃ',
  'Ꙅ' => 'ꙅ',
  'Ꙇ' => 'ꙇ',
  'Ꙉ' => 'ꙉ',
  'Ꙋ' => 'ꙋ',
  'Ꙍ' => 'ꙍ',
  'Ꙏ' => 'ꙏ',
  'Ꙑ' => 'ꙑ',
  'Ꙓ' => 'ꙓ',
  'Ꙕ' => 'ꙕ',
  'Ꙗ' => 'ꙗ',
  'Ꙙ' => 'ꙙ',
  'Ꙛ' => 'ꙛ',
  'Ꙝ' => 'ꙝ',
  'Ꙟ' => 'ꙟ',
  'Ꙡ' => 'ꙡ',
  'Ꙣ' => 'ꙣ',
  'Ꙥ' => 'ꙥ',
  'Ꙧ' => 'ꙧ',
  'Ꙩ' => 'ꙩ',
  'Ꙫ' => 'ꙫ',
  'Ꙭ' => 'ꙭ',
  'Ꚁ' => 'ꚁ',
  'Ꚃ' => 'ꚃ',
  'Ꚅ' => 'ꚅ',
  'Ꚇ' => 'ꚇ',
  'Ꚉ' => 'ꚉ',
  'Ꚋ' => 'ꚋ',
  'Ꚍ' => 'ꚍ',
  'Ꚏ' => 'ꚏ',
  'Ꚑ' => 'ꚑ',
  'Ꚓ' => 'ꚓ',
  'Ꚕ' => 'ꚕ',
  'Ꚗ' => 'ꚗ',
  'Ꚙ' => 'ꚙ',
  'Ꚛ' => 'ꚛ',
  'Ꜣ' => 'ꜣ',
  'Ꜥ' => 'ꜥ',
  'Ꜧ' => 'ꜧ',
  'Ꜩ' => 'ꜩ',
  'Ꜫ' => 'ꜫ',
  'Ꜭ' => 'ꜭ',
  'Ꜯ' => 'ꜯ',
  'Ꜳ' => 'ꜳ',
  'Ꜵ' => 'ꜵ',
  'Ꜷ' => 'ꜷ',
  'Ꜹ' => 'ꜹ',
  'Ꜻ' => 'ꜻ',
  'Ꜽ' => 'ꜽ',
  'Ꜿ' => 'ꜿ',
  'Ꝁ' => 'ꝁ',
  'Ꝃ' => 'ꝃ',
  'Ꝅ' => 'ꝅ',
  'Ꝇ' => 'ꝇ',
  'Ꝉ' => 'ꝉ',
  'Ꝋ' => 'ꝋ',
  'Ꝍ' => 'ꝍ',
  'Ꝏ' => 'ꝏ',
  'Ꝑ' => 'ꝑ',
  'Ꝓ' => 'ꝓ',
  'Ꝕ' => 'ꝕ',
  'Ꝗ' => 'ꝗ',
  'Ꝙ' => 'ꝙ',
  'Ꝛ' => 'ꝛ',
  'Ꝝ' => 'ꝝ',
  'Ꝟ' => 'ꝟ',
  'Ꝡ' => 'ꝡ',
  'Ꝣ' => 'ꝣ',
  'Ꝥ' => 'ꝥ',
  'Ꝧ' => 'ꝧ',
  'Ꝩ' => 'ꝩ',
  'Ꝫ' => 'ꝫ',
  'Ꝭ' => 'ꝭ',
  'Ꝯ' => 'ꝯ',
  'Ꝺ' => 'ꝺ',
  'Ꝼ' => 'ꝼ',
  'Ᵹ' => 'ᵹ',
  'Ꝿ' => 'ꝿ',
  'Ꞁ' => 'ꞁ',
  'Ꞃ' => 'ꞃ',
  'Ꞅ' => 'ꞅ',
  'Ꞇ' => 'ꞇ',
  'Ꞌ' => 'ꞌ',
  'Ɥ' => 'ɥ',
  'Ꞑ' => 'ꞑ',
  'Ꞓ' => 'ꞓ',
  'Ꞗ' => 'ꞗ',
  'Ꞙ' => 'ꞙ',
  'Ꞛ' => 'ꞛ',
  'Ꞝ' => 'ꞝ',
  'Ꞟ' => 'ꞟ',
  'Ꞡ' => 'ꞡ',
  'Ꞣ' => 'ꞣ',
  'Ꞥ' => 'ꞥ',
  'Ꞧ' => 'ꞧ',
  'Ꞩ' => 'ꞩ',
  'Ɦ' => 'ɦ',
  'Ɜ' => 'ɜ',
  'Ɡ' => 'ɡ',
  'Ɬ' => 'ɬ',
  'Ʞ' => 'ʞ',
  'Ʇ' => 'ʇ',
  'A' => 'a',
  'B' => 'b',
  'C' => 'c',
  'D' => 'd',
  'E' => 'e',
  'F' => 'f',
  'G' => 'g',
  'H' => 'h',
  'I' => 'i',
  'J' => 'j',
  'K' => 'k',
  'L' => 'l',
  'M' => 'm',
  'N' => 'n',
  'O' => 'o',
  'P' => 'p',
  'Q' => 'q',
  'R' => 'r',
  'S' => 's',
  'T' => 't',
  'U' => 'u',
  'V' => 'v',
  'W' => 'w',
  'X' => 'x',
  'Y' => 'y',
  'Z' => 'z',
  '𐐀' => '𐐨',
  '𐐁' => '𐐩',
  '𐐂' => '𐐪',
  '𐐃' => '𐐫',
  '𐐄' => '𐐬',
  '𐐅' => '𐐭',
  '𐐆' => '𐐮',
  '𐐇' => '𐐯',
  '𐐈' => '𐐰',
  '𐐉' => '𐐱',
  '𐐊' => '𐐲',
  '𐐋' => '𐐳',
  '𐐌' => '𐐴',
  '𐐍' => '𐐵',
  '𐐎' => '𐐶',
  '𐐏' => '𐐷',
  '𐐐' => '𐐸',
  '𐐑' => '𐐹',
  '𐐒' => '𐐺',
  '𐐓' => '𐐻',
  '𐐔' => '𐐼',
  '𐐕' => '𐐽',
  '𐐖' => '𐐾',
  '𐐗' => '𐐿',
  '𐐘' => '𐑀',
  '𐐙' => '𐑁',
  '𐐚' => '𐑂',
  '𐐛' => '𐑃',
  '𐐜' => '𐑄',
  '𐐝' => '𐑅',
  '𐐞' => '𐑆',
  '𐐟' => '𐑇',
  '𐐠' => '𐑈',
  '𐐡' => '𐑉',
  '𐐢' => '𐑊',
  '𐐣' => '𐑋',
  '𐐤' => '𐑌',
  '𐐥' => '𐑍',
  '𐐦' => '𐑎',
  '𐐧' => '𐑏',
  '𑢠' => '𑣀',
  '𑢡' => '𑣁',
  '𑢢' => '𑣂',
  '𑢣' => '𑣃',
  '𑢤' => '𑣄',
  '𑢥' => '𑣅',
  '𑢦' => '𑣆',
  '𑢧' => '𑣇',
  '𑢨' => '𑣈',
  '𑢩' => '𑣉',
  '𑢪' => '𑣊',
  '𑢫' => '𑣋',
  '𑢬' => '𑣌',
  '𑢭' => '𑣍',
  '𑢮' => '𑣎',
  '𑢯' => '𑣏',
  '𑢰' => '𑣐',
  '𑢱' => '𑣑',
  '𑢲' => '𑣒',
  '𑢳' => '𑣓',
  '𑢴' => '𑣔',
  '𑢵' => '𑣕',
  '𑢶' => '𑣖',
  '𑢷' => '𑣗',
  '𑢸' => '𑣘',
  '𑢹' => '𑣙',
  '𑢺' => '𑣚',
  '𑢻' => '𑣛',
  '𑢼' => '𑣜',
  '𑢽' => '𑣝',
  '𑢾' => '𑣞',
  '𑢿' => '𑣟',
);

$result =& $data;
unset($data);

return $result;
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Mbstring;

/**
 * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
 *
 * Implemented:
 * - mb_chr                  - Returns a specific character from its Unicode code point
 * - mb_convert_encoding     - Convert character encoding
 * - mb_convert_variables    - Convert character code in variable(s)
 * - mb_decode_mimeheader    - Decode string in MIME header field
 * - mb_encode_mimeheader    - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
 * - mb_decode_numericentity - Decode HTML numeric string reference to character
 * - mb_encode_numericentity - Encode character to HTML numeric string reference
 * - mb_convert_case         - Perform case folding on a string
 * - mb_detect_encoding      - Detect character encoding
 * - mb_get_info             - Get internal settings of mbstring
 * - mb_http_input           - Detect HTTP input character encoding
 * - mb_http_output          - Set/Get HTTP output character encoding
 * - mb_internal_encoding    - Set/Get internal character encoding
 * - mb_list_encodings       - Returns an array of all supported encodings
 * - mb_ord                  - Returns the Unicode code point of a character
 * - mb_output_handler       - Callback function converts character encoding in output buffer
 * - mb_scrub                - Replaces ill-formed byte sequences with substitute characters
 * - mb_strlen               - Get string length
 * - mb_strpos               - Find position of first occurrence of string in a string
 * - mb_strrpos              - Find position of last occurrence of a string in a string
 * - mb_strtolower           - Make a string lowercase
 * - mb_strtoupper           - Make a string uppercase
 * - mb_substitute_character - Set/Get substitution character
 * - mb_substr               - Get part of string
 * - mb_stripos              - Finds position of first occurrence of a string within another, case insensitive
 * - mb_stristr              - Finds first occurrence of a string within another, case insensitive
 * - mb_strrchr              - Finds the last occurrence of a character in a string within another
 * - mb_strrichr             - Finds the last occurrence of a character in a string within another, case insensitive
 * - mb_strripos             - Finds position of last occurrence of a string within another, case insensitive
 * - mb_strstr               - Finds first occurrence of a string within anothers
 * - mb_strwidth             - Return width of string
 * - mb_substr_count         - Count the number of substring occurrences
 *
 * Not implemented:
 * - mb_convert_kana         - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
 * - mb_ereg_*               - Regular expression with multibyte support
 * - mb_parse_str            - Parse GET/POST/COOKIE data and set global variable
 * - mb_preferred_mime_name  - Get MIME charset string
 * - mb_regex_encoding       - Returns current encoding for multibyte regex as string
 * - mb_regex_set_options    - Set/Get the default options for mbregex functions
 * - mb_send_mail            - Send encoded mail
 * - mb_split                - Split multibyte string using regular expression
 * - mb_strcut               - Get part of string
 * - mb_strimwidth           - Get truncated string with specified width
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
final class Mbstring
{
    const MB_CASE_FOLD = PHP_INT_MAX;

    private static $encodingList = array('ASCII', 'UTF-8');
    private static $language = 'neutral';
    private static $internalEncoding = 'UTF-8';
    private static $caseFold = array(
        array('µ','ſ',"\xCD\x85",'ς',"\xCF\x90","\xCF\x91","\xCF\x95","\xCF\x96","\xCF\xB0","\xCF\xB1","\xCF\xB5","\xE1\xBA\x9B","\xE1\xBE\xBE"),
        array('μ','s','ι',       'σ','β',       'θ',       'φ',       'π',       'κ',       'ρ',       'ε',       "\xE1\xB9\xA1",'ι'),
    );

    public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
    {
        if (is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
            $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
        } else {
            $fromEncoding = self::getEncoding($fromEncoding);
        }

        $toEncoding = self::getEncoding($toEncoding);

        if ('BASE64' === $fromEncoding) {
            $s = base64_decode($s);
            $fromEncoding = $toEncoding;
        }

        if ('BASE64' === $toEncoding) {
            return base64_encode($s);
        }

        if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
            if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
                $fromEncoding = 'Windows-1252';
            }
            if ('UTF-8' !== $fromEncoding) {
                $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
            }

            return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s);
        }

        if ('HTML-ENTITIES' === $fromEncoding) {
            $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8');
            $fromEncoding = 'UTF-8';
        }

        return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
    }

    public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null)
    {
        $vars = array(&$a, &$b, &$c, &$d, &$e, &$f);

        $ok = true;
        array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
            if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
                $ok = false;
            }
        });

        return $ok ? $fromEncoding : false;
    }

    public static function mb_decode_mimeheader($s)
    {
        return iconv_mime_decode($s, 2, self::$internalEncoding);
    }

    public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
    {
        trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING);
    }

    public static function mb_decode_numericentity($s, $convmap, $encoding = null)
    {
        if (null !== $s && !is_scalar($s) && !(is_object($s) && method_exists($s, '__toString'))) {
            trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.gettype($s).' given', E_USER_WARNING);
            return null;
        }

        if (!is_array($convmap) || !$convmap) {
            return false;
        }

        if (null !== $encoding && !is_scalar($encoding)) {
            trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.gettype($s).' given', E_USER_WARNING);
            return '';  // Instead of null (cf. mb_encode_numericentity).
        }

        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        $cnt = floor(count($convmap) / 4) * 4;

        for ($i = 0; $i < $cnt; $i += 4) {
            // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
            $convmap[$i] += $convmap[$i + 2];
            $convmap[$i + 1] += $convmap[$i + 2];
        }

        $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
            $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
            for ($i = 0; $i < $cnt; $i += 4) {
                if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
                    return Mbstring::mb_chr($c - $convmap[$i + 2]);
                }
            }
            return $m[0];
        }, $s);

        if (null === $encoding) {
            return $s;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $s);
    }

    public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
    {
        if (null !== $s && !is_scalar($s) && !(is_object($s) && method_exists($s, '__toString'))) {
            trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.gettype($s).' given', E_USER_WARNING);
            return null;
        }

        if (!is_array($convmap) || !$convmap) {
            return false;
        }

        if (null !== $encoding && !is_scalar($encoding)) {
            trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.gettype($s).' given', E_USER_WARNING);
            return null;  // Instead of '' (cf. mb_decode_numericentity).
        }

        if (null !== $is_hex && !is_scalar($is_hex)) {
            trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.gettype($s).' given', E_USER_WARNING);
            return null;
        }

        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);

        $cnt = floor(count($convmap) / 4) * 4;
        $i = 0;
        $len = strlen($s);
        $result = '';

        while ($i < $len) {
            $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
            $uchr = substr($s, $i, $ulen);
            $i += $ulen;
            $c = self::mb_ord($uchr);

            for ($j = 0; $j < $cnt; $j += 4) {
                if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
                    $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
                    $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
                    continue 2;
                }
            }
            $result .= $uchr;
        }

        if (null === $encoding) {
            return $result;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $result);
    }

    public static function mb_convert_case($s, $mode, $encoding = null)
    {
        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        if (MB_CASE_TITLE == $mode) {
            $s = preg_replace_callback('/\b\p{Ll}/u', array(__CLASS__, 'title_case_upper'), $s);
            $s = preg_replace_callback('/\B[\p{Lu}\p{Lt}]+/u', array(__CLASS__, 'title_case_lower'), $s);
        } else {
            if (MB_CASE_UPPER == $mode) {
                static $upper = null;
                if (null === $upper) {
                    $upper = self::getData('upperCase');
                }
                $map = $upper;
            } else {
                if (self::MB_CASE_FOLD === $mode) {
                    $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s);
                }

                static $lower = null;
                if (null === $lower) {
                    $lower = self::getData('lowerCase');
                }
                $map = $lower;
            }

            static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);

            $i = 0;
            $len = strlen($s);

            while ($i < $len) {
                $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
                $uchr = substr($s, $i, $ulen);
                $i += $ulen;

                if (isset($map[$uchr])) {
                    $uchr = $map[$uchr];
                    $nlen = strlen($uchr);

                    if ($nlen == $ulen) {
                        $nlen = $i;
                        do {
                            $s[--$nlen] = $uchr[--$ulen];
                        } while ($ulen);
                    } else {
                        $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
                        $len += $nlen - $ulen;
                        $i   += $nlen - $ulen;
                    }
                }
            }
        }

        if (null === $encoding) {
            return $s;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $s);
    }

    public static function mb_internal_encoding($encoding = null)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) {
            self::$internalEncoding = $encoding;

            return true;
        }

        return false;
    }

    public static function mb_language($lang = null)
    {
        if (null === $lang) {
            return self::$language;
        }

        switch ($lang = strtolower($lang)) {
            case 'uni':
            case 'neutral':
                self::$language = $lang;

                return true;
        }

        return false;
    }

    public static function mb_list_encodings()
    {
        return array('UTF-8');
    }

    public static function mb_encoding_aliases($encoding)
    {
        switch (strtoupper($encoding)) {
            case 'UTF8':
            case 'UTF-8':
                return array('utf8');
        }

        return false;
    }

    public static function mb_check_encoding($var = null, $encoding = null)
    {
        if (null === $encoding) {
            if (null === $var) {
                return false;
            }
            $encoding = self::$internalEncoding;
        }

        return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var);
    }

    public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
    {
        if (null === $encodingList) {
            $encodingList = self::$encodingList;
        } else {
            if (!is_array($encodingList)) {
                $encodingList = array_map('trim', explode(',', $encodingList));
            }
            $encodingList = array_map('strtoupper', $encodingList);
        }

        foreach ($encodingList as $enc) {
            switch ($enc) {
                case 'ASCII':
                    if (!preg_match('/[\x80-\xFF]/', $str)) {
                        return $enc;
                    }
                    break;

                case 'UTF8':
                case 'UTF-8':
                    if (preg_match('//u', $str)) {
                        return 'UTF-8';
                    }
                    break;

                default:
                    if (0 === strncmp($enc, 'ISO-8859-', 9)) {
                        return $enc;
                    }
            }
        }

        return false;
    }

    public static function mb_detect_order($encodingList = null)
    {
        if (null === $encodingList) {
            return self::$encodingList;
        }

        if (!is_array($encodingList)) {
            $encodingList = array_map('trim', explode(',', $encodingList));
        }
        $encodingList = array_map('strtoupper', $encodingList);

        foreach ($encodingList as $enc) {
            switch ($enc) {
                default:
                    if (strncmp($enc, 'ISO-8859-', 9)) {
                        return false;
                    }
                case 'ASCII':
                case 'UTF8':
                case 'UTF-8':
            }
        }

        self::$encodingList = $encodingList;

        return true;
    }

    public static function mb_strlen($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strlen($s);
        }

        return @iconv_strlen($s, $encoding);
    }

    public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strpos($haystack, $needle, $offset);
        }

        $needle = (string) $needle;
        if ('' === $needle) {
            trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING);

            return false;
        }

        return iconv_strpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strrpos($haystack, $needle, $offset);
        }

        if ($offset != (int) $offset) {
            $offset = 0;
        } elseif ($offset = (int) $offset) {
            if ($offset < 0) {
                $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
                $offset = 0;
            } else {
                $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
            }
        }

        $pos = iconv_strrpos($haystack, $needle, $encoding);

        return false !== $pos ? $offset + $pos : false;
    }

    public static function mb_strtolower($s, $encoding = null)
    {
        return self::mb_convert_case($s, MB_CASE_LOWER, $encoding);
    }

    public static function mb_strtoupper($s, $encoding = null)
    {
        return self::mb_convert_case($s, MB_CASE_UPPER, $encoding);
    }

    public static function mb_substitute_character($c = null)
    {
        if (0 === strcasecmp($c, 'none')) {
            return true;
        }

        return null !== $c ? false : 'none';
    }

    public static function mb_substr($s, $start, $length = null, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return substr($s, $start, null === $length ? 2147483647 : $length);
        }

        if ($start < 0) {
            $start = iconv_strlen($s, $encoding) + $start;
            if ($start < 0) {
                $start = 0;
            }
        }

        if (null === $length) {
            $length = 2147483647;
        } elseif ($length < 0) {
            $length = iconv_strlen($s, $encoding) + $length - $start;
            if ($length < 0) {
                return '';
            }
        }

        return (string) iconv_substr($s, $start, $length, $encoding);
    }

    public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);

        return self::mb_strpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
    {
        $pos = self::mb_stripos($haystack, $needle, 0, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strrchr($haystack, $needle, $part);
        }
        $needle = self::mb_substr($needle, 0, 1, $encoding);
        $pos = iconv_strrpos($haystack, $needle, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
    {
        $needle = self::mb_substr($needle, 0, 1, $encoding);
        $pos = self::mb_strripos($haystack, $needle, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);

        return self::mb_strrpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
    {
        $pos = strpos($haystack, $needle);
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return substr($haystack, 0, $pos);
        }

        return substr($haystack, $pos);
    }

    public static function mb_get_info($type = 'all')
    {
        $info = array(
            'internal_encoding' => self::$internalEncoding,
            'http_output' => 'pass',
            'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
            'func_overload' => 0,
            'func_overload_list' => 'no overload',
            'mail_charset' => 'UTF-8',
            'mail_header_encoding' => 'BASE64',
            'mail_body_encoding' => 'BASE64',
            'illegal_chars' => 0,
            'encoding_translation' => 'Off',
            'language' => self::$language,
            'detect_order' => self::$encodingList,
            'substitute_character' => 'none',
            'strict_detection' => 'Off',
        );

        if ('all' === $type) {
            return $info;
        }
        if (isset($info[$type])) {
            return $info[$type];
        }

        return false;
    }

    public static function mb_http_input($type = '')
    {
        return false;
    }

    public static function mb_http_output($encoding = null)
    {
        return null !== $encoding ? 'pass' === $encoding : 'pass';
    }

    public static function mb_strwidth($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);

        if ('UTF-8' !== $encoding) {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);

        return ($wide << 1) + iconv_strlen($s, 'UTF-8');
    }

    public static function mb_substr_count($haystack, $needle, $encoding = null)
    {
        return substr_count($haystack, $needle);
    }

    public static function mb_output_handler($contents, $status)
    {
        return $contents;
    }

    public static function mb_chr($code, $encoding = null)
    {
        if (0x80 > $code %= 0x200000) {
            $s = chr($code);
        } elseif (0x800 > $code) {
            $s = chr(0xC0 | $code >> 6).chr(0x80 | $code & 0x3F);
        } elseif (0x10000 > $code) {
            $s = chr(0xE0 | $code >> 12).chr(0x80 | $code >> 6 & 0x3F).chr(0x80 | $code & 0x3F);
        } else {
            $s = chr(0xF0 | $code >> 18).chr(0x80 | $code >> 12 & 0x3F).chr(0x80 | $code >> 6 & 0x3F).chr(0x80 | $code & 0x3F);
        }

        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
            $s = mb_convert_encoding($s, $encoding, 'UTF-8');
        }

        return $s;
    }

    public static function mb_ord($s, $encoding = null)
    {
        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
            $s = mb_convert_encoding($s, 'UTF-8', $encoding);
        }

        $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
        if (0xF0 <= $code) {
            return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
        }
        if (0xE0 <= $code) {
            return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
        }
        if (0xC0 <= $code) {
            return (($code - 0xC0) << 6) + $s[2] - 0x80;
        }

        return $code;
    }

    private static function getSubpart($pos, $part, $haystack, $encoding)
    {
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return self::mb_substr($haystack, 0, $pos, $encoding);
        }

        return self::mb_substr($haystack, $pos, null, $encoding);
    }

    private static function html_encoding_callback(array $m)
    {
        $i = 1;
        $entities = '';
        $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8'));

        while (isset($m[$i])) {
            if (0x80 > $m[$i]) {
                $entities .= chr($m[$i++]);
                continue;
            }
            if (0xF0 <= $m[$i]) {
                $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } elseif (0xE0 <= $m[$i]) {
                $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } else {
                $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
            }

            $entities .= '&#'.$c.';';
        }

        return $entities;
    }

    private static function title_case_lower(array $s)
    {
        return self::mb_convert_case($s[0], MB_CASE_LOWER, 'UTF-8');
    }

    private static function title_case_upper(array $s)
    {
        return self::mb_convert_case($s[0], MB_CASE_UPPER, 'UTF-8');
    }

    private static function getData($file)
    {
        if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
            return require $file;
        }

        return false;
    }

    private static function getEncoding($encoding)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        $encoding = strtoupper($encoding);

        if ('8BIT' === $encoding || 'BINARY' === $encoding) {
            return 'CP850';
        }
        if ('UTF8' === $encoding) {
            return 'UTF-8';
        }

        return $encoding;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect option name typed in the console.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * ExceptionInterface.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
interface ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect command name typed in the console.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
    private $alternatives;

    /**
     * @param string     $message      Exception message to throw
     * @param array      $alternatives List of similar defined names
     * @param int        $code         Exception code
     * @param \Exception $previous     Previous exception used for the exception chaining
     */
    public function __construct($message, array $alternatives = array(), $code = 0, \Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);

        $this->alternatives = $alternatives;
    }

    /**
     * @return array A list of similar defined names
     */
    public function getAlternatives()
    {
        return $this->alternatives;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

/**
 * Contains all events dispatched by an Application.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
final class ConsoleEvents
{
    /**
     * The COMMAND event allows you to attach listeners before any command is
     * executed by the console. It also allows you to modify the command, input and output
     * before they are handled to the command.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
     */
    const COMMAND = 'console.command';

    /**
     * The TERMINATE event allows you to attach listeners after a command is
     * executed by the console.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
     */
    const TERMINATE = 'console.terminate';

    /**
     * The EXCEPTION event occurs when an uncaught exception appears
     * while executing Command#run().
     *
     * This event allows you to deal with the exception or
     * to modify the thrown exception.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleExceptionEvent")
     *
     * @deprecated The console.exception event is deprecated since version 3.3 and will be removed in 4.0. Use the console.error event instead.
     */
    const EXCEPTION = 'console.exception';

    /**
     * The ERROR event occurs when an uncaught exception or error appears.
     *
     * This event allows you to deal with the exception/error or
     * to modify the thrown exception.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
     */
    const ERROR = 'console.error';
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class OutputFormatterStyleStack
{
    /**
     * @var OutputFormatterStyleInterface[]
     */
    private $styles;

    private $emptyStyle;

    public function __construct(OutputFormatterStyleInterface $emptyStyle = null)
    {
        $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle();
        $this->reset();
    }

    /**
     * Resets stack (ie. empty internal arrays).
     */
    public function reset()
    {
        $this->styles = array();
    }

    /**
     * Pushes a style in the stack.
     */
    public function push(OutputFormatterStyleInterface $style)
    {
        $this->styles[] = $style;
    }

    /**
     * Pops a style from the stack.
     *
     * @return OutputFormatterStyleInterface
     *
     * @throws InvalidArgumentException When style tags incorrectly nested
     */
    public function pop(OutputFormatterStyleInterface $style = null)
    {
        if (empty($this->styles)) {
            return $this->emptyStyle;
        }

        if (null === $style) {
            return array_pop($this->styles);
        }

        foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
            if ($style->apply('') === $stackedStyle->apply('')) {
                $this->styles = array_slice($this->styles, 0, $index);

                return $stackedStyle;
            }
        }

        throw new InvalidArgumentException('Incorrectly nested style tag found.');
    }

    /**
     * Computes current style with stacks top codes.
     *
     * @return OutputFormatterStyle
     */
    public function getCurrent()
    {
        if (empty($this->styles)) {
            return $this->emptyStyle;
        }

        return $this->styles[count($this->styles) - 1];
    }

    /**
     * @return $this
     */
    public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle)
    {
        $this->emptyStyle = $emptyStyle;

        return $this;
    }

    /**
     * @return OutputFormatterStyleInterface
     */
    public function getEmptyStyle()
    {
        return $this->emptyStyle;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Formatter class for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatter implements OutputFormatterInterface
{
    private $decorated;
    private $styles = array();
    private $styleStack;

    /**
     * Escapes "<" special char in given text.
     *
     * @param string $text Text to escape
     *
     * @return string Escaped text
     */
    public static function escape($text)
    {
        $text = preg_replace('/([^\\\\]?)</', '$1\\<', $text);

        return self::escapeTrailingBackslash($text);
    }

    /**
     * Escapes trailing "\" in given text.
     *
     * @param string $text Text to escape
     *
     * @return string Escaped text
     *
     * @internal
     */
    public static function escapeTrailingBackslash($text)
    {
        if ('\\' === substr($text, -1)) {
            $len = strlen($text);
            $text = rtrim($text, '\\');
            $text = str_replace("\0", '', $text);
            $text .= str_repeat("\0", $len - strlen($text));
        }

        return $text;
    }

    /**
     * Initializes console output formatter.
     *
     * @param bool                            $decorated Whether this formatter should actually decorate strings
     * @param OutputFormatterStyleInterface[] $styles    Array of "name => FormatterStyle" instances
     */
    public function __construct($decorated = false, array $styles = array())
    {
        $this->decorated = (bool) $decorated;

        $this->setStyle('error', new OutputFormatterStyle('white', 'red'));
        $this->setStyle('info', new OutputFormatterStyle('green'));
        $this->setStyle('comment', new OutputFormatterStyle('yellow'));
        $this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));

        foreach ($styles as $name => $style) {
            $this->setStyle($name, $style);
        }

        $this->styleStack = new OutputFormatterStyleStack();
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated($decorated)
    {
        $this->decorated = (bool) $decorated;
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return $this->decorated;
    }

    /**
     * {@inheritdoc}
     */
    public function setStyle($name, OutputFormatterStyleInterface $style)
    {
        $this->styles[strtolower($name)] = $style;
    }

    /**
     * {@inheritdoc}
     */
    public function hasStyle($name)
    {
        return isset($this->styles[strtolower($name)]);
    }

    /**
     * {@inheritdoc}
     */
    public function getStyle($name)
    {
        if (!$this->hasStyle($name)) {
            throw new InvalidArgumentException(sprintf('Undefined style: %s', $name));
        }

        return $this->styles[strtolower($name)];
    }

    /**
     * {@inheritdoc}
     */
    public function format($message)
    {
        $message = (string) $message;
        $offset = 0;
        $output = '';
        $tagRegex = '[a-z][a-z0-9,_=;-]*+';
        preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE);
        foreach ($matches[0] as $i => $match) {
            $pos = $match[1];
            $text = $match[0];

            if (0 != $pos && '\\' == $message[$pos - 1]) {
                continue;
            }

            // add the text up to the next tag
            $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset));
            $offset = $pos + strlen($text);

            // opening tag?
            if ($open = '/' != $text[1]) {
                $tag = $matches[1][$i][0];
            } else {
                $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : '';
            }

            if (!$open && !$tag) {
                // </>
                $this->styleStack->pop();
            } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) {
                $output .= $this->applyCurrentStyle($text);
            } elseif ($open) {
                $this->styleStack->push($style);
            } else {
                $this->styleStack->pop($style);
            }
        }

        $output .= $this->applyCurrentStyle(substr($message, $offset));

        if (false !== strpos($output, "\0")) {
            return strtr($output, array("\0" => '\\', '\\<' => '<'));
        }

        return str_replace('\\<', '<', $output);
    }

    /**
     * @return OutputFormatterStyleStack
     */
    public function getStyleStack()
    {
        return $this->styleStack;
    }

    /**
     * Tries to create new style instance from string.
     *
     * @param string $string
     *
     * @return OutputFormatterStyle|false false if string is not format string
     */
    private function createStyleFromString($string)
    {
        if (isset($this->styles[$string])) {
            return $this->styles[$string];
        }

        if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, PREG_SET_ORDER)) {
            return false;
        }

        $style = new OutputFormatterStyle();
        foreach ($matches as $match) {
            array_shift($match);

            if ('fg' == $match[0]) {
                $style->setForeground($match[1]);
            } elseif ('bg' == $match[0]) {
                $style->setBackground($match[1]);
            } elseif ('options' === $match[0]) {
                preg_match_all('([^,;]+)', $match[1], $options);
                $options = array_shift($options);
                foreach ($options as $option) {
                    try {
                        $style->setOption($option);
                    } catch (\InvalidArgumentException $e) {
                        @trigger_error(sprintf('Unknown style options are deprecated since Symfony 3.2 and will be removed in 4.0. Exception "%s".', $e->getMessage()), E_USER_DEPRECATED);

                        return false;
                    }
                }
            } else {
                return false;
            }
        }

        return $style;
    }

    /**
     * Applies current style from stack to text, if must be applied.
     *
     * @param string $text Input text
     *
     * @return string Styled text
     */
    private function applyCurrentStyle($text)
    {
        return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter style interface for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterStyleInterface
{
    /**
     * Sets style foreground color.
     *
     * @param string $color The color name
     */
    public function setForeground($color = null);

    /**
     * Sets style background color.
     *
     * @param string $color The color name
     */
    public function setBackground($color = null);

    /**
     * Sets some specific style option.
     *
     * @param string $option The option name
     */
    public function setOption($option);

    /**
     * Unsets some specific style option.
     *
     * @param string $option The option name
     */
    public function unsetOption($option);

    /**
     * Sets multiple style options at once.
     */
    public function setOptions(array $options);

    /**
     * Applies the style to a given text.
     *
     * @param string $text The text to style
     *
     * @return string
     */
    public function apply($text);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter interface for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterInterface
{
    /**
     * Sets the decorated flag.
     *
     * @param bool $decorated Whether to decorate the messages or not
     */
    public function setDecorated($decorated);

    /**
     * Gets the decorated flag.
     *
     * @return bool true if the output will decorate messages, false otherwise
     */
    public function isDecorated();

    /**
     * Sets a new style.
     *
     * @param string                        $name  The style name
     * @param OutputFormatterStyleInterface $style The style instance
     */
    public function setStyle($name, OutputFormatterStyleInterface $style);

    /**
     * Checks if output formatter has style with specified name.
     *
     * @param string $name
     *
     * @return bool
     */
    public function hasStyle($name);

    /**
     * Gets style options from style with specified name.
     *
     * @param string $name
     *
     * @return OutputFormatterStyleInterface
     *
     * @throws \InvalidArgumentException When style isn't defined
     */
    public function getStyle($name);

    /**
     * Formats a message according to the given styles.
     *
     * @param string $message The message to style
     *
     * @return string The styled message
     */
    public function format($message);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Formatter style class for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
    private static $availableForegroundColors = array(
        'black' => array('set' => 30, 'unset' => 39),
        'red' => array('set' => 31, 'unset' => 39),
        'green' => array('set' => 32, 'unset' => 39),
        'yellow' => array('set' => 33, 'unset' => 39),
        'blue' => array('set' => 34, 'unset' => 39),
        'magenta' => array('set' => 35, 'unset' => 39),
        'cyan' => array('set' => 36, 'unset' => 39),
        'white' => array('set' => 37, 'unset' => 39),
        'default' => array('set' => 39, 'unset' => 39),
    );
    private static $availableBackgroundColors = array(
        'black' => array('set' => 40, 'unset' => 49),
        'red' => array('set' => 41, 'unset' => 49),
        'green' => array('set' => 42, 'unset' => 49),
        'yellow' => array('set' => 43, 'unset' => 49),
        'blue' => array('set' => 44, 'unset' => 49),
        'magenta' => array('set' => 45, 'unset' => 49),
        'cyan' => array('set' => 46, 'unset' => 49),
        'white' => array('set' => 47, 'unset' => 49),
        'default' => array('set' => 49, 'unset' => 49),
    );
    private static $availableOptions = array(
        'bold' => array('set' => 1, 'unset' => 22),
        'underscore' => array('set' => 4, 'unset' => 24),
        'blink' => array('set' => 5, 'unset' => 25),
        'reverse' => array('set' => 7, 'unset' => 27),
        'conceal' => array('set' => 8, 'unset' => 28),
    );

    private $foreground;
    private $background;
    private $options = array();

    /**
     * Initializes output formatter style.
     *
     * @param string|null $foreground The style foreground color name
     * @param string|null $background The style background color name
     * @param array       $options    The style options
     */
    public function __construct($foreground = null, $background = null, array $options = array())
    {
        if (null !== $foreground) {
            $this->setForeground($foreground);
        }
        if (null !== $background) {
            $this->setBackground($background);
        }
        if (count($options)) {
            $this->setOptions($options);
        }
    }

    /**
     * Sets style foreground color.
     *
     * @param string|null $color The color name
     *
     * @throws InvalidArgumentException When the color name isn't defined
     */
    public function setForeground($color = null)
    {
        if (null === $color) {
            $this->foreground = null;

            return;
        }

        if (!isset(static::$availableForegroundColors[$color])) {
            throw new InvalidArgumentException(sprintf(
                'Invalid foreground color specified: "%s". Expected one of (%s)',
                $color,
                implode(', ', array_keys(static::$availableForegroundColors))
            ));
        }

        $this->foreground = static::$availableForegroundColors[$color];
    }

    /**
     * Sets style background color.
     *
     * @param string|null $color The color name
     *
     * @throws InvalidArgumentException When the color name isn't defined
     */
    public function setBackground($color = null)
    {
        if (null === $color) {
            $this->background = null;

            return;
        }

        if (!isset(static::$availableBackgroundColors[$color])) {
            throw new InvalidArgumentException(sprintf(
                'Invalid background color specified: "%s". Expected one of (%s)',
                $color,
                implode(', ', array_keys(static::$availableBackgroundColors))
            ));
        }

        $this->background = static::$availableBackgroundColors[$color];
    }

    /**
     * Sets some specific style option.
     *
     * @param string $option The option name
     *
     * @throws InvalidArgumentException When the option name isn't defined
     */
    public function setOption($option)
    {
        if (!isset(static::$availableOptions[$option])) {
            throw new InvalidArgumentException(sprintf(
                'Invalid option specified: "%s". Expected one of (%s)',
                $option,
                implode(', ', array_keys(static::$availableOptions))
            ));
        }

        if (!in_array(static::$availableOptions[$option], $this->options)) {
            $this->options[] = static::$availableOptions[$option];
        }
    }

    /**
     * Unsets some specific style option.
     *
     * @param string $option The option name
     *
     * @throws InvalidArgumentException When the option name isn't defined
     */
    public function unsetOption($option)
    {
        if (!isset(static::$availableOptions[$option])) {
            throw new InvalidArgumentException(sprintf(
                'Invalid option specified: "%s". Expected one of (%s)',
                $option,
                implode(', ', array_keys(static::$availableOptions))
            ));
        }

        $pos = array_search(static::$availableOptions[$option], $this->options);
        if (false !== $pos) {
            unset($this->options[$pos]);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function setOptions(array $options)
    {
        $this->options = array();

        foreach ($options as $option) {
            $this->setOption($option);
        }
    }

    /**
     * Applies the style to a given text.
     *
     * @param string $text The text to style
     *
     * @return string
     */
    public function apply($text)
    {
        $setCodes = array();
        $unsetCodes = array();

        if (null !== $this->foreground) {
            $setCodes[] = $this->foreground['set'];
            $unsetCodes[] = $this->foreground['unset'];
        }
        if (null !== $this->background) {
            $setCodes[] = $this->background['set'];
            $unsetCodes[] = $this->background['unset'];
        }
        if (count($this->options)) {
            foreach ($this->options as $option) {
                $setCodes[] = $option['set'];
                $unsetCodes[] = $option['unset'];
            }
        }

        if (0 === count($setCodes)) {
            return $text;
        }

        return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;

/**
 * Decorates output to add console style guide helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
abstract class OutputStyle implements OutputInterface, StyleInterface
{
    private $output;

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;
    }

    /**
     * {@inheritdoc}
     */
    public function newLine($count = 1)
    {
        $this->output->write(str_repeat(PHP_EOL, $count));
    }

    /**
     * @param int $max
     *
     * @return ProgressBar
     */
    public function createProgressBar($max = 0)
    {
        return new ProgressBar($this->output, $max);
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
    {
        $this->output->write($messages, $newline, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, $type = self::OUTPUT_NORMAL)
    {
        $this->output->writeln($messages, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity($level)
    {
        $this->output->setVerbosity($level);
    }

    /**
     * {@inheritdoc}
     */
    public function getVerbosity()
    {
        return $this->output->getVerbosity();
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated($decorated)
    {
        $this->output->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return $this->output->isDecorated();
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        $this->output->setFormatter($formatter);
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        return $this->output->getFormatter();
    }

    /**
     * {@inheritdoc}
     */
    public function isQuiet()
    {
        return $this->output->isQuiet();
    }

    /**
     * {@inheritdoc}
     */
    public function isVerbose()
    {
        return $this->output->isVerbose();
    }

    /**
     * {@inheritdoc}
     */
    public function isVeryVerbose()
    {
        return $this->output->isVeryVerbose();
    }

    /**
     * {@inheritdoc}
     */
    public function isDebug()
    {
        return $this->output->isDebug();
    }

    protected function getErrorOutput()
    {
        if (!$this->output instanceof ConsoleOutputInterface) {
            return $this->output;
        }

        return $this->output->getErrorOutput();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

/**
 * Output style helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
interface StyleInterface
{
    /**
     * Formats a command title.
     *
     * @param string $message
     */
    public function title($message);

    /**
     * Formats a section title.
     *
     * @param string $message
     */
    public function section($message);

    /**
     * Formats a list.
     */
    public function listing(array $elements);

    /**
     * Formats informational text.
     *
     * @param string|array $message
     */
    public function text($message);

    /**
     * Formats a success result bar.
     *
     * @param string|array $message
     */
    public function success($message);

    /**
     * Formats an error result bar.
     *
     * @param string|array $message
     */
    public function error($message);

    /**
     * Formats an warning result bar.
     *
     * @param string|array $message
     */
    public function warning($message);

    /**
     * Formats a note admonition.
     *
     * @param string|array $message
     */
    public function note($message);

    /**
     * Formats a caution admonition.
     *
     * @param string|array $message
     */
    public function caution($message);

    /**
     * Formats a table.
     */
    public function table(array $headers, array $rows);

    /**
     * Asks a question.
     *
     * @param string        $question
     * @param string|null   $default
     * @param callable|null $validator
     *
     * @return mixed
     */
    public function ask($question, $default = null, $validator = null);

    /**
     * Asks a question with the user input hidden.
     *
     * @param string        $question
     * @param callable|null $validator
     *
     * @return mixed
     */
    public function askHidden($question, $validator = null);

    /**
     * Asks for confirmation.
     *
     * @param string $question
     * @param bool   $default
     *
     * @return bool
     */
    public function confirm($question, $default = true);

    /**
     * Asks a choice question.
     *
     * @param string          $question
     * @param array           $choices
     * @param string|int|null $default
     *
     * @return mixed
     */
    public function choice($question, array $choices, $default = null);

    /**
     * Add newline(s).
     *
     * @param int $count The number of newlines
     */
    public function newLine($count = 1);

    /**
     * Starts the progress output.
     *
     * @param int $max Maximum steps (0 if unknown)
     */
    public function progressStart($max = 0);

    /**
     * Advances the progress output X steps.
     *
     * @param int $step Number of steps to advance
     */
    public function progressAdvance($step = 1);

    /**
     * Finishes the progress output.
     */
    public function progressFinish();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;

/**
 * Output decorator helpers for the Symfony Style Guide.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyStyle extends OutputStyle
{
    const MAX_LINE_LENGTH = 120;

    private $input;
    private $questionHelper;
    private $progressBar;
    private $lineLength;
    private $bufferedOutput;

    public function __construct(InputInterface $input, OutputInterface $output)
    {
        $this->input = $input;
        $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
        // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
        $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
        $this->lineLength = min($width - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);

        parent::__construct($output);
    }

    /**
     * Formats a message as a block of text.
     *
     * @param string|array $messages The message to write in the block
     * @param string|null  $type     The block type (added in [] on first line)
     * @param string|null  $style    The style to apply to the whole block
     * @param string       $prefix   The prefix for the block
     * @param bool         $padding  Whether to add vertical padding
     * @param bool         $escape   Whether to escape the message
     */
    public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = true)
    {
        $messages = is_array($messages) ? array_values($messages) : array($messages);

        $this->autoPrependBlock();
        $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape));
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function title($message)
    {
        $this->autoPrependBlock();
        $this->writeln(array(
            sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
            sprintf('<comment>%s</>', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
        ));
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function section($message)
    {
        $this->autoPrependBlock();
        $this->writeln(array(
            sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
            sprintf('<comment>%s</>', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
        ));
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function listing(array $elements)
    {
        $this->autoPrependText();
        $elements = array_map(function ($element) {
            return sprintf(' * %s', $element);
        }, $elements);

        $this->writeln($elements);
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function text($message)
    {
        $this->autoPrependText();

        $messages = is_array($message) ? array_values($message) : array($message);
        foreach ($messages as $message) {
            $this->writeln(sprintf(' %s', $message));
        }
    }

    /**
     * Formats a command comment.
     *
     * @param string|array $message
     */
    public function comment($message)
    {
        $this->block($message, null, null, '<fg=default;bg=default> // </>', false, false);
    }

    /**
     * {@inheritdoc}
     */
    public function success($message)
    {
        $this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function error($message)
    {
        $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function warning($message)
    {
        $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function note($message)
    {
        $this->block($message, 'NOTE', 'fg=yellow', ' ! ');
    }

    /**
     * {@inheritdoc}
     */
    public function caution($message)
    {
        $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function table(array $headers, array $rows)
    {
        $style = clone Table::getStyleDefinition('symfony-style-guide');
        $style->setCellHeaderFormat('<info>%s</info>');

        $table = new Table($this);
        $table->setHeaders($headers);
        $table->setRows($rows);
        $table->setStyle($style);

        $table->render();
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function ask($question, $default = null, $validator = null)
    {
        $question = new Question($question, $default);
        $question->setValidator($validator);

        return $this->askQuestion($question);
    }

    /**
     * {@inheritdoc}
     */
    public function askHidden($question, $validator = null)
    {
        $question = new Question($question);

        $question->setHidden(true);
        $question->setValidator($validator);

        return $this->askQuestion($question);
    }

    /**
     * {@inheritdoc}
     */
    public function confirm($question, $default = true)
    {
        return $this->askQuestion(new ConfirmationQuestion($question, $default));
    }

    /**
     * {@inheritdoc}
     */
    public function choice($question, array $choices, $default = null)
    {
        if (null !== $default) {
            $values = array_flip($choices);
            $default = $values[$default];
        }

        return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
    }

    /**
     * {@inheritdoc}
     */
    public function progressStart($max = 0)
    {
        $this->progressBar = $this->createProgressBar($max);
        $this->progressBar->start();
    }

    /**
     * {@inheritdoc}
     */
    public function progressAdvance($step = 1)
    {
        $this->getProgressBar()->advance($step);
    }

    /**
     * {@inheritdoc}
     */
    public function progressFinish()
    {
        $this->getProgressBar()->finish();
        $this->newLine(2);
        $this->progressBar = null;
    }

    /**
     * {@inheritdoc}
     */
    public function createProgressBar($max = 0)
    {
        $progressBar = parent::createProgressBar($max);

        if ('\\' !== DIRECTORY_SEPARATOR) {
            $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591
            $progressBar->setProgressCharacter('');
            $progressBar->setBarCharacter('▓'); // dark shade character \u2593
        }

        return $progressBar;
    }

    /**
     * @return mixed
     */
    public function askQuestion(Question $question)
    {
        if ($this->input->isInteractive()) {
            $this->autoPrependBlock();
        }

        if (!$this->questionHelper) {
            $this->questionHelper = new SymfonyQuestionHelper();
        }

        $answer = $this->questionHelper->ask($this->input, $this, $question);

        if ($this->input->isInteractive()) {
            $this->newLine();
            $this->bufferedOutput->write("\n");
        }

        return $answer;
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, $type = self::OUTPUT_NORMAL)
    {
        parent::writeln($messages, $type);
        $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type);
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
    {
        parent::write($messages, $newline, $type);
        $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function newLine($count = 1)
    {
        parent::newLine($count);
        $this->bufferedOutput->write(str_repeat("\n", $count));
    }

    /**
     * Returns a new instance which makes use of stderr if available.
     *
     * @return self
     */
    public function getErrorStyle()
    {
        return new self($this->input, $this->getErrorOutput());
    }

    /**
     * @return ProgressBar
     */
    private function getProgressBar()
    {
        if (!$this->progressBar) {
            throw new RuntimeException('The ProgressBar is not started.');
        }

        return $this->progressBar;
    }

    private function autoPrependBlock()
    {
        $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);

        if (!isset($chars[0])) {
            return $this->newLine(); //empty history, so we should start with a new line.
        }
        //Prepend new line for each non LF chars (This means no blank line was output before)
        $this->newLine(2 - substr_count($chars, "\n"));
    }

    private function autoPrependText()
    {
        $fetched = $this->bufferedOutput->fetch();
        //Prepend new line if last char isn't EOL:
        if ("\n" !== substr($fetched, -1)) {
            $this->newLine();
        }
    }

    private function reduceBuffer($messages)
    {
        // We need to know if the two last chars are PHP_EOL
        // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer
        return array_map(function ($value) {
            return substr($value, -4);
        }, array_merge(array($this->bufferedOutput->fetch()), (array) $messages));
    }

    private function createBlock($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = false)
    {
        $indentLength = 0;
        $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix);
        $lines = array();

        if (null !== $type) {
            $type = sprintf('[%s] ', $type);
            $indentLength = strlen($type);
            $lineIndentation = str_repeat(' ', $indentLength);
        }

        // wrap and add newlines for each element
        foreach ($messages as $key => $message) {
            if ($escape) {
                $message = OutputFormatter::escape($message);
            }

            $lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true)));

            if (count($messages) > 1 && $key < count($messages) - 1) {
                $lines[] = '';
            }
        }

        $firstLineIndex = 0;
        if ($padding && $this->isDecorated()) {
            $firstLineIndex = 1;
            array_unshift($lines, '');
            $lines[] = '';
        }

        foreach ($lines as $i => &$line) {
            if (null !== $type) {
                $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
            }

            $line = $prefix.$line;
            $line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line));

            if ($style) {
                $line = sprintf('<%s>%s</>', $style, $line);
            }
        }

        return $lines;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Eases the testing of console commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class CommandTester
{
    private $command;
    private $input;
    private $output;
    private $inputs = array();
    private $statusCode;

    public function __construct(Command $command)
    {
        $this->command = $command;
    }

    /**
     * Executes the command.
     *
     * Available execution options:
     *
     *  * interactive: Sets the input interactive flag
     *  * decorated:   Sets the output decorated flag
     *  * verbosity:   Sets the output verbosity flag
     *
     * @param array $input   An array of command arguments and options
     * @param array $options An array of execution options
     *
     * @return int The command exit code
     */
    public function execute(array $input, array $options = array())
    {
        // set the command name automatically if the application requires
        // this argument and no command name was passed
        if (!isset($input['command'])
            && (null !== $application = $this->command->getApplication())
            && $application->getDefinition()->hasArgument('command')
        ) {
            $input = array_merge(array('command' => $this->command->getName()), $input);
        }

        $this->input = new ArrayInput($input);
        if ($this->inputs) {
            $this->input->setStream(self::createStream($this->inputs));
        }

        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        $this->output = new StreamOutput(fopen('php://memory', 'w', false));
        $this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false);
        if (isset($options['verbosity'])) {
            $this->output->setVerbosity($options['verbosity']);
        }

        return $this->statusCode = $this->command->run($this->input, $this->output);
    }

    /**
     * Gets the display returned by the last execution of the command.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     *
     * @return string The display
     */
    public function getDisplay($normalize = false)
    {
        rewind($this->output->getStream());

        $display = stream_get_contents($this->output->getStream());

        if ($normalize) {
            $display = str_replace(PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the input instance used by the last execution of the command.
     *
     * @return InputInterface The current input instance
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Gets the output instance used by the last execution of the command.
     *
     * @return OutputInterface The current output instance
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * Gets the status code returned by the last execution of the application.
     *
     * @return int The status code
     */
    public function getStatusCode()
    {
        return $this->statusCode;
    }

    /**
     * Sets the user inputs.
     *
     * @param array $inputs An array of strings representing each input
     *                      passed to the command input stream
     *
     * @return CommandTester
     */
    public function setInputs(array $inputs)
    {
        $this->inputs = $inputs;

        return $this;
    }

    private static function createStream(array $inputs)
    {
        $stream = fopen('php://memory', 'r+', false);

        fwrite($stream, implode(PHP_EOL, $inputs));
        rewind($stream);

        return $stream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;

/**
 * Eases the testing of console applications.
 *
 * When testing an application, don't forget to disable the auto exit flag:
 *
 *     $application = new Application();
 *     $application->setAutoExit(false);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ApplicationTester
{
    private $application;
    private $input;
    private $statusCode;
    /**
     * @var OutputInterface
     */
    private $output;
    private $captureStreamsIndependently = false;

    public function __construct(Application $application)
    {
        $this->application = $application;
    }

    /**
     * Executes the application.
     *
     * Available options:
     *
     *  * interactive:               Sets the input interactive flag
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     *
     * @param array $input   An array of arguments and options
     * @param array $options An array of options
     *
     * @return int The command exit code
     */
    public function run(array $input, $options = array())
    {
        $this->input = new ArrayInput($input);
        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        $this->captureStreamsIndependently = array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
        if (!$this->captureStreamsIndependently) {
            $this->output = new StreamOutput(fopen('php://memory', 'w', false));
            if (isset($options['decorated'])) {
                $this->output->setDecorated($options['decorated']);
            }
            if (isset($options['verbosity'])) {
                $this->output->setVerbosity($options['verbosity']);
            }
        } else {
            $this->output = new ConsoleOutput(
                isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL,
                isset($options['decorated']) ? $options['decorated'] : null
            );

            $errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
            $errorOutput->setFormatter($this->output->getFormatter());
            $errorOutput->setVerbosity($this->output->getVerbosity());
            $errorOutput->setDecorated($this->output->isDecorated());

            $reflectedOutput = new \ReflectionObject($this->output);
            $strErrProperty = $reflectedOutput->getProperty('stderr');
            $strErrProperty->setAccessible(true);
            $strErrProperty->setValue($this->output, $errorOutput);

            $reflectedParent = $reflectedOutput->getParentClass();
            $streamProperty = $reflectedParent->getProperty('stream');
            $streamProperty->setAccessible(true);
            $streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
        }

        return $this->statusCode = $this->application->run($this->input, $this->output);
    }

    /**
     * Gets the display returned by the last execution of the application.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     *
     * @return string The display
     */
    public function getDisplay($normalize = false)
    {
        rewind($this->output->getStream());

        $display = stream_get_contents($this->output->getStream());

        if ($normalize) {
            $display = str_replace(PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the output written to STDERR by the application.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     *
     * @return string
     */
    public function getErrorOutput($normalize = false)
    {
        if (!$this->captureStreamsIndependently) {
            throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.');
        }

        rewind($this->output->getErrorOutput()->getStream());

        $display = stream_get_contents($this->output->getErrorOutput()->getStream());

        if ($normalize) {
            $display = str_replace(PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the input instance used by the last execution of the application.
     *
     * @return InputInterface The current input instance
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Gets the output instance used by the last execution of the application.
     *
     * @return OutputInterface The current output instance
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * Gets the status code returned by the last execution of the application.
     *
     * @return int The status code
     */
    public function getStatusCode()
    {
        return $this->statusCode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\EventListener;

use Psr\Log\LoggerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * @author James Halsall <james.t.halsall@googlemail.com>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class ErrorListener implements EventSubscriberInterface
{
    private $logger;

    public function __construct(LoggerInterface $logger = null)
    {
        $this->logger = $logger;
    }

    public function onConsoleError(ConsoleErrorEvent $event)
    {
        if (null === $this->logger) {
            return;
        }

        $error = $event->getError();

        if (!$inputString = $this->getInputString($event)) {
            return $this->logger->error('An error occurred while using the console. Message: "{message}"', array('error' => $error, 'message' => $error->getMessage()));
        }

        $this->logger->error('Error thrown while running command "{command}". Message: "{message}"', array('error' => $error, 'command' => $inputString, 'message' => $error->getMessage()));
    }

    public function onConsoleTerminate(ConsoleTerminateEvent $event)
    {
        if (null === $this->logger) {
            return;
        }

        $exitCode = $event->getExitCode();

        if (0 === $exitCode) {
            return;
        }

        if (!$inputString = $this->getInputString($event)) {
            return $this->logger->debug('The console exited with code "{code}"', array('code' => $exitCode));
        }

        $this->logger->debug('Command "{command}" exited with code "{code}"', array('command' => $inputString, 'code' => $exitCode));
    }

    public static function getSubscribedEvents()
    {
        return array(
            ConsoleEvents::ERROR => array('onConsoleError', -128),
            ConsoleEvents::TERMINATE => array('onConsoleTerminate', -128),
        );
    }

    private static function getInputString(ConsoleEvent $event)
    {
        $commandName = $event->getCommand() ? $event->getCommand()->getName() : null;
        $input = $event->getInput();

        if (method_exists($input, '__toString')) {
            if ($commandName) {
                return str_replace(array("'$commandName'", "\"$commandName\""), $commandName, (string) $input);
            }

            return (string) $input;
        }

        return $commandName;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * An Application is the container for a collection of commands.
 *
 * It is the main entry point of a Console application.
 *
 * This class is optimized for a standard CLI environment.
 *
 * Usage:
 *
 *     $app = new Application('myapp', '1.0 (stable)');
 *     $app->add(new SimpleCommand());
 *     $app->run();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Application
{
    private $commands = array();
    private $wantHelps = false;
    private $runningCommand;
    private $name;
    private $version;
    private $commandLoader;
    private $catchExceptions = true;
    private $autoExit = true;
    private $definition;
    private $helperSet;
    private $dispatcher;
    private $terminal;
    private $defaultCommand;
    private $singleCommand;
    private $initialized;

    /**
     * @param string $name    The name of the application
     * @param string $version The version of the application
     */
    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
    {
        $this->name = $name;
        $this->version = $version;
        $this->terminal = new Terminal();
        $this->defaultCommand = 'list';
    }

    public function setDispatcher(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    public function setCommandLoader(CommandLoaderInterface $commandLoader)
    {
        $this->commandLoader = $commandLoader;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     *
     * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
     */
    public function run(InputInterface $input = null, OutputInterface $output = null)
    {
        putenv('LINES='.$this->terminal->getHeight());
        putenv('COLUMNS='.$this->terminal->getWidth());

        if (null === $input) {
            $input = new ArgvInput();
        }

        if (null === $output) {
            $output = new ConsoleOutput();
        }

        $renderException = function ($e) use ($output) {
            if (!$e instanceof \Exception) {
                $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
            }
            if ($output instanceof ConsoleOutputInterface) {
                $this->renderException($e, $output->getErrorOutput());
            } else {
                $this->renderException($e, $output);
            }
        };
        if ($phpHandler = set_exception_handler($renderException)) {
            restore_exception_handler();
            if (!is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
                $debugHandler = true;
            } elseif ($debugHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
                $phpHandler[0]->setExceptionHandler($debugHandler);
            }
        }

        if (null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
            @trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), E_USER_DEPRECATED);
        }

        $this->configureIO($input, $output);

        try {
            $exitCode = $this->doRun($input, $output);
        } catch (\Exception $e) {
            if (!$this->catchExceptions) {
                throw $e;
            }

            $renderException($e);

            $exitCode = $e->getCode();
            if (is_numeric($exitCode)) {
                $exitCode = (int) $exitCode;
                if (0 === $exitCode) {
                    $exitCode = 1;
                }
            } else {
                $exitCode = 1;
            }
        } finally {
            // if the exception handler changed, keep it
            // otherwise, unregister $renderException
            if (!$phpHandler) {
                if (set_exception_handler($renderException) === $renderException) {
                    restore_exception_handler();
                }
                restore_exception_handler();
            } elseif (!$debugHandler) {
                $finalHandler = $phpHandler[0]->setExceptionHandler(null);
                if ($finalHandler !== $renderException) {
                    $phpHandler[0]->setExceptionHandler($finalHandler);
                }
            }
        }

        if ($this->autoExit) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }

            exit($exitCode);
        }

        return $exitCode;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(array('--version', '-V'), true)) {
            $output->writeln($this->getLongVersion());

            return 0;
        }

        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(array('--help', '-h'), true)) {
            if (!$name) {
                $name = 'help';
                $input = new ArrayInput(array('command_name' => $this->defaultCommand));
            } else {
                $this->wantHelps = true;
            }
        }

        if (!$name) {
            $name = $this->defaultCommand;
            $definition = $this->getDefinition();
            $definition->setArguments(array_merge(
                $definition->getArguments(),
                array(
                    'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
                )
            ));
        }

        try {
            $e = $this->runningCommand = null;
            // the command name MUST be the first element of the input
            $command = $this->find($name);
        } catch (\Exception $e) {
        } catch (\Throwable $e) {
        }
        if (null !== $e) {
            if (null !== $this->dispatcher) {
                $event = new ConsoleErrorEvent($input, $output, $e);
                $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
                $e = $event->getError();

                if (0 === $event->getExitCode()) {
                    return 0;
                }
            }

            throw $e;
        }

        $this->runningCommand = $command;
        $exitCode = $this->doRunCommand($command, $input, $output);
        $this->runningCommand = null;

        return $exitCode;
    }

    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Get the helper set associated with the command.
     *
     * @return HelperSet The HelperSet instance associated with this command
     */
    public function getHelperSet()
    {
        if (!$this->helperSet) {
            $this->helperSet = $this->getDefaultHelperSet();
        }

        return $this->helperSet;
    }

    public function setDefinition(InputDefinition $definition)
    {
        $this->definition = $definition;
    }

    /**
     * Gets the InputDefinition related to this Application.
     *
     * @return InputDefinition The InputDefinition instance
     */
    public function getDefinition()
    {
        if (!$this->definition) {
            $this->definition = $this->getDefaultInputDefinition();
        }

        if ($this->singleCommand) {
            $inputDefinition = $this->definition;
            $inputDefinition->setArguments();

            return $inputDefinition;
        }

        return $this->definition;
    }

    /**
     * Gets the help message.
     *
     * @return string A help message
     */
    public function getHelp()
    {
        return $this->getLongVersion();
    }

    /**
     * Gets whether to catch exceptions or not during commands execution.
     *
     * @return bool Whether to catch exceptions or not during commands execution
     */
    public function areExceptionsCaught()
    {
        return $this->catchExceptions;
    }

    /**
     * Sets whether to catch exceptions or not during commands execution.
     *
     * @param bool $boolean Whether to catch exceptions or not during commands execution
     */
    public function setCatchExceptions($boolean)
    {
        $this->catchExceptions = (bool) $boolean;
    }

    /**
     * Gets whether to automatically exit after a command execution or not.
     *
     * @return bool Whether to automatically exit after a command execution or not
     */
    public function isAutoExitEnabled()
    {
        return $this->autoExit;
    }

    /**
     * Sets whether to automatically exit after a command execution or not.
     *
     * @param bool $boolean Whether to automatically exit after a command execution or not
     */
    public function setAutoExit($boolean)
    {
        $this->autoExit = (bool) $boolean;
    }

    /**
     * Gets the name of the application.
     *
     * @return string The application name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Sets the application name.
     *
     * @param string $name The application name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Gets the application version.
     *
     * @return string The application version
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Sets the application version.
     *
     * @param string $version The application version
     */
    public function setVersion($version)
    {
        $this->version = $version;
    }

    /**
     * Returns the long version of the application.
     *
     * @return string The long application version
     */
    public function getLongVersion()
    {
        if ('UNKNOWN' !== $this->getName()) {
            if ('UNKNOWN' !== $this->getVersion()) {
                return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
            }

            return $this->getName();
        }

        return 'Console Tool';
    }

    /**
     * Registers a new command.
     *
     * @param string $name The command name
     *
     * @return Command The newly created command
     */
    public function register($name)
    {
        return $this->add(new Command($name));
    }

    /**
     * Adds an array of command objects.
     *
     * If a Command is not enabled it will not be added.
     *
     * @param Command[] $commands An array of commands
     */
    public function addCommands(array $commands)
    {
        foreach ($commands as $command) {
            $this->add($command);
        }
    }

    /**
     * Adds a command object.
     *
     * If a command with the same name already exists, it will be overridden.
     * If the command is not enabled it will not be added.
     *
     * @return Command|null The registered command if enabled or null
     */
    public function add(Command $command)
    {
        $this->init();

        $command->setApplication($this);

        if (!$command->isEnabled()) {
            $command->setApplication(null);

            return;
        }

        if (null === $command->getDefinition()) {
            throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
        }

        if (!$command->getName()) {
            throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($command)));
        }

        $this->commands[$command->getName()] = $command;

        foreach ($command->getAliases() as $alias) {
            $this->commands[$alias] = $command;
        }

        return $command;
    }

    /**
     * Returns a registered command by name or alias.
     *
     * @param string $name The command name or alias
     *
     * @return Command A Command object
     *
     * @throws CommandNotFoundException When given command name does not exist
     */
    public function get($name)
    {
        $this->init();

        if (!$this->has($name)) {
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
        }

        $command = $this->commands[$name];

        if ($this->wantHelps) {
            $this->wantHelps = false;

            $helpCommand = $this->get('help');
            $helpCommand->setCommand($command);

            return $helpCommand;
        }

        return $command;
    }

    /**
     * Returns true if the command exists, false otherwise.
     *
     * @param string $name The command name or alias
     *
     * @return bool true if the command exists, false otherwise
     */
    public function has($name)
    {
        $this->init();

        return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
    }

    /**
     * Returns an array of all unique namespaces used by currently registered commands.
     *
     * It does not return the global namespace which always exists.
     *
     * @return string[] An array of namespaces
     */
    public function getNamespaces()
    {
        $namespaces = array();
        foreach ($this->all() as $command) {
            $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));

            foreach ($command->getAliases() as $alias) {
                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
            }
        }

        return array_values(array_unique(array_filter($namespaces)));
    }

    /**
     * Finds a registered namespace by a name or an abbreviation.
     *
     * @param string $namespace A namespace or abbreviation to search for
     *
     * @return string A registered namespace
     *
     * @throws CommandNotFoundException When namespace is incorrect or ambiguous
     */
    public function findNamespace($namespace)
    {
        $allNamespaces = $this->getNamespaces();
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
        $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);

        if (empty($namespaces)) {
            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);

            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
                if (1 == count($alternatives)) {
                    $message .= "\n\nDid you mean this?\n    ";
                } else {
                    $message .= "\n\nDid you mean one of these?\n    ";
                }

                $message .= implode("\n    ", $alternatives);
            }

            throw new CommandNotFoundException($message, $alternatives);
        }

        $exact = in_array($namespace, $namespaces, true);
        if (count($namespaces) > 1 && !$exact) {
            throw new CommandNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
        }

        return $exact ? $namespace : reset($namespaces);
    }

    /**
     * Finds a command by name or alias.
     *
     * Contrary to get, this command tries to find the best
     * match if you give it an abbreviation of a name or alias.
     *
     * @param string $name A command name or a command alias
     *
     * @return Command A Command instance
     *
     * @throws CommandNotFoundException When command name is incorrect or ambiguous
     */
    public function find($name)
    {
        $this->init();

        $aliases = array();
        $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
        $commands = preg_grep('{^'.$expr.'}', $allCommands);

        if (empty($commands)) {
            $commands = preg_grep('{^'.$expr.'}i', $allCommands);
        }

        // if no commands matched or we just matched namespaces
        if (empty($commands) || count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
            if (false !== $pos = strrpos($name, ':')) {
                // check if a namespace exists and contains commands
                $this->findNamespace(substr($name, 0, $pos));
            }

            $message = sprintf('Command "%s" is not defined.', $name);

            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
                if (1 == count($alternatives)) {
                    $message .= "\n\nDid you mean this?\n    ";
                } else {
                    $message .= "\n\nDid you mean one of these?\n    ";
                }
                $message .= implode("\n    ", $alternatives);
            }

            throw new CommandNotFoundException($message, $alternatives);
        }

        // filter out aliases for commands which are already on the list
        if (count($commands) > 1) {
            $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
            $commands = array_unique(array_filter($commands, function ($nameOrAlias) use ($commandList, $commands, &$aliases) {
                $commandName = $commandList[$nameOrAlias] instanceof Command ? $commandList[$nameOrAlias]->getName() : $nameOrAlias;
                $aliases[$nameOrAlias] = $commandName;

                return $commandName === $nameOrAlias || !in_array($commandName, $commands);
            }));
        }

        $exact = in_array($name, $commands, true) || isset($aliases[$name]);
        if (count($commands) > 1 && !$exact) {
            $usableWidth = $this->terminal->getWidth() - 10;
            $abbrevs = array_values($commands);
            $maxLen = 0;
            foreach ($abbrevs as $abbrev) {
                $maxLen = max(Helper::strlen($abbrev), $maxLen);
            }
            $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) {
                if (!$commandList[$cmd] instanceof Command) {
                    return $cmd;
                }
                $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();

                return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
            }, array_values($commands));
            $suggestions = $this->getAbbreviationSuggestions($abbrevs);

            throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
        }

        return $this->get($exact ? $name : reset($commands));
    }

    /**
     * Gets the commands (registered in the given namespace if provided).
     *
     * The array keys are the full names and the values the command instances.
     *
     * @param string $namespace A namespace name
     *
     * @return Command[] An array of Command instances
     */
    public function all($namespace = null)
    {
        $this->init();

        if (null === $namespace) {
            if (!$this->commandLoader) {
                return $this->commands;
            }

            $commands = $this->commands;
            foreach ($this->commandLoader->getNames() as $name) {
                if (!isset($commands[$name]) && $this->has($name)) {
                    $commands[$name] = $this->get($name);
                }
            }

            return $commands;
        }

        $commands = array();
        foreach ($this->commands as $name => $command) {
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
                $commands[$name] = $command;
            }
        }

        if ($this->commandLoader) {
            foreach ($this->commandLoader->getNames() as $name) {
                if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
                    $commands[$name] = $this->get($name);
                }
            }
        }

        return $commands;
    }

    /**
     * Returns an array of possible abbreviations given a set of names.
     *
     * @param array $names An array of names
     *
     * @return array An array of abbreviations
     */
    public static function getAbbreviations($names)
    {
        $abbrevs = array();
        foreach ($names as $name) {
            for ($len = strlen($name); $len > 0; --$len) {
                $abbrev = substr($name, 0, $len);
                $abbrevs[$abbrev][] = $name;
            }
        }

        return $abbrevs;
    }

    /**
     * Renders a caught exception.
     */
    public function renderException(\Exception $e, OutputInterface $output)
    {
        $output->writeln('', OutputInterface::VERBOSITY_QUIET);

        $this->doRenderException($e, $output);

        if (null !== $this->runningCommand) {
            $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
            $output->writeln('', OutputInterface::VERBOSITY_QUIET);
        }
    }

    protected function doRenderException(\Exception $e, OutputInterface $output)
    {
        do {
            $message = trim($e->getMessage());
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $title = sprintf('  [%s%s]  ', get_class($e), 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
                $len = Helper::strlen($title);
            } else {
                $len = 0;
            }

            $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
            // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327
            if (defined('HHVM_VERSION') && $width > 1 << 31) {
                $width = 1 << 31;
            }
            $lines = array();
            foreach ('' !== $message ? preg_split('/\r?\n/', $message) : array() as $line) {
                foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
                    // pre-format lines to get the right string length
                    $lineLength = Helper::strlen($line) + 4;
                    $lines[] = array($line, $lineLength);

                    $len = max($lineLength, $len);
                }
            }

            $messages = array();
            if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
            }
            $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title))));
            }
            foreach ($lines as $line) {
                $messages[] = sprintf('<error>  %s  %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
            }
            $messages[] = $emptyLine;
            $messages[] = '';

            $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);

            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);

                // exception related properties
                $trace = $e->getTrace();

                for ($i = 0, $count = count($trace); $i < $count; ++$i) {
                    $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
                    $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
                    $function = $trace[$i]['function'];
                    $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
                    $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';

                    $output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET);
                }

                $output->writeln('', OutputInterface::VERBOSITY_QUIET);
            }
        } while ($e = $e->getPrevious());
    }

    /**
     * Tries to figure out the terminal width in which this application runs.
     *
     * @return int|null
     *
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
     */
    protected function getTerminalWidth()
    {
        @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);

        return $this->terminal->getWidth();
    }

    /**
     * Tries to figure out the terminal height in which this application runs.
     *
     * @return int|null
     *
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
     */
    protected function getTerminalHeight()
    {
        @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);

        return $this->terminal->getHeight();
    }

    /**
     * Tries to figure out the terminal dimensions based on the current environment.
     *
     * @return array Array containing width and height
     *
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
     */
    public function getTerminalDimensions()
    {
        @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);

        return array($this->terminal->getWidth(), $this->terminal->getHeight());
    }

    /**
     * Sets terminal dimensions.
     *
     * Can be useful to force terminal dimensions for functional tests.
     *
     * @param int $width  The width
     * @param int $height The height
     *
     * @return $this
     *
     * @deprecated since version 3.2, to be removed in 4.0. Set the COLUMNS and LINES env vars instead.
     */
    public function setTerminalDimensions($width, $height)
    {
        @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.', __METHOD__), E_USER_DEPRECATED);

        putenv('COLUMNS='.$width);
        putenv('LINES='.$height);

        return $this;
    }

    /**
     * Configures the input and output instances based on the user arguments and options.
     */
    protected function configureIO(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(array('--ansi'), true)) {
            $output->setDecorated(true);
        } elseif (true === $input->hasParameterOption(array('--no-ansi'), true)) {
            $output->setDecorated(false);
        }

        if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) {
            $input->setInteractive(false);
        } elseif (function_exists('posix_isatty')) {
            $inputStream = null;

            if ($input instanceof StreamableInputInterface) {
                $inputStream = $input->getStream();
            }

            // This check ensures that calling QuestionHelper::setInputStream() works
            // To be removed in 4.0 (in the same time as QuestionHelper::setInputStream)
            if (!$inputStream && $this->getHelperSet()->has('question')) {
                $inputStream = $this->getHelperSet()->get('question')->getInputStream(false);
            }

            if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
                $input->setInteractive(false);
            }
        }

        switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
            case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
            case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
            case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
            case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
            default: $shellVerbosity = 0; break;
        }

        if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
            $shellVerbosity = -1;
        } else {
            if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
                $shellVerbosity = 3;
            } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
                $shellVerbosity = 2;
            } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
                $shellVerbosity = 1;
            }
        }

        if (-1 === $shellVerbosity) {
            $input->setInteractive(false);
        }

        putenv('SHELL_VERBOSITY='.$shellVerbosity);
        $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
        $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
    }

    /**
     * Runs the current command.
     *
     * If an event dispatcher has been attached to the application,
     * events are also dispatched during the life-cycle of the command.
     *
     * @return int 0 if everything went fine, or an error code
     */
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
    {
        foreach ($command->getHelperSet() as $helper) {
            if ($helper instanceof InputAwareInterface) {
                $helper->setInput($input);
            }
        }

        if (null === $this->dispatcher) {
            return $command->run($input, $output);
        }

        // bind before the console.command event, so the listeners have access to input options/arguments
        try {
            $command->mergeApplicationDefinition();
            $input->bind($command->getDefinition());
        } catch (ExceptionInterface $e) {
            // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
        }

        $event = new ConsoleCommandEvent($command, $input, $output);
        $e = null;

        try {
            $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);

            if ($event->commandShouldRun()) {
                $exitCode = $command->run($input, $output);
            } else {
                $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
            }
        } catch (\Exception $e) {
        } catch (\Throwable $e) {
        }
        if (null !== $e) {
            if ($this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
                $x = $e instanceof \Exception ? $e : new FatalThrowableError($e);
                $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode());
                $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);

                if ($x !== $event->getException()) {
                    $e = $event->getException();
                }
            }
            $event = new ConsoleErrorEvent($input, $output, $e, $command);
            $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
            $e = $event->getError();

            if (0 === $exitCode = $event->getExitCode()) {
                $e = null;
            }
        }

        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
        $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);

        if (null !== $e) {
            throw $e;
        }

        return $event->getExitCode();
    }

    /**
     * Gets the name of the command based on input.
     *
     * @return string The command name
     */
    protected function getCommandName(InputInterface $input)
    {
        return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
    }

    /**
     * Gets the default input definition.
     *
     * @return InputDefinition An InputDefinition instance
     */
    protected function getDefaultInputDefinition()
    {
        return new InputDefinition(array(
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),

            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
            new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
            new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
            new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
        ));
    }

    /**
     * Gets the default commands that should always be available.
     *
     * @return Command[] An array of default Command instances
     */
    protected function getDefaultCommands()
    {
        return array(new HelpCommand(), new ListCommand());
    }

    /**
     * Gets the default helper set with the helpers that should always be available.
     *
     * @return HelperSet A HelperSet instance
     */
    protected function getDefaultHelperSet()
    {
        return new HelperSet(array(
            new FormatterHelper(),
            new DebugFormatterHelper(),
            new ProcessHelper(),
            new QuestionHelper(),
        ));
    }

    /**
     * Returns abbreviated suggestions in string format.
     *
     * @param array $abbrevs Abbreviated suggestions to convert
     *
     * @return string A formatted string of abbreviated suggestions
     */
    private function getAbbreviationSuggestions($abbrevs)
    {
        return '    '.implode("\n    ", $abbrevs);
    }

    /**
     * Returns the namespace part of the command name.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @param string $name  The full name of the command
     * @param string $limit The maximum number of parts of the namespace
     *
     * @return string The namespace of the command
     */
    public function extractNamespace($name, $limit = null)
    {
        $parts = explode(':', $name);
        array_pop($parts);

        return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
    }

    /**
     * Finds alternative of $name among $collection,
     * if nothing is found in $collection, try in $abbrevs.
     *
     * @param string   $name       The string
     * @param iterable $collection The collection
     *
     * @return string[] A sorted array of similar string
     */
    private function findAlternatives($name, $collection)
    {
        $threshold = 1e3;
        $alternatives = array();

        $collectionParts = array();
        foreach ($collection as $item) {
            $collectionParts[$item] = explode(':', $item);
        }

        foreach (explode(':', $name) as $i => $subname) {
            foreach ($collectionParts as $collectionName => $parts) {
                $exists = isset($alternatives[$collectionName]);
                if (!isset($parts[$i]) && $exists) {
                    $alternatives[$collectionName] += $threshold;
                    continue;
                } elseif (!isset($parts[$i])) {
                    continue;
                }

                $lev = levenshtein($subname, $parts[$i]);
                if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
                } elseif ($exists) {
                    $alternatives[$collectionName] += $threshold;
                }
            }
        }

        foreach ($collection as $item) {
            $lev = levenshtein($name, $item);
            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
            }
        }

        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
        ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);

        return array_keys($alternatives);
    }

    /**
     * Sets the default Command name.
     *
     * @param string $commandName     The Command name
     * @param bool   $isSingleCommand Set to true if there is only one command in this application
     *
     * @return self
     */
    public function setDefaultCommand($commandName, $isSingleCommand = false)
    {
        $this->defaultCommand = $commandName;

        if ($isSingleCommand) {
            // Ensure the command exist
            $this->find($commandName);

            $this->singleCommand = true;
        }

        return $this;
    }

    private function splitStringByWidth($string, $width)
    {
        // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
        // additionally, array_slice() is not enough as some character has doubled width.
        // we need a function to split string not by character count but by string width
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return str_split($string, $width);
        }

        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
        $lines = array();
        $line = '';
        foreach (preg_split('//u', $utf8String) as $char) {
            // test if $char could be appended to current line
            if (mb_strwidth($line.$char, 'utf8') <= $width) {
                $line .= $char;
                continue;
            }
            // if not, push current line to array and make new line
            $lines[] = str_pad($line, $width);
            $line = $char;
        }

        $lines[] = count($lines) ? str_pad($line, $width) : $line;

        mb_convert_variables($encoding, 'utf8', $lines);

        return $lines;
    }

    /**
     * Returns all namespaces of the command name.
     *
     * @param string $name The full name of the command
     *
     * @return string[] The namespaces of the command
     */
    private function extractAllNamespaces($name)
    {
        // -1 as third argument is needed to skip the command short name when exploding
        $parts = explode(':', $name, -1);
        $namespaces = array();

        foreach ($parts as $part) {
            if (count($namespaces)) {
                $namespaces[] = end($namespaces).':'.$part;
            } else {
                $namespaces[] = $part;
            }
        }

        return $namespaces;
    }

    private function init()
    {
        if ($this->initialized) {
            return;
        }
        $this->initialized = true;

        foreach ($this->getDefaultCommands() as $command) {
            $this->add($command);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * Descriptor interface.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface DescriptorInterface
{
    /**
     * Describes an InputArgument instance.
     *
     * @param OutputInterface $output
     * @param object          $object
     * @param array           $options
     */
    public function describe(OutputInterface $output, $object, array $options = array());
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Markdown descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class MarkdownDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    public function describe(OutputInterface $output, $object, array $options = array())
    {
        $decorated = $output->isDecorated();
        $output->setDecorated(false);

        parent::describe($output, $object, $options);

        $output->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    protected function write($content, $decorated = true)
    {
        parent::write($content, $decorated);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        $this->write(
            '#### `'.($argument->getName() ?: '<none>')."`\n\n"
            .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
            .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
            .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        $name = '--'.$option->getName();
        if ($option->getShortcut()) {
            $name .= '|-'.implode('|-', explode('|', $option->getShortcut())).'';
        }

        $this->write(
            '#### `'.$name.'`'."\n\n"
            .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '')
            .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
            .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
            .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        if ($showArguments = count($definition->getArguments()) > 0) {
            $this->write('### Arguments');
            foreach ($definition->getArguments() as $argument) {
                $this->write("\n\n");
                $this->write($this->describeInputArgument($argument));
            }
        }

        if (count($definition->getOptions()) > 0) {
            if ($showArguments) {
                $this->write("\n\n");
            }

            $this->write('### Options');
            foreach ($definition->getOptions() as $option) {
                $this->write("\n\n");
                $this->write($this->describeInputOption($option));
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        $this->write(
            '`'.$command->getName()."`\n"
            .str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n"
            .($command->getDescription() ? $command->getDescription()."\n\n" : '')
            .'### Usage'."\n\n"
            .array_reduce(array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()), function ($carry, $usage) {
                return $carry.'* `'.$usage.'`'."\n";
            })
        );

        if ($help = $command->getProcessedHelp()) {
            $this->write("\n");
            $this->write($help);
        }

        if ($command->getNativeDefinition()) {
            $this->write("\n\n");
            $this->describeInputDefinition($command->getNativeDefinition());
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);
        $title = $this->getApplicationTitle($application);

        $this->write($title."\n".str_repeat('=', Helper::strlen($title)));

        foreach ($description->getNamespaces() as $namespace) {
            if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                $this->write("\n\n");
                $this->write('**'.$namespace['id'].':**');
            }

            $this->write("\n\n");
            $this->write(implode("\n", array_map(function ($commandName) use ($description) {
                return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName()));
            }, $namespace['commands'])));
        }

        foreach ($description->getCommands() as $command) {
            $this->write("\n\n");
            $this->write($this->describeCommand($command));
        }
    }

    private function getApplicationTitle(Application $application)
    {
        if ('UNKNOWN' !== $application->getName()) {
            if ('UNKNOWN' !== $application->getVersion()) {
                return sprintf('%s %s', $application->getName(), $application->getVersion());
            }

            return $application->getName();
        }

        return 'Console Tool';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
abstract class Descriptor implements DescriptorInterface
{
    /**
     * @var OutputInterface
     */
    protected $output;

    /**
     * {@inheritdoc}
     */
    public function describe(OutputInterface $output, $object, array $options = array())
    {
        $this->output = $output;

        switch (true) {
            case $object instanceof InputArgument:
                $this->describeInputArgument($object, $options);
                break;
            case $object instanceof InputOption:
                $this->describeInputOption($object, $options);
                break;
            case $object instanceof InputDefinition:
                $this->describeInputDefinition($object, $options);
                break;
            case $object instanceof Command:
                $this->describeCommand($object, $options);
                break;
            case $object instanceof Application:
                $this->describeApplication($object, $options);
                break;
            default:
                throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
        }
    }

    /**
     * Writes content to output.
     *
     * @param string $content
     * @param bool   $decorated
     */
    protected function write($content, $decorated = false)
    {
        $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
    }

    /**
     * Describes an InputArgument instance.
     *
     * @return string|mixed
     */
    abstract protected function describeInputArgument(InputArgument $argument, array $options = array());

    /**
     * Describes an InputOption instance.
     *
     * @return string|mixed
     */
    abstract protected function describeInputOption(InputOption $option, array $options = array());

    /**
     * Describes an InputDefinition instance.
     *
     * @return string|mixed
     */
    abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array());

    /**
     * Describes a Command instance.
     *
     * @return string|mixed
     */
    abstract protected function describeCommand(Command $command, array $options = array());

    /**
     * Describes an Application instance.
     *
     * @return string|mixed
     */
    abstract protected function describeApplication(Application $application, array $options = array());
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
class ApplicationDescription
{
    const GLOBAL_NAMESPACE = '_global';

    private $application;
    private $namespace;
    private $showHidden;

    /**
     * @var array
     */
    private $namespaces;

    /**
     * @var Command[]
     */
    private $commands;

    /**
     * @var Command[]
     */
    private $aliases;

    /**
     * @param Application $application
     * @param string|null $namespace
     * @param bool        $showHidden
     */
    public function __construct(Application $application, $namespace = null, $showHidden = false)
    {
        $this->application = $application;
        $this->namespace = $namespace;
        $this->showHidden = $showHidden;
    }

    /**
     * @return array
     */
    public function getNamespaces()
    {
        if (null === $this->namespaces) {
            $this->inspectApplication();
        }

        return $this->namespaces;
    }

    /**
     * @return Command[]
     */
    public function getCommands()
    {
        if (null === $this->commands) {
            $this->inspectApplication();
        }

        return $this->commands;
    }

    /**
     * @param string $name
     *
     * @return Command
     *
     * @throws CommandNotFoundException
     */
    public function getCommand($name)
    {
        if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
            throw new CommandNotFoundException(sprintf('Command %s does not exist.', $name));
        }

        return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
    }

    private function inspectApplication()
    {
        $this->commands = array();
        $this->namespaces = array();

        $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
        foreach ($this->sortCommands($all) as $namespace => $commands) {
            $names = array();

            /** @var Command $command */
            foreach ($commands as $name => $command) {
                if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
                    continue;
                }

                if ($command->getName() === $name) {
                    $this->commands[$name] = $command;
                } else {
                    $this->aliases[$name] = $command;
                }

                $names[] = $name;
            }

            $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names);
        }
    }

    /**
     * @return array
     */
    private function sortCommands(array $commands)
    {
        $namespacedCommands = array();
        $globalCommands = array();
        foreach ($commands as $name => $command) {
            $key = $this->application->extractNamespace($name, 1);
            if (!$key) {
                $globalCommands['_global'][$name] = $command;
            } else {
                $namespacedCommands[$key][$name] = $command;
            }
        }
        ksort($namespacedCommands);
        $namespacedCommands = array_merge($globalCommands, $namespacedCommands);

        foreach ($namespacedCommands as &$commandsSet) {
            ksort($commandsSet);
        }
        // unset reference to keep scope clear
        unset($commandsSet);

        return $namespacedCommands;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * JSON descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class JsonDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        $this->writeData($this->getInputArgumentData($argument), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        $this->writeData($this->getInputOptionData($option), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $this->writeData($this->getInputDefinitionData($definition), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $this->writeData($this->getCommandData($command), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace, true);
        $commands = array();

        foreach ($description->getCommands() as $command) {
            $commands[] = $this->getCommandData($command);
        }

        $data = array();
        if ('UNKNOWN' !== $application->getName()) {
            $data['application']['name'] = $application->getName();
            if ('UNKNOWN' !== $application->getVersion()) {
                $data['application']['version'] = $application->getVersion();
            }
        }

        $data['commands'] = $commands;

        if ($describedNamespace) {
            $data['namespace'] = $describedNamespace;
        } else {
            $data['namespaces'] = array_values($description->getNamespaces());
        }

        $this->writeData($data, $options);
    }

    /**
     * Writes data as json.
     *
     * @return array|string
     */
    private function writeData(array $data, array $options)
    {
        $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0));
    }

    /**
     * @return array
     */
    private function getInputArgumentData(InputArgument $argument)
    {
        return array(
            'name' => $argument->getName(),
            'is_required' => $argument->isRequired(),
            'is_array' => $argument->isArray(),
            'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
            'default' => INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
        );
    }

    /**
     * @return array
     */
    private function getInputOptionData(InputOption $option)
    {
        return array(
            'name' => '--'.$option->getName(),
            'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '',
            'accept_value' => $option->acceptValue(),
            'is_value_required' => $option->isValueRequired(),
            'is_multiple' => $option->isArray(),
            'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
            'default' => INF === $option->getDefault() ? 'INF' : $option->getDefault(),
        );
    }

    /**
     * @return array
     */
    private function getInputDefinitionData(InputDefinition $definition)
    {
        $inputArguments = array();
        foreach ($definition->getArguments() as $name => $argument) {
            $inputArguments[$name] = $this->getInputArgumentData($argument);
        }

        $inputOptions = array();
        foreach ($definition->getOptions() as $name => $option) {
            $inputOptions[$name] = $this->getInputOptionData($option);
        }

        return array('arguments' => $inputArguments, 'options' => $inputOptions);
    }

    /**
     * @return array
     */
    private function getCommandData(Command $command)
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        return array(
            'name' => $command->getName(),
            'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()),
            'description' => $command->getDescription(),
            'help' => $command->getProcessedHelp(),
            'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
            'hidden' => $command->isHidden(),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * Text descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class TextDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) {
            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
        } else {
            $default = '';
        }

        $totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName());
        $spacingWidth = $totalWidth - strlen($argument->getName());

        $this->writeText(sprintf('  <info>%s</info>  %s%s%s',
            $argument->getName(),
            str_repeat(' ', $spacingWidth),
            // + 4 = 2 spaces before <info>, 2 spaces after </info>
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
            $default
        ), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) {
            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
        } else {
            $default = '';
        }

        $value = '';
        if ($option->acceptValue()) {
            $value = '='.strtoupper($option->getName());

            if ($option->isValueOptional()) {
                $value = '['.$value.']';
            }
        }

        $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($option));
        $synopsis = sprintf('%s%s',
            $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : '    ',
            sprintf('--%s%s', $option->getName(), $value)
        );

        $spacingWidth = $totalWidth - Helper::strlen($synopsis);

        $this->writeText(sprintf('  <info>%s</info>  %s%s%s%s',
            $synopsis,
            str_repeat(' ', $spacingWidth),
            // + 4 = 2 spaces before <info>, 2 spaces after </info>
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
            $default,
            $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
        ), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
        foreach ($definition->getArguments() as $argument) {
            $totalWidth = max($totalWidth, Helper::strlen($argument->getName()));
        }

        if ($definition->getArguments()) {
            $this->writeText('<comment>Arguments:</comment>', $options);
            $this->writeText("\n");
            foreach ($definition->getArguments() as $argument) {
                $this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth)));
                $this->writeText("\n");
            }
        }

        if ($definition->getArguments() && $definition->getOptions()) {
            $this->writeText("\n");
        }

        if ($definition->getOptions()) {
            $laterOptions = array();

            $this->writeText('<comment>Options:</comment>', $options);
            foreach ($definition->getOptions() as $option) {
                if (strlen($option->getShortcut()) > 1) {
                    $laterOptions[] = $option;
                    continue;
                }
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth)));
            }
            foreach ($laterOptions as $option) {
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth)));
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $command->getSynopsis(true);
        $command->getSynopsis(false);
        $command->mergeApplicationDefinition(false);

        $this->writeText('<comment>Usage:</comment>', $options);
        foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) {
            $this->writeText("\n");
            $this->writeText('  '.OutputFormatter::escape($usage), $options);
        }
        $this->writeText("\n");

        $definition = $command->getNativeDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->writeText("\n");
            $this->describeInputDefinition($definition, $options);
            $this->writeText("\n");
        }

        if ($help = $command->getProcessedHelp()) {
            $this->writeText("\n");
            $this->writeText('<comment>Help:</comment>', $options);
            $this->writeText("\n");
            $this->writeText('  '.str_replace("\n", "\n  ", $help), $options);
            $this->writeText("\n");
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);

        if (isset($options['raw_text']) && $options['raw_text']) {
            $width = $this->getColumnWidth($description->getCommands());

            foreach ($description->getCommands() as $command) {
                $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
                $this->writeText("\n");
            }
        } else {
            if ('' != $help = $application->getHelp()) {
                $this->writeText("$help\n\n", $options);
            }

            $this->writeText("<comment>Usage:</comment>\n", $options);
            $this->writeText("  command [options] [arguments]\n\n", $options);

            $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);

            $this->writeText("\n");
            $this->writeText("\n");

            $commands = $description->getCommands();
            $namespaces = $description->getNamespaces();
            if ($describedNamespace && $namespaces) {
                // make sure all alias commands are included when describing a specific namespace
                $describedNamespaceInfo = reset($namespaces);
                foreach ($describedNamespaceInfo['commands'] as $name) {
                    $commands[$name] = $description->getCommand($name);
                }
            }

            // calculate max. width based on available commands per namespace
            $width = $this->getColumnWidth(call_user_func_array('array_merge', array_map(function ($namespace) use ($commands) {
                return array_intersect($namespace['commands'], array_keys($commands));
            }, $namespaces)));

            if ($describedNamespace) {
                $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
            } else {
                $this->writeText('<comment>Available commands:</comment>', $options);
            }

            foreach ($namespaces as $namespace) {
                $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) {
                    return isset($commands[$name]);
                });

                if (!$namespace['commands']) {
                    continue;
                }

                if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                    $this->writeText("\n");
                    $this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
                }

                foreach ($namespace['commands'] as $name) {
                    $this->writeText("\n");
                    $spacingWidth = $width - Helper::strlen($name);
                    $command = $commands[$name];
                    $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
                    $this->writeText(sprintf('  <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
                }
            }

            $this->writeText("\n");
        }
    }

    /**
     * {@inheritdoc}
     */
    private function writeText($content, array $options = array())
    {
        $this->write(
            isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
            isset($options['raw_output']) ? !$options['raw_output'] : true
        );
    }

    /**
     * Formats command aliases to show them in the command description.
     *
     * @return string
     */
    private function getCommandAliasesText(Command $command)
    {
        $text = '';
        $aliases = $command->getAliases();

        if ($aliases) {
            $text = '['.implode('|', $aliases).'] ';
        }

        return $text;
    }

    /**
     * Formats input option/argument default value.
     *
     * @param mixed $default
     *
     * @return string
     */
    private function formatDefaultValue($default)
    {
        if (INF === $default) {
            return 'INF';
        }

        if (is_string($default)) {
            $default = OutputFormatter::escape($default);
        } elseif (is_array($default)) {
            foreach ($default as $key => $value) {
                if (is_string($value)) {
                    $default[$key] = OutputFormatter::escape($value);
                }
            }
        }

        return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
    }

    /**
     * @param (Command|string)[] $commands
     *
     * @return int
     */
    private function getColumnWidth(array $commands)
    {
        $widths = array();

        foreach ($commands as $command) {
            if ($command instanceof Command) {
                $widths[] = Helper::strlen($command->getName());
                foreach ($command->getAliases() as $alias) {
                    $widths[] = Helper::strlen($alias);
                }
            } else {
                $widths[] = Helper::strlen($command);
            }
        }

        return $widths ? max($widths) + 2 : 0;
    }

    /**
     * @param InputOption[] $options
     *
     * @return int
     */
    private function calculateTotalWidthForOptions(array $options)
    {
        $totalWidth = 0;
        foreach ($options as $option) {
            // "-" + shortcut + ", --" + name
            $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName());

            if ($option->acceptValue()) {
                $valueLength = 1 + Helper::strlen($option->getName()); // = + value
                $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]

                $nameLength += $valueLength;
            }
            $totalWidth = max($totalWidth, $nameLength);
        }

        return $totalWidth;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * XML descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class XmlDescriptor extends Descriptor
{
    /**
     * @return \DOMDocument
     */
    public function getInputDefinitionDocument(InputDefinition $definition)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($definitionXML = $dom->createElement('definition'));

        $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
        foreach ($definition->getArguments() as $argument) {
            $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
        }

        $definitionXML->appendChild($optionsXML = $dom->createElement('options'));
        foreach ($definition->getOptions() as $option) {
            $this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
        }

        return $dom;
    }

    /**
     * @return \DOMDocument
     */
    public function getCommandDocument(Command $command)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($commandXML = $dom->createElement('command'));

        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        $commandXML->setAttribute('id', $command->getName());
        $commandXML->setAttribute('name', $command->getName());
        $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);

        $commandXML->appendChild($usagesXML = $dom->createElement('usages'));

        foreach (array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()) as $usage) {
            $usagesXML->appendChild($dom->createElement('usage', $usage));
        }

        $commandXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));

        $commandXML->appendChild($helpXML = $dom->createElement('help'));
        $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));

        $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition());
        $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));

        return $dom;
    }

    /**
     * @param Application $application
     * @param string|null $namespace
     *
     * @return \DOMDocument
     */
    public function getApplicationDocument(Application $application, $namespace = null)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($rootXml = $dom->createElement('symfony'));

        if ('UNKNOWN' !== $application->getName()) {
            $rootXml->setAttribute('name', $application->getName());
            if ('UNKNOWN' !== $application->getVersion()) {
                $rootXml->setAttribute('version', $application->getVersion());
            }
        }

        $rootXml->appendChild($commandsXML = $dom->createElement('commands'));

        $description = new ApplicationDescription($application, $namespace, true);

        if ($namespace) {
            $commandsXML->setAttribute('namespace', $namespace);
        }

        foreach ($description->getCommands() as $command) {
            $this->appendDocument($commandsXML, $this->getCommandDocument($command));
        }

        if (!$namespace) {
            $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));

            foreach ($description->getNamespaces() as $namespaceDescription) {
                $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
                $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);

                foreach ($namespaceDescription['commands'] as $name) {
                    $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
                    $commandXML->appendChild($dom->createTextNode($name));
                }
            }
        }

        return $dom;
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        $this->writeDocument($this->getInputArgumentDocument($argument));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        $this->writeDocument($this->getInputOptionDocument($option));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $this->writeDocument($this->getInputDefinitionDocument($definition));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $this->writeDocument($this->getCommandDocument($command));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null));
    }

    /**
     * Appends document children to parent node.
     */
    private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
    {
        foreach ($importedParent->childNodes as $childNode) {
            $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
        }
    }

    /**
     * Writes DOM document.
     *
     * @return \DOMDocument|string
     */
    private function writeDocument(\DOMDocument $dom)
    {
        $dom->formatOutput = true;
        $this->write($dom->saveXML());
    }

    /**
     * @return \DOMDocument
     */
    private function getInputArgumentDocument(InputArgument $argument)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');

        $dom->appendChild($objectXML = $dom->createElement('argument'));
        $objectXML->setAttribute('name', $argument->getName());
        $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
        $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));

        $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
        $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : (is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array()));
        foreach ($defaults as $default) {
            $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
            $defaultXML->appendChild($dom->createTextNode($default));
        }

        return $dom;
    }

    /**
     * @return \DOMDocument
     */
    private function getInputOptionDocument(InputOption $option)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');

        $dom->appendChild($objectXML = $dom->createElement('option'));
        $objectXML->setAttribute('name', '--'.$option->getName());
        $pos = strpos($option->getShortcut(), '|');
        if (false !== $pos) {
            $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
            $objectXML->setAttribute('shortcuts', '-'.implode('|-', explode('|', $option->getShortcut())));
        } else {
            $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
        }
        $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
        $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
        $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($option->getDescription()));

        if ($option->acceptValue()) {
            $defaults = is_array($option->getDefault()) ? $option->getDefault() : (is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array()));
            $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));

            if (!empty($defaults)) {
                foreach ($defaults as $default) {
                    $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
                    $defaultXML->appendChild($dom->createTextNode($default));
                }
            }
        }

        return $dom;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Logger;

use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;

/**
 * PSR-3 compliant console logger.
 *
 * @author Kévin Dunglas <dunglas@gmail.com>
 *
 * @see http://www.php-fig.org/psr/psr-3/
 */
class ConsoleLogger extends AbstractLogger
{
    const INFO = 'info';
    const ERROR = 'error';

    private $output;
    private $verbosityLevelMap = array(
        LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
        LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
        LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
    );
    private $formatLevelMap = array(
        LogLevel::EMERGENCY => self::ERROR,
        LogLevel::ALERT => self::ERROR,
        LogLevel::CRITICAL => self::ERROR,
        LogLevel::ERROR => self::ERROR,
        LogLevel::WARNING => self::INFO,
        LogLevel::NOTICE => self::INFO,
        LogLevel::INFO => self::INFO,
        LogLevel::DEBUG => self::INFO,
    );
    private $errored = false;

    public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array())
    {
        $this->output = $output;
        $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
        $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
    }

    /**
     * {@inheritdoc}
     */
    public function log($level, $message, array $context = array())
    {
        if (!isset($this->verbosityLevelMap[$level])) {
            throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
        }

        $output = $this->output;

        // Write to the error output if necessary and available
        if (self::ERROR === $this->formatLevelMap[$level]) {
            if ($this->output instanceof ConsoleOutputInterface) {
                $output = $output->getErrorOutput();
            }
            $this->errored = true;
        }

        // the if condition check isn't necessary -- it's the same one that $output will do internally anyway.
        // We only do it for efficiency here as the message formatting is relatively expensive.
        if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) {
            $output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]);
        }
    }

    /**
     * Returns true when any messages have been logged at error levels.
     *
     * @return bool
     */
    public function hasErrored()
    {
        return $this->errored;
    }

    /**
     * Interpolates context values into the message placeholders.
     *
     * @author PHP Framework Interoperability Group
     *
     * @param string $message
     * @param array  $context
     *
     * @return string
     */
    private function interpolate($message, array $context)
    {
        if (false === strpos($message, '{')) {
            return $message;
        }

        $replacements = array();
        foreach ($context as $key => $val) {
            if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
                $replacements["{{$key}}"] = $val;
            } elseif ($val instanceof \DateTimeInterface) {
                $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);
            } elseif (\is_object($val)) {
                $replacements["{{$key}}"] = '[object '.\get_class($val).']';
            } else {
                $replacements["{{$key}}"] = '['.\gettype($val).']';
            }
        }

        return strtr($message, $replacements);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Represents a choice question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ChoiceQuestion extends Question
{
    private $choices;
    private $multiselect = false;
    private $prompt = ' > ';
    private $errorMessage = 'Value "%s" is invalid';

    /**
     * @param string $question The question to ask to the user
     * @param array  $choices  The list of available choices
     * @param mixed  $default  The default answer to return
     */
    public function __construct($question, array $choices, $default = null)
    {
        if (!$choices) {
            throw new \LogicException('Choice question must have at least 1 choice available.');
        }

        parent::__construct($question, $default);

        $this->choices = $choices;
        $this->setValidator($this->getDefaultValidator());
        $this->setAutocompleterValues($choices);
    }

    /**
     * Returns available choices.
     *
     * @return array
     */
    public function getChoices()
    {
        return $this->choices;
    }

    /**
     * Sets multiselect option.
     *
     * When multiselect is set to true, multiple choices can be answered.
     *
     * @param bool $multiselect
     *
     * @return $this
     */
    public function setMultiselect($multiselect)
    {
        $this->multiselect = $multiselect;
        $this->setValidator($this->getDefaultValidator());

        return $this;
    }

    /**
     * Returns whether the choices are multiselect.
     *
     * @return bool
     */
    public function isMultiselect()
    {
        return $this->multiselect;
    }

    /**
     * Gets the prompt for choices.
     *
     * @return string
     */
    public function getPrompt()
    {
        return $this->prompt;
    }

    /**
     * Sets the prompt for choices.
     *
     * @param string $prompt
     *
     * @return $this
     */
    public function setPrompt($prompt)
    {
        $this->prompt = $prompt;

        return $this;
    }

    /**
     * Sets the error message for invalid values.
     *
     * The error message has a string placeholder (%s) for the invalid value.
     *
     * @param string $errorMessage
     *
     * @return $this
     */
    public function setErrorMessage($errorMessage)
    {
        $this->errorMessage = $errorMessage;
        $this->setValidator($this->getDefaultValidator());

        return $this;
    }

    /**
     * Returns the default answer validator.
     *
     * @return callable
     */
    private function getDefaultValidator()
    {
        $choices = $this->choices;
        $errorMessage = $this->errorMessage;
        $multiselect = $this->multiselect;
        $isAssoc = $this->isAssoc($choices);

        return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
            // Collapse all spaces.
            $selectedChoices = str_replace(' ', '', $selected);

            if ($multiselect) {
                // Check for a separated comma values
                if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $selected));
                }
                $selectedChoices = explode(',', $selectedChoices);
            } else {
                $selectedChoices = array($selected);
            }

            $multiselectChoices = array();
            foreach ($selectedChoices as $value) {
                $results = array();
                foreach ($choices as $key => $choice) {
                    if ($choice === $value) {
                        $results[] = $key;
                    }
                }

                if (count($results) > 1) {
                    throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results)));
                }

                $result = array_search($value, $choices);

                if (!$isAssoc) {
                    if (false !== $result) {
                        $result = $choices[$result];
                    } elseif (isset($choices[$value])) {
                        $result = $choices[$value];
                    }
                } elseif (false === $result && isset($choices[$value])) {
                    $result = $value;
                }

                if (false === $result) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $value));
                }

                $multiselectChoices[] = (string) $result;
            }

            if ($multiselect) {
                return $multiselectChoices;
            }

            return current($multiselectChoices);
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a Question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Question
{
    private $question;
    private $attempts;
    private $hidden = false;
    private $hiddenFallback = true;
    private $autocompleterValues;
    private $validator;
    private $default;
    private $normalizer;

    /**
     * @param string $question The question to ask to the user
     * @param mixed  $default  The default answer to return if the user enters nothing
     */
    public function __construct($question, $default = null)
    {
        $this->question = $question;
        $this->default = $default;
    }

    /**
     * Returns the question.
     *
     * @return string
     */
    public function getQuestion()
    {
        return $this->question;
    }

    /**
     * Returns the default answer.
     *
     * @return mixed
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * Returns whether the user response must be hidden.
     *
     * @return bool
     */
    public function isHidden()
    {
        return $this->hidden;
    }

    /**
     * Sets whether the user response must be hidden or not.
     *
     * @param bool $hidden
     *
     * @return $this
     *
     * @throws LogicException In case the autocompleter is also used
     */
    public function setHidden($hidden)
    {
        if ($this->autocompleterValues) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->hidden = (bool) $hidden;

        return $this;
    }

    /**
     * In case the response can not be hidden, whether to fallback on non-hidden question or not.
     *
     * @return bool
     */
    public function isHiddenFallback()
    {
        return $this->hiddenFallback;
    }

    /**
     * Sets whether to fallback on non-hidden question if the response can not be hidden.
     *
     * @param bool $fallback
     *
     * @return $this
     */
    public function setHiddenFallback($fallback)
    {
        $this->hiddenFallback = (bool) $fallback;

        return $this;
    }

    /**
     * Gets values for the autocompleter.
     *
     * @return null|iterable
     */
    public function getAutocompleterValues()
    {
        return $this->autocompleterValues;
    }

    /**
     * Sets values for the autocompleter.
     *
     * @param null|iterable $values
     *
     * @return $this
     *
     * @throws InvalidArgumentException
     * @throws LogicException
     */
    public function setAutocompleterValues($values)
    {
        if (is_array($values)) {
            $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);
        }

        if (null !== $values && !is_array($values) && !$values instanceof \Traversable) {
            throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or a `Traversable` object.');
        }

        if ($this->hidden) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->autocompleterValues = $values;

        return $this;
    }

    /**
     * Sets a validator for the question.
     *
     * @param null|callable $validator
     *
     * @return $this
     */
    public function setValidator(callable $validator = null)
    {
        $this->validator = $validator;

        return $this;
    }

    /**
     * Gets the validator for the question.
     *
     * @return null|callable
     */
    public function getValidator()
    {
        return $this->validator;
    }

    /**
     * Sets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     *
     * @param null|int $attempts
     *
     * @return $this
     *
     * @throws InvalidArgumentException in case the number of attempts is invalid
     */
    public function setMaxAttempts($attempts)
    {
        if (null !== $attempts && $attempts < 1) {
            throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
        }

        $this->attempts = $attempts;

        return $this;
    }

    /**
     * Gets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     *
     * @return null|int
     */
    public function getMaxAttempts()
    {
        return $this->attempts;
    }

    /**
     * Sets a normalizer for the response.
     *
     * The normalizer can be a callable (a string), a closure or a class implementing __invoke.
     *
     * @param callable $normalizer
     *
     * @return $this
     */
    public function setNormalizer(callable $normalizer)
    {
        $this->normalizer = $normalizer;

        return $this;
    }

    /**
     * Gets the normalizer for the response.
     *
     * The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
     *
     * @return callable
     */
    public function getNormalizer()
    {
        return $this->normalizer;
    }

    protected function isAssoc($array)
    {
        return (bool) count(array_filter(array_keys($array), 'is_string'));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

/**
 * Represents a yes/no question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConfirmationQuestion extends Question
{
    private $trueAnswerRegex;

    /**
     * @param string $question        The question to ask to the user
     * @param bool   $default         The default answer to return, true or false
     * @param string $trueAnswerRegex A regex to match the "yes" answer
     */
    public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i')
    {
        parent::__construct($question, (bool) $default);

        $this->trueAnswerRegex = $trueAnswerRegex;
        $this->setNormalizer($this->getDefaultNormalizer());
    }

    /**
     * Returns the default answer normalizer.
     *
     * @return callable
     */
    private function getDefaultNormalizer()
    {
        $default = $this->getDefault();
        $regex = $this->trueAnswerRegex;

        return function ($answer) use ($default, $regex) {
            if (is_bool($answer)) {
                return $answer;
            }

            $answerIsTrue = (bool) preg_match($regex, $answer);
            if (false === $default) {
                return $answer && $answerIsTrue;
            }

            return !$answer || $answerIsTrue;
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputDefinition;

/**
 * ListCommand displays the list of all available commands for the application.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ListCommand extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('list')
            ->setDefinition($this->createDefinition())
            ->setDescription('Lists commands')
            ->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:

  <info>php %command.full_name%</info>

You can also display the commands for a specific namespace:

  <info>php %command.full_name% test</info>

You can also output the information in other formats by using the <comment>--format</comment> option:

  <info>php %command.full_name% --format=xml</info>

It's also possible to get raw list of commands (useful for embedding command runner):

  <info>php %command.full_name% --raw</info>
EOF
            )
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function getNativeDefinition()
    {
        return $this->createDefinition();
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $helper = new DescriptorHelper();
        $helper->describe($output, $this->getApplication(), array(
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
            'namespace' => $input->getArgument('namespace'),
        ));
    }

    /**
     * {@inheritdoc}
     */
    private function createDefinition()
    {
        return new InputDefinition(array(
            new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
            new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
            new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
        ));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * HelpCommand displays the help for a given command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HelpCommand extends Command
{
    private $command;

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->ignoreValidationErrors();

        $this
            ->setName('help')
            ->setDefinition(array(
                new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
                new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
            ))
            ->setDescription('Displays help for a command')
            ->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:

  <info>php %command.full_name% list</info>

You can also output the help in other formats by using the <comment>--format</comment> option:

  <info>php %command.full_name% --format=xml list</info>

To display the list of available commands, please use the <info>list</info> command.
EOF
            )
        ;
    }

    public function setCommand(Command $command)
    {
        $this->command = $command;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if (null === $this->command) {
            $this->command = $this->getApplication()->find($input->getArgument('command_name'));
        }

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->command, array(
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
        ));

        $this->command = null;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;

/**
 * Basic lock feature for commands.
 *
 * @author Geoffrey Brier <geoffrey.brier@gmail.com>
 */
trait LockableTrait
{
    /** @var Lock */
    private $lock;

    /**
     * Locks a command.
     *
     * @return bool
     */
    private function lock($name = null, $blocking = false)
    {
        if (!class_exists(SemaphoreStore::class)) {
            throw new RuntimeException('To enable the locking feature you must install the symfony/lock component.');
        }

        if (null !== $this->lock) {
            throw new LogicException('A lock is already in place.');
        }

        if (SemaphoreStore::isSupported($blocking)) {
            $store = new SemaphoreStore();
        } else {
            $store = new FlockStore();
        }

        $this->lock = (new Factory($store))->createLock($name ?: $this->getName());
        if (!$this->lock->acquire($blocking)) {
            $this->lock = null;

            return false;
        }

        return true;
    }

    /**
     * Releases the command lock if there is one.
     */
    private function release()
    {
        if ($this->lock) {
            $this->lock->release();
            $this->lock = null;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Base class for all commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Command
{
    /**
     * @var string|null The default command name
     */
    protected static $defaultName;

    private $application;
    private $name;
    private $processTitle;
    private $aliases = array();
    private $definition;
    private $hidden = false;
    private $help;
    private $description;
    private $ignoreValidationErrors = false;
    private $applicationDefinitionMerged = false;
    private $applicationDefinitionMergedWithArgs = false;
    private $code;
    private $synopsis = array();
    private $usages = array();
    private $helperSet;

    /**
     * @return string|null The default command name or null when no default name is set
     */
    public static function getDefaultName()
    {
        $class = get_called_class();
        $r = new \ReflectionProperty($class, 'defaultName');

        return $class === $r->class ? static::$defaultName : null;
    }

    /**
     * @param string|null $name The name of the command; passing null means it must be set in configure()
     *
     * @throws LogicException When the command name is empty
     */
    public function __construct($name = null)
    {
        $this->definition = new InputDefinition();

        if (null !== $name || null !== $name = static::getDefaultName()) {
            $this->setName($name);
        }

        $this->configure();
    }

    /**
     * Ignores validation errors.
     *
     * This is mainly useful for the help command.
     */
    public function ignoreValidationErrors()
    {
        $this->ignoreValidationErrors = true;
    }

    public function setApplication(Application $application = null)
    {
        $this->application = $application;
        if ($application) {
            $this->setHelperSet($application->getHelperSet());
        } else {
            $this->helperSet = null;
        }
    }

    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Gets the helper set.
     *
     * @return HelperSet A HelperSet instance
     */
    public function getHelperSet()
    {
        return $this->helperSet;
    }

    /**
     * Gets the application instance for this command.
     *
     * @return Application An Application instance
     */
    public function getApplication()
    {
        return $this->application;
    }

    /**
     * Checks whether the command is enabled or not in the current environment.
     *
     * Override this to check for x or y and return false if the command can not
     * run properly under the current conditions.
     *
     * @return bool
     */
    public function isEnabled()
    {
        return true;
    }

    /**
     * Configures the current command.
     */
    protected function configure()
    {
    }

    /**
     * Executes the current command.
     *
     * This method is not abstract because you can use this class
     * as a concrete class. In this case, instead of defining the
     * execute() method, you set the code to execute by passing
     * a Closure to the setCode() method.
     *
     * @return null|int null or 0 if everything went fine, or an error code
     *
     * @throws LogicException When this abstract method is not implemented
     *
     * @see setCode()
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        throw new LogicException('You must override the execute() method in the concrete command class.');
    }

    /**
     * Interacts with the user.
     *
     * This method is executed before the InputDefinition is validated.
     * This means that this is the only place where the command can
     * interactively ask for values of missing required arguments.
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Initializes the command just after the input has been validated.
     *
     * This is mainly useful when a lot of commands extends one main command
     * where some things need to be initialized based on the input arguments and options.
     */
    protected function initialize(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Runs the command.
     *
     * The code to execute is either defined directly with the
     * setCode() method or by overriding the execute() method
     * in a sub-class.
     *
     * @return int The command exit code
     *
     * @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}.
     *
     * @see setCode()
     * @see execute()
     */
    public function run(InputInterface $input, OutputInterface $output)
    {
        // force the creation of the synopsis before the merge with the app definition
        $this->getSynopsis(true);
        $this->getSynopsis(false);

        // add the application arguments and options
        $this->mergeApplicationDefinition();

        // bind the input against the command specific arguments/options
        try {
            $input->bind($this->definition);
        } catch (ExceptionInterface $e) {
            if (!$this->ignoreValidationErrors) {
                throw $e;
            }
        }

        $this->initialize($input, $output);

        if (null !== $this->processTitle) {
            if (function_exists('cli_set_process_title')) {
                if (false === @cli_set_process_title($this->processTitle)) {
                    if ('Darwin' === PHP_OS) {
                        $output->writeln('<comment>Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.</comment>');
                    } else {
                        $error = error_get_last();
                        trigger_error($error['message'], E_USER_WARNING);
                    }
                }
            } elseif (function_exists('setproctitle')) {
                setproctitle($this->processTitle);
            } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
                $output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
            }
        }

        if ($input->isInteractive()) {
            $this->interact($input, $output);
        }

        // The command name argument is often omitted when a command is executed directly with its run() method.
        // It would fail the validation if we didn't make sure the command argument is present,
        // since it's required by the application.
        if ($input->hasArgument('command') && null === $input->getArgument('command')) {
            $input->setArgument('command', $this->getName());
        }

        $input->validate();

        if ($this->code) {
            $statusCode = call_user_func($this->code, $input, $output);
        } else {
            $statusCode = $this->execute($input, $output);
        }

        return is_numeric($statusCode) ? (int) $statusCode : 0;
    }

    /**
     * Sets the code to execute when running this command.
     *
     * If this method is used, it overrides the code defined
     * in the execute() method.
     *
     * @param callable $code A callable(InputInterface $input, OutputInterface $output)
     *
     * @return $this
     *
     * @throws InvalidArgumentException
     *
     * @see execute()
     */
    public function setCode(callable $code)
    {
        if ($code instanceof \Closure) {
            $r = new \ReflectionFunction($code);
            if (null === $r->getClosureThis()) {
                if (\PHP_VERSION_ID < 70000) {
                    // Bug in PHP5: https://bugs.php.net/bug.php?id=64761
                    // This means that we cannot bind static closures and therefore we must
                    // ignore any errors here.  There is no way to test if the closure is
                    // bindable.
                    $code = @\Closure::bind($code, $this);
                } else {
                    $code = \Closure::bind($code, $this);
                }
            }
        }

        $this->code = $code;

        return $this;
    }

    /**
     * Merges the application definition with the command definition.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
     */
    public function mergeApplicationDefinition($mergeArgs = true)
    {
        if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) {
            return;
        }

        $this->definition->addOptions($this->application->getDefinition()->getOptions());

        if ($mergeArgs) {
            $currentArguments = $this->definition->getArguments();
            $this->definition->setArguments($this->application->getDefinition()->getArguments());
            $this->definition->addArguments($currentArguments);
        }

        $this->applicationDefinitionMerged = true;
        if ($mergeArgs) {
            $this->applicationDefinitionMergedWithArgs = true;
        }
    }

    /**
     * Sets an array of argument and option instances.
     *
     * @param array|InputDefinition $definition An array of argument and option instances or a definition instance
     *
     * @return $this
     */
    public function setDefinition($definition)
    {
        if ($definition instanceof InputDefinition) {
            $this->definition = $definition;
        } else {
            $this->definition->setDefinition($definition);
        }

        $this->applicationDefinitionMerged = false;

        return $this;
    }

    /**
     * Gets the InputDefinition attached to this Command.
     *
     * @return InputDefinition An InputDefinition instance
     */
    public function getDefinition()
    {
        return $this->definition;
    }

    /**
     * Gets the InputDefinition to be used to create representations of this Command.
     *
     * Can be overridden to provide the original command representation when it would otherwise
     * be changed by merging with the application InputDefinition.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @return InputDefinition An InputDefinition instance
     */
    public function getNativeDefinition()
    {
        return $this->getDefinition();
    }

    /**
     * Adds an argument.
     *
     * @param string $name        The argument name
     * @param int    $mode        The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
     * @param string $description A description text
     * @param mixed  $default     The default value (for InputArgument::OPTIONAL mode only)
     *
     * @return $this
     */
    public function addArgument($name, $mode = null, $description = '', $default = null)
    {
        $this->definition->addArgument(new InputArgument($name, $mode, $description, $default));

        return $this;
    }

    /**
     * Adds an option.
     *
     * @param string $name        The option name
     * @param string $shortcut    The shortcut (can be null)
     * @param int    $mode        The option mode: One of the InputOption::VALUE_* constants
     * @param string $description A description text
     * @param mixed  $default     The default value (must be null for InputOption::VALUE_NONE)
     *
     * @return $this
     */
    public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
    {
        $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));

        return $this;
    }

    /**
     * Sets the name of the command.
     *
     * This method can set both the namespace and the name if
     * you separate them by a colon (:)
     *
     *     $command->setName('foo:bar');
     *
     * @param string $name The command name
     *
     * @return $this
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    public function setName($name)
    {
        $this->validateName($name);

        $this->name = $name;

        return $this;
    }

    /**
     * Sets the process title of the command.
     *
     * This feature should be used only when creating a long process command,
     * like a daemon.
     *
     * PHP 5.5+ or the proctitle PECL library is required
     *
     * @param string $title The process title
     *
     * @return $this
     */
    public function setProcessTitle($title)
    {
        $this->processTitle = $title;

        return $this;
    }

    /**
     * Returns the command name.
     *
     * @return string The command name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param bool $hidden Whether or not the command should be hidden from the list of commands
     *
     * @return Command The current instance
     */
    public function setHidden($hidden)
    {
        $this->hidden = (bool) $hidden;

        return $this;
    }

    /**
     * @return bool whether the command should be publicly shown or not
     */
    public function isHidden()
    {
        return $this->hidden;
    }

    /**
     * Sets the description for the command.
     *
     * @param string $description The description for the command
     *
     * @return $this
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Returns the description for the command.
     *
     * @return string The description for the command
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Sets the help for the command.
     *
     * @param string $help The help for the command
     *
     * @return $this
     */
    public function setHelp($help)
    {
        $this->help = $help;

        return $this;
    }

    /**
     * Returns the help for the command.
     *
     * @return string The help for the command
     */
    public function getHelp()
    {
        return $this->help;
    }

    /**
     * Returns the processed help for the command replacing the %command.name% and
     * %command.full_name% patterns with the real values dynamically.
     *
     * @return string The processed help for the command
     */
    public function getProcessedHelp()
    {
        $name = $this->name;

        $placeholders = array(
            '%command.name%',
            '%command.full_name%',
        );
        $replacements = array(
            $name,
            $_SERVER['PHP_SELF'].' '.$name,
        );

        return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
    }

    /**
     * Sets the aliases for the command.
     *
     * @param string[] $aliases An array of aliases for the command
     *
     * @return $this
     *
     * @throws InvalidArgumentException When an alias is invalid
     */
    public function setAliases($aliases)
    {
        if (!is_array($aliases) && !$aliases instanceof \Traversable) {
            throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable');
        }

        foreach ($aliases as $alias) {
            $this->validateName($alias);
        }

        $this->aliases = $aliases;

        return $this;
    }

    /**
     * Returns the aliases for the command.
     *
     * @return array An array of aliases for the command
     */
    public function getAliases()
    {
        return $this->aliases;
    }

    /**
     * Returns the synopsis for the command.
     *
     * @param bool $short Whether to show the short version of the synopsis (with options folded) or not
     *
     * @return string The synopsis
     */
    public function getSynopsis($short = false)
    {
        $key = $short ? 'short' : 'long';

        if (!isset($this->synopsis[$key])) {
            $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
        }

        return $this->synopsis[$key];
    }

    /**
     * Add a command usage example.
     *
     * @param string $usage The usage, it'll be prefixed with the command name
     *
     * @return $this
     */
    public function addUsage($usage)
    {
        if (0 !== strpos($usage, $this->name)) {
            $usage = sprintf('%s %s', $this->name, $usage);
        }

        $this->usages[] = $usage;

        return $this;
    }

    /**
     * Returns alternative usages of the command.
     *
     * @return array
     */
    public function getUsages()
    {
        return $this->usages;
    }

    /**
     * Gets a helper instance by name.
     *
     * @param string $name The helper name
     *
     * @return mixed The helper value
     *
     * @throws LogicException           if no HelperSet is defined
     * @throws InvalidArgumentException if the helper is not defined
     */
    public function getHelper($name)
    {
        if (null === $this->helperSet) {
            throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
        }

        return $this->helperSet->get($name);
    }

    /**
     * Validates a command name.
     *
     * It must be non-empty and parts can optionally be separated by ":".
     *
     * @param string $name
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    private function validateName($name)
    {
        if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
            throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\DependencyInjection;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\TypedReference;

/**
 * Registers console commands.
 *
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class AddConsoleCommandPass implements CompilerPassInterface
{
    private $commandLoaderServiceId;
    private $commandTag;

    public function __construct($commandLoaderServiceId = 'console.command_loader', $commandTag = 'console.command')
    {
        $this->commandLoaderServiceId = $commandLoaderServiceId;
        $this->commandTag = $commandTag;
    }

    public function process(ContainerBuilder $container)
    {
        $commandServices = $container->findTaggedServiceIds($this->commandTag, true);
        $lazyCommandMap = array();
        $lazyCommandRefs = array();
        $serviceIds = array();
        $lazyServiceIds = array();

        foreach ($commandServices as $id => $tags) {
            $definition = $container->getDefinition($id);
            $class = $container->getParameterBag()->resolveValue($definition->getClass());

            $commandId = 'console.command.'.strtolower(str_replace('\\', '_', $class));

            if (isset($tags[0]['command'])) {
                $commandName = $tags[0]['command'];
            } else {
                if (!$r = $container->getReflectionClass($class)) {
                    throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
                }
                if (!$r->isSubclassOf(Command::class)) {
                    throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
                }
                $commandName = $class::getDefaultName();
            }

            if (null === $commandName) {
                if (isset($serviceIds[$commandId]) || $container->hasAlias($commandId)) {
                    $commandId = $commandId.'_'.$id;
                }
                if (!$definition->isPublic() || $definition->isPrivate()) {
                    $container->setAlias($commandId, $id)->setPublic(true);
                    $id = $commandId;
                }
                $serviceIds[$commandId] = $id;

                continue;
            }

            $serviceIds[$commandId] = $id;
            $lazyServiceIds[$id] = true;
            unset($tags[0]);
            $lazyCommandMap[$commandName] = $id;
            $lazyCommandRefs[$id] = new TypedReference($id, $class);
            $aliases = array();

            foreach ($tags as $tag) {
                if (isset($tag['command'])) {
                    $aliases[] = $tag['command'];
                    $lazyCommandMap[$tag['command']] = $id;
                }
            }

            $definition->addMethodCall('setName', array($commandName));

            if ($aliases) {
                $definition->addMethodCall('setAliases', array($aliases));
            }
        }

        $container
            ->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
            ->setPublic(true)
            ->setArguments(array(ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap));

        $container->setParameter('console.command.ids', $serviceIds);
        $container->setParameter('console.lazy_command.ids', $lazyServiceIds);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;

/**
 * Input is the base class for all concrete Input classes.
 *
 * Three concrete classes are provided by default:
 *
 *  * `ArgvInput`: The input comes from the CLI arguments (argv)
 *  * `StringInput`: The input is provided as a string
 *  * `ArrayInput`: The input is provided as an array
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Input implements InputInterface, StreamableInputInterface
{
    protected $definition;
    protected $stream;
    protected $options = array();
    protected $arguments = array();
    protected $interactive = true;

    public function __construct(InputDefinition $definition = null)
    {
        if (null === $definition) {
            $this->definition = new InputDefinition();
        } else {
            $this->bind($definition);
            $this->validate();
        }
    }

    /**
     * {@inheritdoc}
     */
    public function bind(InputDefinition $definition)
    {
        $this->arguments = array();
        $this->options = array();
        $this->definition = $definition;

        $this->parse();
    }

    /**
     * Processes command line arguments.
     */
    abstract protected function parse();

    /**
     * {@inheritdoc}
     */
    public function validate()
    {
        $definition = $this->definition;
        $givenArguments = $this->arguments;

        $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) {
            return !array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
        });

        if (count($missingArguments) > 0) {
            throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function isInteractive()
    {
        return $this->interactive;
    }

    /**
     * {@inheritdoc}
     */
    public function setInteractive($interactive)
    {
        $this->interactive = (bool) $interactive;
    }

    /**
     * {@inheritdoc}
     */
    public function getArguments()
    {
        return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
    }

    /**
     * {@inheritdoc}
     */
    public function getArgument($name)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault();
    }

    /**
     * {@inheritdoc}
     */
    public function setArgument($name, $value)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function hasArgument($name)
    {
        return $this->definition->hasArgument($name);
    }

    /**
     * {@inheritdoc}
     */
    public function getOptions()
    {
        return array_merge($this->definition->getOptionDefaults(), $this->options);
    }

    /**
     * {@inheritdoc}
     */
    public function getOption($name)
    {
        if (!$this->definition->hasOption($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
        }

        return array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
    }

    /**
     * {@inheritdoc}
     */
    public function setOption($name, $value)
    {
        if (!$this->definition->hasOption($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
        }

        $this->options[$name] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function hasOption($name)
    {
        return $this->definition->hasOption($name);
    }

    /**
     * Escapes a token through escapeshellarg if it contains unsafe chars.
     *
     * @param string $token
     *
     * @return string
     */
    public function escapeToken($token)
    {
        return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
    }

    /**
     * {@inheritdoc}
     */
    public function setStream($stream)
    {
        $this->stream = $stream;
    }

    /**
     * {@inheritdoc}
     */
    public function getStream()
    {
        return $this->stream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * StringInput represents an input provided as a string.
 *
 * Usage:
 *
 *     $input = new StringInput('foo --bar="foobar"');
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StringInput extends ArgvInput
{
    const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
    const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';

    /**
     * @param string $input A string representing the parameters from the CLI
     */
    public function __construct($input)
    {
        parent::__construct(array());

        $this->setTokens($this->tokenize($input));
    }

    /**
     * Tokenizes a string.
     *
     * @param string $input The input to tokenize
     *
     * @return array An array of tokens
     *
     * @throws InvalidArgumentException When unable to parse input (should never happen)
     */
    private function tokenize($input)
    {
        $tokens = array();
        $length = strlen($input);
        $cursor = 0;
        while ($cursor < $length) {
            if (preg_match('/\s+/A', $input, $match, null, $cursor)) {
            } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) {
                $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2)));
            } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) {
                $tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
            } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
                $tokens[] = stripcslashes($match[1]);
            } else {
                // should never happen
                throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
            }

            $cursor += strlen($match[0]);
        }

        return $tokens;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;

/**
 * ArrayInput represents an input provided as an array.
 *
 * Usage:
 *
 *     $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ArrayInput extends Input
{
    private $parameters;

    public function __construct(array $parameters, InputDefinition $definition = null)
    {
        $this->parameters = $parameters;

        parent::__construct($definition);
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstArgument()
    {
        foreach ($this->parameters as $key => $value) {
            if ($key && '-' === $key[0]) {
                continue;
            }

            return $value;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values, $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if (!is_int($k)) {
                $v = $k;
            }

            if ($onlyParams && '--' === $v) {
                return false;
            }

            if (in_array($v, $values)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterOption($values, $default = false, $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if ($onlyParams && ('--' === $k || (is_int($k) && '--' === $v))) {
                return false;
            }

            if (is_int($k)) {
                if (in_array($v, $values)) {
                    return true;
                }
            } elseif (in_array($k, $values)) {
                return $v;
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     *
     * @return string
     */
    public function __toString()
    {
        $params = array();
        foreach ($this->parameters as $param => $val) {
            if ($param && '-' === $param[0]) {
                if (is_array($val)) {
                    foreach ($val as $v) {
                        $params[] = $param.('' != $v ? '='.$this->escapeToken($v) : '');
                    }
                } else {
                    $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : '');
                }
            } else {
                $params[] = is_array($val) ? implode(' ', array_map(array($this, 'escapeToken'), $val)) : $this->escapeToken($val);
            }
        }

        return implode(' ', $params);
    }

    /**
     * {@inheritdoc}
     */
    protected function parse()
    {
        foreach ($this->parameters as $key => $value) {
            if ('--' === $key) {
                return;
            }
            if (0 === strpos($key, '--')) {
                $this->addLongOption(substr($key, 2), $value);
            } elseif ('-' === $key[0]) {
                $this->addShortOption(substr($key, 1), $value);
            } else {
                $this->addArgument($key, $value);
            }
        }
    }

    /**
     * Adds a short option value.
     *
     * @param string $shortcut The short option key
     * @param mixed  $value    The value for the option
     *
     * @throws InvalidOptionException When option given doesn't exist
     */
    private function addShortOption($shortcut, $value)
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
        }

        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
    }

    /**
     * Adds a long option value.
     *
     * @param string $name  The long option key
     * @param mixed  $value The value for the option
     *
     * @throws InvalidOptionException When option given doesn't exist
     * @throws InvalidOptionException When a required value is missing
     */
    private function addLongOption($name, $value)
    {
        if (!$this->definition->hasOption($name)) {
            throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
        }

        $option = $this->definition->getOption($name);

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name));
            }

            if (!$option->isValueOptional()) {
                $value = true;
            }
        }

        $this->options[$name] = $value;
    }

    /**
     * Adds an argument value.
     *
     * @param string $name  The argument name
     * @param mixed  $value The value for the argument
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    private function addArgument($name, $value)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * InputAwareInterface should be implemented by classes that depends on the
 * Console Input.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
interface InputAwareInterface
{
    /**
     * Sets the Console Input.
     *
     * @param InputInterface
     */
    public function setInput(InputInterface $input);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;

/**
 * InputInterface is the interface implemented by all input classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface InputInterface
{
    /**
     * Returns the first argument from the raw parameters (not parsed).
     *
     * @return string|null The value of the first argument or null otherwise
     */
    public function getFirstArgument();

    /**
     * Returns true if the raw parameters (not parsed) contain a value.
     *
     * This method is to be used to introspect the input parameters
     * before they have been validated. It must be used carefully.
     * Does not necessarily return the correct result for short options
     * when multiple flags are combined in the same option.
     *
     * @param string|array $values     The values to look for in the raw parameters (can be an array)
     * @param bool         $onlyParams Only check real parameters, skip those following an end of options (--) signal
     *
     * @return bool true if the value is contained in the raw parameters
     */
    public function hasParameterOption($values, $onlyParams = false);

    /**
     * Returns the value of a raw option (not parsed).
     *
     * This method is to be used to introspect the input parameters
     * before they have been validated. It must be used carefully.
     * Does not necessarily return the correct result for short options
     * when multiple flags are combined in the same option.
     *
     * @param string|array $values     The value(s) to look for in the raw parameters (can be an array)
     * @param mixed        $default    The default value to return if no result is found
     * @param bool         $onlyParams Only check real parameters, skip those following an end of options (--) signal
     *
     * @return mixed The option value
     */
    public function getParameterOption($values, $default = false, $onlyParams = false);

    /**
     * Binds the current Input instance with the given arguments and options.
     */
    public function bind(InputDefinition $definition);

    /**
     * Validates the input.
     *
     * @throws RuntimeException When not enough arguments are given
     */
    public function validate();

    /**
     * Returns all the given arguments merged with the default values.
     *
     * @return array
     */
    public function getArguments();

    /**
     * Returns the argument value for a given argument name.
     *
     * @param string $name The argument name
     *
     * @return mixed The argument value
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function getArgument($name);

    /**
     * Sets an argument value by name.
     *
     * @param string $name  The argument name
     * @param string $value The argument value
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function setArgument($name, $value);

    /**
     * Returns true if an InputArgument object exists by name or position.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return bool true if the InputArgument object exists, false otherwise
     */
    public function hasArgument($name);

    /**
     * Returns all the given options merged with the default values.
     *
     * @return array
     */
    public function getOptions();

    /**
     * Returns the option value for a given option name.
     *
     * @param string $name The option name
     *
     * @return mixed The option value
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function getOption($name);

    /**
     * Sets an option value by name.
     *
     * @param string      $name  The option name
     * @param string|bool $value The option value
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function setOption($name, $value);

    /**
     * Returns true if an InputOption object exists by name.
     *
     * @param string $name The InputOption name
     *
     * @return bool true if the InputOption object exists, false otherwise
     */
    public function hasOption($name);

    /**
     * Is this input means interactive?
     *
     * @return bool
     */
    public function isInteractive();

    /**
     * Sets the input interactivity.
     *
     * @param bool $interactive If the input should be interactive
     */
    public function setInteractive($interactive);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\RuntimeException;

/**
 * ArgvInput represents an input coming from the CLI arguments.
 *
 * Usage:
 *
 *     $input = new ArgvInput();
 *
 * By default, the `$_SERVER['argv']` array is used for the input values.
 *
 * This can be overridden by explicitly passing the input values in the constructor:
 *
 *     $input = new ArgvInput($_SERVER['argv']);
 *
 * If you pass it yourself, don't forget that the first element of the array
 * is the name of the running application.
 *
 * When passing an argument to the constructor, be sure that it respects
 * the same rules as the argv one. It's almost always better to use the
 * `StringInput` when you want to provide your own input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
 * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
 */
class ArgvInput extends Input
{
    private $tokens;
    private $parsed;

    /**
     * @param array|null           $argv       An array of parameters from the CLI (in the argv format)
     * @param InputDefinition|null $definition A InputDefinition instance
     */
    public function __construct(array $argv = null, InputDefinition $definition = null)
    {
        if (null === $argv) {
            $argv = $_SERVER['argv'];
        }

        // strip the application name
        array_shift($argv);

        $this->tokens = $argv;

        parent::__construct($definition);
    }

    protected function setTokens(array $tokens)
    {
        $this->tokens = $tokens;
    }

    /**
     * {@inheritdoc}
     */
    protected function parse()
    {
        $parseOptions = true;
        $this->parsed = $this->tokens;
        while (null !== $token = array_shift($this->parsed)) {
            if ($parseOptions && '' == $token) {
                $this->parseArgument($token);
            } elseif ($parseOptions && '--' == $token) {
                $parseOptions = false;
            } elseif ($parseOptions && 0 === strpos($token, '--')) {
                $this->parseLongOption($token);
            } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
                $this->parseShortOption($token);
            } else {
                $this->parseArgument($token);
            }
        }
    }

    /**
     * Parses a short option.
     *
     * @param string $token The current token
     */
    private function parseShortOption($token)
    {
        $name = substr($token, 1);

        if (strlen($name) > 1) {
            if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
                // an option with a value (with no space)
                $this->addShortOption($name[0], substr($name, 1));
            } else {
                $this->parseShortOptionSet($name);
            }
        } else {
            $this->addShortOption($name, null);
        }
    }

    /**
     * Parses a short option set.
     *
     * @param string $name The current token
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function parseShortOptionSet($name)
    {
        $len = strlen($name);
        for ($i = 0; $i < $len; ++$i) {
            if (!$this->definition->hasShortcut($name[$i])) {
                throw new RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
            }

            $option = $this->definition->getOptionForShortcut($name[$i]);
            if ($option->acceptValue()) {
                $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));

                break;
            } else {
                $this->addLongOption($option->getName(), null);
            }
        }
    }

    /**
     * Parses a long option.
     *
     * @param string $token The current token
     */
    private function parseLongOption($token)
    {
        $name = substr($token, 2);

        if (false !== $pos = strpos($name, '=')) {
            if (0 === strlen($value = substr($name, $pos + 1))) {
                // if no value after "=" then substr() returns "" since php7 only, false before
                // see http://php.net/manual/fr/migration70.incompatible.php#119151
                if (\PHP_VERSION_ID < 70000 && false === $value) {
                    $value = '';
                }
                array_unshift($this->parsed, $value);
            }
            $this->addLongOption(substr($name, 0, $pos), $value);
        } else {
            $this->addLongOption($name, null);
        }
    }

    /**
     * Parses an argument.
     *
     * @param string $token The current token
     *
     * @throws RuntimeException When too many arguments are given
     */
    private function parseArgument($token)
    {
        $c = count($this->arguments);

        // if input is expecting another argument, add it
        if ($this->definition->hasArgument($c)) {
            $arg = $this->definition->getArgument($c);
            $this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token;

        // if last argument isArray(), append token to last argument
        } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
            $arg = $this->definition->getArgument($c - 1);
            $this->arguments[$arg->getName()][] = $token;

        // unexpected argument
        } else {
            $all = $this->definition->getArguments();
            if (count($all)) {
                throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all))));
            }

            throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token));
        }
    }

    /**
     * Adds a short option value.
     *
     * @param string $shortcut The short option key
     * @param mixed  $value    The value for the option
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addShortOption($shortcut, $value)
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
        }

        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
    }

    /**
     * Adds a long option value.
     *
     * @param string $name  The long option key
     * @param mixed  $value The value for the option
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addLongOption($name, $value)
    {
        if (!$this->definition->hasOption($name)) {
            throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
        }

        $option = $this->definition->getOption($name);

        if (null !== $value && !$option->acceptValue()) {
            throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
        }

        if (in_array($value, array('', null), true) && $option->acceptValue() && count($this->parsed)) {
            // if option accepts an optional or mandatory argument
            // let's see if there is one provided
            $next = array_shift($this->parsed);
            if ((isset($next[0]) && '-' !== $next[0]) || in_array($next, array('', null), true)) {
                $value = $next;
            } else {
                array_unshift($this->parsed, $next);
            }
        }

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name));
            }

            if (!$option->isArray() && !$option->isValueOptional()) {
                $value = true;
            }
        }

        if ($option->isArray()) {
            $this->options[$name][] = $value;
        } else {
            $this->options[$name] = $value;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstArgument()
    {
        foreach ($this->tokens as $token) {
            if ($token && '-' === $token[0]) {
                continue;
            }

            return $token;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values, $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($this->tokens as $token) {
            if ($onlyParams && '--' === $token) {
                return false;
            }
            foreach ($values as $value) {
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = 0 === strpos($value, '--') ? $value.'=' : $value;
                if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterOption($values, $default = false, $onlyParams = false)
    {
        $values = (array) $values;
        $tokens = $this->tokens;

        while (0 < count($tokens)) {
            $token = array_shift($tokens);
            if ($onlyParams && '--' === $token) {
                return false;
            }

            foreach ($values as $value) {
                if ($token === $value) {
                    return array_shift($tokens);
                }
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = 0 === strpos($value, '--') ? $value.'=' : $value;
                if ('' !== $leading && 0 === strpos($token, $leading)) {
                    return substr($token, strlen($leading));
                }
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     *
     * @return string
     */
    public function __toString()
    {
        $tokens = array_map(function ($token) {
            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
                return $match[1].$this->escapeToken($match[2]);
            }

            if ($token && '-' !== $token[0]) {
                return $this->escapeToken($token);
            }

            return $token;
        }, $this->tokens);

        return implode(' ', $tokens);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * A InputDefinition represents a set of valid command line arguments and options.
 *
 * Usage:
 *
 *     $definition = new InputDefinition(array(
 *       new InputArgument('name', InputArgument::REQUIRED),
 *       new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
 *     ));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputDefinition
{
    private $arguments;
    private $requiredCount;
    private $hasAnArrayArgument = false;
    private $hasOptional;
    private $options;
    private $shortcuts;

    /**
     * @param array $definition An array of InputArgument and InputOption instance
     */
    public function __construct(array $definition = array())
    {
        $this->setDefinition($definition);
    }

    /**
     * Sets the definition of the input.
     */
    public function setDefinition(array $definition)
    {
        $arguments = array();
        $options = array();
        foreach ($definition as $item) {
            if ($item instanceof InputOption) {
                $options[] = $item;
            } else {
                $arguments[] = $item;
            }
        }

        $this->setArguments($arguments);
        $this->setOptions($options);
    }

    /**
     * Sets the InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     */
    public function setArguments($arguments = array())
    {
        $this->arguments = array();
        $this->requiredCount = 0;
        $this->hasOptional = false;
        $this->hasAnArrayArgument = false;
        $this->addArguments($arguments);
    }

    /**
     * Adds an array of InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     */
    public function addArguments($arguments = array())
    {
        if (null !== $arguments) {
            foreach ($arguments as $argument) {
                $this->addArgument($argument);
            }
        }
    }

    /**
     * @throws LogicException When incorrect argument is given
     */
    public function addArgument(InputArgument $argument)
    {
        if (isset($this->arguments[$argument->getName()])) {
            throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
        }

        if ($this->hasAnArrayArgument) {
            throw new LogicException('Cannot add an argument after an array argument.');
        }

        if ($argument->isRequired() && $this->hasOptional) {
            throw new LogicException('Cannot add a required argument after an optional one.');
        }

        if ($argument->isArray()) {
            $this->hasAnArrayArgument = true;
        }

        if ($argument->isRequired()) {
            ++$this->requiredCount;
        } else {
            $this->hasOptional = true;
        }

        $this->arguments[$argument->getName()] = $argument;
    }

    /**
     * Returns an InputArgument by name or by position.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return InputArgument An InputArgument object
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function getArgument($name)
    {
        if (!$this->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;

        return $arguments[$name];
    }

    /**
     * Returns true if an InputArgument object exists by name or position.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return bool true if the InputArgument object exists, false otherwise
     */
    public function hasArgument($name)
    {
        $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;

        return isset($arguments[$name]);
    }

    /**
     * Gets the array of InputArgument objects.
     *
     * @return InputArgument[] An array of InputArgument objects
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * Returns the number of InputArguments.
     *
     * @return int The number of InputArguments
     */
    public function getArgumentCount()
    {
        return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
    }

    /**
     * Returns the number of required InputArguments.
     *
     * @return int The number of required InputArguments
     */
    public function getArgumentRequiredCount()
    {
        return $this->requiredCount;
    }

    /**
     * Gets the default values.
     *
     * @return array An array of default values
     */
    public function getArgumentDefaults()
    {
        $values = array();
        foreach ($this->arguments as $argument) {
            $values[$argument->getName()] = $argument->getDefault();
        }

        return $values;
    }

    /**
     * Sets the InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     */
    public function setOptions($options = array())
    {
        $this->options = array();
        $this->shortcuts = array();
        $this->addOptions($options);
    }

    /**
     * Adds an array of InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     */
    public function addOptions($options = array())
    {
        foreach ($options as $option) {
            $this->addOption($option);
        }
    }

    /**
     * @throws LogicException When option given already exist
     */
    public function addOption(InputOption $option)
    {
        if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
            throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
        }

        if ($option->getShortcut()) {
            foreach (explode('|', $option->getShortcut()) as $shortcut) {
                if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) {
                    throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
                }
            }
        }

        $this->options[$option->getName()] = $option;
        if ($option->getShortcut()) {
            foreach (explode('|', $option->getShortcut()) as $shortcut) {
                $this->shortcuts[$shortcut] = $option->getName();
            }
        }
    }

    /**
     * Returns an InputOption by name.
     *
     * @param string $name The InputOption name
     *
     * @return InputOption A InputOption object
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function getOption($name)
    {
        if (!$this->hasOption($name)) {
            throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
        }

        return $this->options[$name];
    }

    /**
     * Returns true if an InputOption object exists by name.
     *
     * This method can't be used to check if the user included the option when
     * executing the command (use getOption() instead).
     *
     * @param string $name The InputOption name
     *
     * @return bool true if the InputOption object exists, false otherwise
     */
    public function hasOption($name)
    {
        return isset($this->options[$name]);
    }

    /**
     * Gets the array of InputOption objects.
     *
     * @return InputOption[] An array of InputOption objects
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Returns true if an InputOption object exists by shortcut.
     *
     * @param string $name The InputOption shortcut
     *
     * @return bool true if the InputOption object exists, false otherwise
     */
    public function hasShortcut($name)
    {
        return isset($this->shortcuts[$name]);
    }

    /**
     * Gets an InputOption by shortcut.
     *
     * @param string $shortcut The Shortcut name
     *
     * @return InputOption An InputOption object
     */
    public function getOptionForShortcut($shortcut)
    {
        return $this->getOption($this->shortcutToName($shortcut));
    }

    /**
     * Gets an array of default values.
     *
     * @return array An array of all default values
     */
    public function getOptionDefaults()
    {
        $values = array();
        foreach ($this->options as $option) {
            $values[$option->getName()] = $option->getDefault();
        }

        return $values;
    }

    /**
     * Returns the InputOption name given a shortcut.
     *
     * @param string $shortcut The shortcut
     *
     * @return string The InputOption name
     *
     * @throws InvalidArgumentException When option given does not exist
     */
    private function shortcutToName($shortcut)
    {
        if (!isset($this->shortcuts[$shortcut])) {
            throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
        }

        return $this->shortcuts[$shortcut];
    }

    /**
     * Gets the synopsis.
     *
     * @param bool $short Whether to return the short version (with options folded) or not
     *
     * @return string The synopsis
     */
    public function getSynopsis($short = false)
    {
        $elements = array();

        if ($short && $this->getOptions()) {
            $elements[] = '[options]';
        } elseif (!$short) {
            foreach ($this->getOptions() as $option) {
                $value = '';
                if ($option->acceptValue()) {
                    $value = sprintf(
                        ' %s%s%s',
                        $option->isValueOptional() ? '[' : '',
                        strtoupper($option->getName()),
                        $option->isValueOptional() ? ']' : ''
                    );
                }

                $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
                $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value);
            }
        }

        if (count($elements) && $this->getArguments()) {
            $elements[] = '[--]';
        }

        foreach ($this->getArguments() as $argument) {
            $element = '<'.$argument->getName().'>';
            if (!$argument->isRequired()) {
                $element = '['.$element.']';
            } elseif ($argument->isArray()) {
                $element = $element.' ('.$element.')';
            }

            if ($argument->isArray()) {
                $element .= '...';
            }

            $elements[] = $element;
        }

        return implode(' ', $elements);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a command line option.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputOption
{
    const VALUE_NONE = 1;
    const VALUE_REQUIRED = 2;
    const VALUE_OPTIONAL = 4;
    const VALUE_IS_ARRAY = 8;

    private $name;
    private $shortcut;
    private $mode;
    private $default;
    private $description;

    /**
     * @param string       $name        The option name
     * @param string|array $shortcut    The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param int          $mode        The option mode: One of the VALUE_* constants
     * @param string       $description A description text
     * @param mixed        $default     The default value (must be null for self::VALUE_NONE)
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null)
    {
        if (0 === strpos($name, '--')) {
            $name = substr($name, 2);
        }

        if (empty($name)) {
            throw new InvalidArgumentException('An option name cannot be empty.');
        }

        if (empty($shortcut)) {
            $shortcut = null;
        }

        if (null !== $shortcut) {
            if (is_array($shortcut)) {
                $shortcut = implode('|', $shortcut);
            }
            $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
            $shortcuts = array_filter($shortcuts);
            $shortcut = implode('|', $shortcuts);

            if (empty($shortcut)) {
                throw new InvalidArgumentException('An option shortcut cannot be empty.');
            }
        }

        if (null === $mode) {
            $mode = self::VALUE_NONE;
        } elseif (!is_int($mode) || $mode > 15 || $mode < 1) {
            throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
        }

        $this->name = $name;
        $this->shortcut = $shortcut;
        $this->mode = $mode;
        $this->description = $description;

        if ($this->isArray() && !$this->acceptValue()) {
            throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
        }

        $this->setDefault($default);
    }

    /**
     * Returns the option shortcut.
     *
     * @return string The shortcut
     */
    public function getShortcut()
    {
        return $this->shortcut;
    }

    /**
     * Returns the option name.
     *
     * @return string The name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Returns true if the option accepts a value.
     *
     * @return bool true if value mode is not self::VALUE_NONE, false otherwise
     */
    public function acceptValue()
    {
        return $this->isValueRequired() || $this->isValueOptional();
    }

    /**
     * Returns true if the option requires a value.
     *
     * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
     */
    public function isValueRequired()
    {
        return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
    }

    /**
     * Returns true if the option takes an optional value.
     *
     * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
     */
    public function isValueOptional()
    {
        return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
    }

    /**
     * Returns true if the option can take multiple values.
     *
     * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
     */
    public function isArray()
    {
        return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
    }

    /**
     * Sets the default value.
     *
     * @param mixed $default The default value
     *
     * @throws LogicException When incorrect default value is given
     */
    public function setDefault($default = null)
    {
        if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
            throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
        }

        if ($this->isArray()) {
            if (null === $default) {
                $default = array();
            } elseif (!is_array($default)) {
                throw new LogicException('A default value for an array option must be an array.');
            }
        }

        $this->default = $this->acceptValue() ? $default : false;
    }

    /**
     * Returns the default value.
     *
     * @return mixed The default value
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * Returns the description text.
     *
     * @return string The description text
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Checks whether the given option equals this one.
     *
     * @return bool
     */
    public function equals(InputOption $option)
    {
        return $option->getName() === $this->getName()
            && $option->getShortcut() === $this->getShortcut()
            && $option->getDefault() === $this->getDefault()
            && $option->isArray() === $this->isArray()
            && $option->isValueRequired() === $this->isValueRequired()
            && $option->isValueOptional() === $this->isValueOptional()
        ;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a command line argument.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputArgument
{
    const REQUIRED = 1;
    const OPTIONAL = 2;
    const IS_ARRAY = 4;

    private $name;
    private $mode;
    private $default;
    private $description;

    /**
     * @param string $name        The argument name
     * @param int    $mode        The argument mode: self::REQUIRED or self::OPTIONAL
     * @param string $description A description text
     * @param mixed  $default     The default value (for self::OPTIONAL mode only)
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    public function __construct($name, $mode = null, $description = '', $default = null)
    {
        if (null === $mode) {
            $mode = self::OPTIONAL;
        } elseif (!is_int($mode) || $mode > 7 || $mode < 1) {
            throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
        }

        $this->name = $name;
        $this->mode = $mode;
        $this->description = $description;

        $this->setDefault($default);
    }

    /**
     * Returns the argument name.
     *
     * @return string The argument name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Returns true if the argument is required.
     *
     * @return bool true if parameter mode is self::REQUIRED, false otherwise
     */
    public function isRequired()
    {
        return self::REQUIRED === (self::REQUIRED & $this->mode);
    }

    /**
     * Returns true if the argument can take multiple values.
     *
     * @return bool true if mode is self::IS_ARRAY, false otherwise
     */
    public function isArray()
    {
        return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
    }

    /**
     * Sets the default value.
     *
     * @param mixed $default The default value
     *
     * @throws LogicException When incorrect default value is given
     */
    public function setDefault($default = null)
    {
        if (self::REQUIRED === $this->mode && null !== $default) {
            throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
        }

        if ($this->isArray()) {
            if (null === $default) {
                $default = array();
            } elseif (!is_array($default)) {
                throw new LogicException('A default value for an array argument must be an array.');
            }
        }

        $this->default = $default;
    }

    /**
     * Returns the default value.
     *
     * @return mixed The default value
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * Returns the description text.
     *
     * @return string The description text
     */
    public function getDescription()
    {
        return $this->description;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * StreamableInputInterface is the interface implemented by all input classes
 * that have an input stream.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
interface StreamableInputInterface extends InputInterface
{
    /**
     * Sets the input stream to read from when interacting with the user.
     *
     * This is mainly useful for testing purpose.
     *
     * @param resource $stream The input stream
     */
    public function setStream($stream);

    /**
     * Returns the input stream.
     *
     * @return resource|null
     */
    public function getStream();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class ProgressIndicator
{
    private $output;
    private $startTime;
    private $format;
    private $message;
    private $indicatorValues;
    private $indicatorCurrent;
    private $indicatorChangeInterval;
    private $indicatorUpdateTime;
    private $started = false;

    private static $formatters;
    private static $formats;

    /**
     * @param OutputInterface $output
     * @param string|null     $format                  Indicator format
     * @param int             $indicatorChangeInterval Change interval in milliseconds
     * @param array|null      $indicatorValues         Animated indicator characters
     */
    public function __construct(OutputInterface $output, $format = null, $indicatorChangeInterval = 100, $indicatorValues = null)
    {
        $this->output = $output;

        if (null === $format) {
            $format = $this->determineBestFormat();
        }

        if (null === $indicatorValues) {
            $indicatorValues = array('-', '\\', '|', '/');
        }

        $indicatorValues = array_values($indicatorValues);

        if (2 > count($indicatorValues)) {
            throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
        }

        $this->format = self::getFormatDefinition($format);
        $this->indicatorChangeInterval = $indicatorChangeInterval;
        $this->indicatorValues = $indicatorValues;
        $this->startTime = time();
    }

    /**
     * Sets the current indicator message.
     *
     * @param string|null $message
     */
    public function setMessage($message)
    {
        $this->message = $message;

        $this->display();
    }

    /**
     * Starts the indicator output.
     *
     * @param $message
     */
    public function start($message)
    {
        if ($this->started) {
            throw new LogicException('Progress indicator already started.');
        }

        $this->message = $message;
        $this->started = true;
        $this->startTime = time();
        $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
        $this->indicatorCurrent = 0;

        $this->display();
    }

    /**
     * Advances the indicator.
     */
    public function advance()
    {
        if (!$this->started) {
            throw new LogicException('Progress indicator has not yet been started.');
        }

        if (!$this->output->isDecorated()) {
            return;
        }

        $currentTime = $this->getCurrentTimeInMilliseconds();

        if ($currentTime < $this->indicatorUpdateTime) {
            return;
        }

        $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
        ++$this->indicatorCurrent;

        $this->display();
    }

    /**
     * Finish the indicator with message.
     *
     * @param $message
     */
    public function finish($message)
    {
        if (!$this->started) {
            throw new LogicException('Progress indicator has not yet been started.');
        }

        $this->message = $message;
        $this->display();
        $this->output->writeln('');
        $this->started = false;
    }

    /**
     * Gets the format for a given name.
     *
     * @param string $name The format name
     *
     * @return string|null A format string
     */
    public static function getFormatDefinition($name)
    {
        if (!self::$formats) {
            self::$formats = self::initFormats();
        }

        return isset(self::$formats[$name]) ? self::$formats[$name] : null;
    }

    /**
     * Sets a placeholder formatter for a given name.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @param string   $name     The placeholder name (including the delimiter char like %)
     * @param callable $callable A PHP callable
     */
    public static function setPlaceholderFormatterDefinition($name, $callable)
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        self::$formatters[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name.
     *
     * @param string $name The placeholder name (including the delimiter char like %)
     *
     * @return callable|null A PHP callable
     */
    public static function getPlaceholderFormatterDefinition($name)
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
    }

    private function display()
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        $self = $this;

        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) {
            if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
                return call_user_func($formatter, $self);
            }

            return $matches[0];
        }, $this->format));
    }

    private function determineBestFormat()
    {
        switch ($this->output->getVerbosity()) {
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            case OutputInterface::VERBOSITY_VERBOSE:
                return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
            case OutputInterface::VERBOSITY_DEBUG:
                return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
            default:
                return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
        }
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param string $message The message
     */
    private function overwrite($message)
    {
        if ($this->output->isDecorated()) {
            $this->output->write("\x0D\x1B[2K");
            $this->output->write($message);
        } else {
            $this->output->writeln($message);
        }
    }

    private function getCurrentTimeInMilliseconds()
    {
        return round(microtime(true) * 1000);
    }

    private static function initPlaceholderFormatters()
    {
        return array(
            'indicator' => function (ProgressIndicator $indicator) {
                return $indicator->indicatorValues[$indicator->indicatorCurrent % count($indicator->indicatorValues)];
            },
            'message' => function (ProgressIndicator $indicator) {
                return $indicator->message;
            },
            'elapsed' => function (ProgressIndicator $indicator) {
                return Helper::formatTime(time() - $indicator->startTime);
            },
            'memory' => function () {
                return Helper::formatMemory(memory_get_usage(true));
            },
        );
    }

    private static function initFormats()
    {
        return array(
            'normal' => ' %indicator% %message%',
            'normal_no_ansi' => ' %message%',

            'verbose' => ' %indicator% %message% (%elapsed:6s%)',
            'verbose_no_ansi' => ' %message% (%elapsed:6s%)',

            'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
            'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputAwareInterface;

/**
 * An implementation of InputAwareInterface for Helpers.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
    protected $input;

    /**
     * {@inheritdoc}
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Helps outputting debug information when running an external program from a command.
 *
 * An external program can be a Process, an HTTP request, or anything else.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DebugFormatterHelper extends Helper
{
    private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default');
    private $started = array();
    private $count = -1;

    /**
     * Starts a debug formatting session.
     *
     * @param string $id      The id of the formatting session
     * @param string $message The message to display
     * @param string $prefix  The prefix to use
     *
     * @return string
     */
    public function start($id, $message, $prefix = 'RUN')
    {
        $this->started[$id] = array('border' => ++$this->count % count($this->colors));

        return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
    }

    /**
     * Adds progress to a formatting session.
     *
     * @param string $id          The id of the formatting session
     * @param string $buffer      The message to display
     * @param bool   $error       Whether to consider the buffer as error
     * @param string $prefix      The prefix for output
     * @param string $errorPrefix The prefix for error output
     *
     * @return string
     */
    public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR')
    {
        $message = '';

        if ($error) {
            if (isset($this->started[$id]['out'])) {
                $message .= "\n";
                unset($this->started[$id]['out']);
            }
            if (!isset($this->started[$id]['err'])) {
                $message .= sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
                $this->started[$id]['err'] = true;
            }

            $message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
        } else {
            if (isset($this->started[$id]['err'])) {
                $message .= "\n";
                unset($this->started[$id]['err']);
            }
            if (!isset($this->started[$id]['out'])) {
                $message .= sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
                $this->started[$id]['out'] = true;
            }

            $message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
        }

        return $message;
    }

    /**
     * Stops a formatting session.
     *
     * @param string $id         The id of the formatting session
     * @param string $message    The message to display
     * @param bool   $successful Whether to consider the result as success
     * @param string $prefix     The prefix for the end output
     *
     * @return string
     */
    public function stop($id, $message, $successful, $prefix = 'RES')
    {
        $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';

        if ($successful) {
            return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
        }

        $message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);

        unset($this->started[$id]['out'], $this->started[$id]['err']);

        return $message;
    }

    /**
     * @param string $id The id of the formatting session
     *
     * @return string
     */
    private function getBorder($id)
    {
        return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'debug_formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * HelperInterface is the interface all helpers must implement.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface HelperInterface
{
    /**
     * Sets the helper set associated with this helper.
     */
    public function setHelperSet(HelperSet $helperSet = null);

    /**
     * Gets the helper set associated with this helper.
     *
     * @return HelperSet A HelperSet instance
     */
    public function getHelperSet();

    /**
     * Returns the canonical name of this helper.
     *
     * @return string The canonical name
     */
    public function getName();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

/**
 * The ProcessHelper class provides helpers to run external processes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ProcessHelper extends Helper
{
    /**
     * Runs an external process.
     *
     * @param OutputInterface      $output    An OutputInterface instance
     * @param string|array|Process $cmd       An instance of Process or an array of arguments to escape and run or a command to run
     * @param string|null          $error     An error message that must be displayed if something went wrong
     * @param callable|null        $callback  A PHP callback to run whenever there is some
     *                                        output available on STDOUT or STDERR
     * @param int                  $verbosity The threshold for verbosity
     *
     * @return Process The process that ran
     */
    public function run(OutputInterface $output, $cmd, $error = null, callable $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        if ($cmd instanceof Process) {
            $process = $cmd;
        } else {
            $process = new Process($cmd);
        }

        if ($verbosity <= $output->getVerbosity()) {
            $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
        }

        if ($output->isDebug()) {
            $callback = $this->wrapCallback($output, $process, $callback);
        }

        $process->run($callback);

        if ($verbosity <= $output->getVerbosity()) {
            $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
            $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
        }

        if (!$process->isSuccessful() && null !== $error) {
            $output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
        }

        return $process;
    }

    /**
     * Runs the process.
     *
     * This is identical to run() except that an exception is thrown if the process
     * exits with a non-zero exit code.
     *
     * @param OutputInterface $output   An OutputInterface instance
     * @param string|Process  $cmd      An instance of Process or a command to run
     * @param string|null     $error    An error message that must be displayed if something went wrong
     * @param callable|null   $callback A PHP callback to run whenever there is some
     *                                  output available on STDOUT or STDERR
     *
     * @return Process The process that ran
     *
     * @throws ProcessFailedException
     *
     * @see run()
     */
    public function mustRun(OutputInterface $output, $cmd, $error = null, callable $callback = null)
    {
        $process = $this->run($output, $cmd, $error, $callback);

        if (!$process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }

        return $process;
    }

    /**
     * Wraps a Process callback to add debugging output.
     *
     * @param OutputInterface $output   An OutputInterface interface
     * @param Process         $process  The Process
     * @param callable|null   $callback A PHP callable
     *
     * @return callable
     */
    public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
            $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));

            if (null !== $callback) {
                call_user_func($callback, $type, $buffer);
            }
        };
    }

    private function escapeString($str)
    {
        return str_replace('<', '\\<', $str);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'process';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * Symfony Style Guide compliant question helper.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyQuestionHelper extends QuestionHelper
{
    /**
     * {@inheritdoc}
     *
     * To be removed in 4.0
     */
    public function ask(InputInterface $input, OutputInterface $output, Question $question)
    {
        $validator = $question->getValidator();
        $question->setValidator(function ($value) use ($validator) {
            if (null !== $validator) {
                $value = $validator($value);
            } else {
                // make required
                if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) {
                    @trigger_error('The default question validator is deprecated since Symfony 3.3 and will not be used anymore in version 4.0. Set a custom question validator if needed.', E_USER_DEPRECATED);

                    throw new LogicException('A value is required.');
                }
            }

            return $value;
        });

        return parent::ask($input, $output, $question);
    }

    /**
     * {@inheritdoc}
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
        $default = $question->getDefault();

        switch (true) {
            case null === $default:
                $text = sprintf(' <info>%s</info>:', $text);

                break;

            case $question instanceof ConfirmationQuestion:
                $text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');

                break;

            case $question instanceof ChoiceQuestion && $question->isMultiselect():
                $choices = $question->getChoices();
                $default = explode(',', $default);

                foreach ($default as $key => $value) {
                    $default[$key] = $choices[trim($value)];
                }

                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));

                break;

            case $question instanceof ChoiceQuestion:
                $choices = $question->getChoices();
                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default]));

                break;

            default:
                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
        }

        $output->writeln($text);

        if ($question instanceof ChoiceQuestion) {
            $width = max(array_map('strlen', array_keys($question->getChoices())));

            foreach ($question->getChoices() as $key => $value) {
                $output->writeln(sprintf("  [<comment>%-${width}s</comment>] %s", $key, $value));
            }
        }

        $output->write(' > ');
    }

    /**
     * {@inheritdoc}
     */
    protected function writeError(OutputInterface $output, \Exception $error)
    {
        if ($output instanceof SymfonyStyle) {
            $output->newLine();
            $output->error($error->getMessage());

            return;
        }

        parent::writeError($output, $error);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Marks a row as being a separator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TableSeparator extends TableCell
{
    public function __construct(array $options = array())
    {
        parent::__construct('', $options);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Defines the styles for a Table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 */
class TableStyle
{
    private $paddingChar = ' ';
    private $horizontalBorderChar = '-';
    private $verticalBorderChar = '|';
    private $crossingChar = '+';
    private $cellHeaderFormat = '<info>%s</info>';
    private $cellRowFormat = '%s';
    private $cellRowContentFormat = ' %s ';
    private $borderFormat = '%s';
    private $padType = STR_PAD_RIGHT;

    /**
     * Sets padding character, used for cell padding.
     *
     * @param string $paddingChar
     *
     * @return $this
     */
    public function setPaddingChar($paddingChar)
    {
        if (!$paddingChar) {
            throw new LogicException('The padding char must not be empty');
        }

        $this->paddingChar = $paddingChar;

        return $this;
    }

    /**
     * Gets padding character, used for cell padding.
     *
     * @return string
     */
    public function getPaddingChar()
    {
        return $this->paddingChar;
    }

    /**
     * Sets horizontal border character.
     *
     * @param string $horizontalBorderChar
     *
     * @return $this
     */
    public function setHorizontalBorderChar($horizontalBorderChar)
    {
        $this->horizontalBorderChar = $horizontalBorderChar;

        return $this;
    }

    /**
     * Gets horizontal border character.
     *
     * @return string
     */
    public function getHorizontalBorderChar()
    {
        return $this->horizontalBorderChar;
    }

    /**
     * Sets vertical border character.
     *
     * @param string $verticalBorderChar
     *
     * @return $this
     */
    public function setVerticalBorderChar($verticalBorderChar)
    {
        $this->verticalBorderChar = $verticalBorderChar;

        return $this;
    }

    /**
     * Gets vertical border character.
     *
     * @return string
     */
    public function getVerticalBorderChar()
    {
        return $this->verticalBorderChar;
    }

    /**
     * Sets crossing character.
     *
     * @param string $crossingChar
     *
     * @return $this
     */
    public function setCrossingChar($crossingChar)
    {
        $this->crossingChar = $crossingChar;

        return $this;
    }

    /**
     * Gets crossing character.
     *
     * @return string $crossingChar
     */
    public function getCrossingChar()
    {
        return $this->crossingChar;
    }

    /**
     * Sets header cell format.
     *
     * @param string $cellHeaderFormat
     *
     * @return $this
     */
    public function setCellHeaderFormat($cellHeaderFormat)
    {
        $this->cellHeaderFormat = $cellHeaderFormat;

        return $this;
    }

    /**
     * Gets header cell format.
     *
     * @return string
     */
    public function getCellHeaderFormat()
    {
        return $this->cellHeaderFormat;
    }

    /**
     * Sets row cell format.
     *
     * @param string $cellRowFormat
     *
     * @return $this
     */
    public function setCellRowFormat($cellRowFormat)
    {
        $this->cellRowFormat = $cellRowFormat;

        return $this;
    }

    /**
     * Gets row cell format.
     *
     * @return string
     */
    public function getCellRowFormat()
    {
        return $this->cellRowFormat;
    }

    /**
     * Sets row cell content format.
     *
     * @param string $cellRowContentFormat
     *
     * @return $this
     */
    public function setCellRowContentFormat($cellRowContentFormat)
    {
        $this->cellRowContentFormat = $cellRowContentFormat;

        return $this;
    }

    /**
     * Gets row cell content format.
     *
     * @return string
     */
    public function getCellRowContentFormat()
    {
        return $this->cellRowContentFormat;
    }

    /**
     * Sets table border format.
     *
     * @param string $borderFormat
     *
     * @return $this
     */
    public function setBorderFormat($borderFormat)
    {
        $this->borderFormat = $borderFormat;

        return $this;
    }

    /**
     * Gets table border format.
     *
     * @return string
     */
    public function getBorderFormat()
    {
        return $this->borderFormat;
    }

    /**
     * Sets cell padding type.
     *
     * @param int $padType STR_PAD_*
     *
     * @return $this
     */
    public function setPadType($padType)
    {
        if (!in_array($padType, array(STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH), true)) {
            throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
        }

        $this->padType = $padType;

        return $this;
    }

    /**
     * Gets cell padding type.
     *
     * @return int
     */
    public function getPadType()
    {
        return $this->padType;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 */
class TableCell
{
    private $value;
    private $options = array(
        'rowspan' => 1,
        'colspan' => 1,
    );

    /**
     * @param string $value
     * @param array  $options
     */
    public function __construct($value = '', array $options = array())
    {
        if (is_numeric($value) && !is_string($value)) {
            $value = (string) $value;
        }

        $this->value = $value;

        // check option names
        if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
            throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
        }

        $this->options = array_merge($this->options, $options);
    }

    /**
     * Returns the cell value.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->value;
    }

    /**
     * Gets number of colspan.
     *
     * @return int
     */
    public function getColspan()
    {
        return (int) $this->options['colspan'];
    }

    /**
     * Gets number of rowspan.
     *
     * @return int
     */
    public function getRowspan()
    {
        return (int) $this->options['rowspan'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * HelperSet represents a set of helpers to be used with a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HelperSet implements \IteratorAggregate
{
    /**
     * @var Helper[]
     */
    private $helpers = array();
    private $command;

    /**
     * @param Helper[] $helpers An array of helper
     */
    public function __construct(array $helpers = array())
    {
        foreach ($helpers as $alias => $helper) {
            $this->set($helper, is_int($alias) ? null : $alias);
        }
    }

    /**
     * Sets a helper.
     *
     * @param HelperInterface $helper The helper instance
     * @param string          $alias  An alias
     */
    public function set(HelperInterface $helper, $alias = null)
    {
        $this->helpers[$helper->getName()] = $helper;
        if (null !== $alias) {
            $this->helpers[$alias] = $helper;
        }

        $helper->setHelperSet($this);
    }

    /**
     * Returns true if the helper if defined.
     *
     * @param string $name The helper name
     *
     * @return bool true if the helper is defined, false otherwise
     */
    public function has($name)
    {
        return isset($this->helpers[$name]);
    }

    /**
     * Gets a helper value.
     *
     * @param string $name The helper name
     *
     * @return HelperInterface The helper instance
     *
     * @throws InvalidArgumentException if the helper is not defined
     */
    public function get($name)
    {
        if (!$this->has($name)) {
            throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
        }

        return $this->helpers[$name];
    }

    public function setCommand(Command $command = null)
    {
        $this->command = $command;
    }

    /**
     * Gets the command associated with this helper set.
     *
     * @return Command A Command instance
     */
    public function getCommand()
    {
        return $this->command;
    }

    /**
     * @return Helper[]
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->helpers);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Provides helpers to display a table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 * @author Max Grigorian <maxakawizard@gmail.com>
 */
class Table
{
    /**
     * Table headers.
     */
    private $headers = array();

    /**
     * Table rows.
     */
    private $rows = array();

    /**
     * Column widths cache.
     */
    private $effectiveColumnWidths = array();

    /**
     * Number of columns cache.
     *
     * @var int
     */
    private $numberOfColumns;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * @var TableStyle
     */
    private $style;

    /**
     * @var array
     */
    private $columnStyles = array();

    /**
     * User set column widths.
     *
     * @var array
     */
    private $columnWidths = array();

    private static $styles;

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;

        if (!self::$styles) {
            self::$styles = self::initStyles();
        }

        $this->setStyle('default');
    }

    /**
     * Sets a style definition.
     *
     * @param string     $name  The style name
     * @param TableStyle $style A TableStyle instance
     */
    public static function setStyleDefinition($name, TableStyle $style)
    {
        if (!self::$styles) {
            self::$styles = self::initStyles();
        }

        self::$styles[$name] = $style;
    }

    /**
     * Gets a style definition by name.
     *
     * @param string $name The style name
     *
     * @return TableStyle
     */
    public static function getStyleDefinition($name)
    {
        if (!self::$styles) {
            self::$styles = self::initStyles();
        }

        if (isset(self::$styles[$name])) {
            return self::$styles[$name];
        }

        throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
    }

    /**
     * Sets table style.
     *
     * @param TableStyle|string $name The style name or a TableStyle instance
     *
     * @return $this
     */
    public function setStyle($name)
    {
        $this->style = $this->resolveStyle($name);

        return $this;
    }

    /**
     * Gets the current table style.
     *
     * @return TableStyle
     */
    public function getStyle()
    {
        return $this->style;
    }

    /**
     * Sets table column style.
     *
     * @param int               $columnIndex Column index
     * @param TableStyle|string $name        The style name or a TableStyle instance
     *
     * @return $this
     */
    public function setColumnStyle($columnIndex, $name)
    {
        $columnIndex = (int) $columnIndex;

        $this->columnStyles[$columnIndex] = $this->resolveStyle($name);

        return $this;
    }

    /**
     * Gets the current style for a column.
     *
     * If style was not set, it returns the global table style.
     *
     * @param int $columnIndex Column index
     *
     * @return TableStyle
     */
    public function getColumnStyle($columnIndex)
    {
        if (isset($this->columnStyles[$columnIndex])) {
            return $this->columnStyles[$columnIndex];
        }

        return $this->getStyle();
    }

    /**
     * Sets the minimum width of a column.
     *
     * @param int $columnIndex Column index
     * @param int $width       Minimum column width in characters
     *
     * @return $this
     */
    public function setColumnWidth($columnIndex, $width)
    {
        $this->columnWidths[(int) $columnIndex] = (int) $width;

        return $this;
    }

    /**
     * Sets the minimum width of all columns.
     *
     * @param array $widths
     *
     * @return $this
     */
    public function setColumnWidths(array $widths)
    {
        $this->columnWidths = array();
        foreach ($widths as $index => $width) {
            $this->setColumnWidth($index, $width);
        }

        return $this;
    }

    public function setHeaders(array $headers)
    {
        $headers = array_values($headers);
        if (!empty($headers) && !is_array($headers[0])) {
            $headers = array($headers);
        }

        $this->headers = $headers;

        return $this;
    }

    public function setRows(array $rows)
    {
        $this->rows = array();

        return $this->addRows($rows);
    }

    public function addRows(array $rows)
    {
        foreach ($rows as $row) {
            $this->addRow($row);
        }

        return $this;
    }

    public function addRow($row)
    {
        if ($row instanceof TableSeparator) {
            $this->rows[] = $row;

            return $this;
        }

        if (!is_array($row)) {
            throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
        }

        $this->rows[] = array_values($row);

        return $this;
    }

    public function setRow($column, array $row)
    {
        $this->rows[$column] = $row;

        return $this;
    }

    /**
     * Renders table to output.
     *
     * Example:
     * <code>
     * +---------------+-----------------------+------------------+
     * | ISBN          | Title                 | Author           |
     * +---------------+-----------------------+------------------+
     * | 99921-58-10-7 | Divine Comedy         | Dante Alighieri  |
     * | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
     * +---------------+-----------------------+------------------+
     * </code>
     */
    public function render()
    {
        $this->calculateNumberOfColumns();
        $rows = $this->buildTableRows($this->rows);
        $headers = $this->buildTableRows($this->headers);

        $this->calculateColumnsWidth(array_merge($headers, $rows));

        $this->renderRowSeparator();
        if (!empty($headers)) {
            foreach ($headers as $header) {
                $this->renderRow($header, $this->style->getCellHeaderFormat());
                $this->renderRowSeparator();
            }
        }
        foreach ($rows as $row) {
            if ($row instanceof TableSeparator) {
                $this->renderRowSeparator();
            } else {
                $this->renderRow($row, $this->style->getCellRowFormat());
            }
        }
        if (!empty($rows)) {
            $this->renderRowSeparator();
        }

        $this->cleanup();
    }

    /**
     * Renders horizontal header separator.
     *
     * Example: <code>+-----+-----------+-------+</code>
     */
    private function renderRowSeparator()
    {
        if (0 === $count = $this->numberOfColumns) {
            return;
        }

        if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) {
            return;
        }

        $markup = $this->style->getCrossingChar();
        for ($column = 0; $column < $count; ++$column) {
            $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar();
        }

        $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
    }

    /**
     * Renders vertical column separator.
     */
    private function renderColumnSeparator()
    {
        return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar());
    }

    /**
     * Renders table row.
     *
     * Example: <code>| 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |</code>
     *
     * @param array  $row
     * @param string $cellFormat
     */
    private function renderRow(array $row, $cellFormat)
    {
        if (empty($row)) {
            return;
        }

        $rowContent = $this->renderColumnSeparator();
        foreach ($this->getRowColumns($row) as $column) {
            $rowContent .= $this->renderCell($row, $column, $cellFormat);
            $rowContent .= $this->renderColumnSeparator();
        }
        $this->output->writeln($rowContent);
    }

    /**
     * Renders table cell with padding.
     *
     * @param array  $row
     * @param int    $column
     * @param string $cellFormat
     */
    private function renderCell(array $row, $column, $cellFormat)
    {
        $cell = isset($row[$column]) ? $row[$column] : '';
        $width = $this->effectiveColumnWidths[$column];
        if ($cell instanceof TableCell && $cell->getColspan() > 1) {
            // add the width of the following columns(numbers of colspan).
            foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
                $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
            }
        }

        // str_pad won't work properly with multi-byte strings, we need to fix the padding
        if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
            $width += strlen($cell) - mb_strwidth($cell, $encoding);
        }

        $style = $this->getColumnStyle($column);

        if ($cell instanceof TableSeparator) {
            return sprintf($style->getBorderFormat(), str_repeat($style->getHorizontalBorderChar(), $width));
        }

        $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
        $content = sprintf($style->getCellRowContentFormat(), $cell);

        return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType()));
    }

    /**
     * Calculate number of columns for this table.
     */
    private function calculateNumberOfColumns()
    {
        if (null !== $this->numberOfColumns) {
            return;
        }

        $columns = array(0);
        foreach (array_merge($this->headers, $this->rows) as $row) {
            if ($row instanceof TableSeparator) {
                continue;
            }

            $columns[] = $this->getNumberOfColumns($row);
        }

        $this->numberOfColumns = max($columns);
    }

    private function buildTableRows($rows)
    {
        $unmergedRows = array();
        for ($rowKey = 0; $rowKey < count($rows); ++$rowKey) {
            $rows = $this->fillNextRows($rows, $rowKey);

            // Remove any new line breaks and replace it with a new line
            foreach ($rows[$rowKey] as $column => $cell) {
                if (!strstr($cell, "\n")) {
                    continue;
                }
                $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
                foreach ($lines as $lineKey => $line) {
                    if ($cell instanceof TableCell) {
                        $line = new TableCell($line, array('colspan' => $cell->getColspan()));
                    }
                    if (0 === $lineKey) {
                        $rows[$rowKey][$column] = $line;
                    } else {
                        $unmergedRows[$rowKey][$lineKey][$column] = $line;
                    }
                }
            }
        }

        $tableRows = array();
        foreach ($rows as $rowKey => $row) {
            $tableRows[] = $this->fillCells($row);
            if (isset($unmergedRows[$rowKey])) {
                $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]);
            }
        }

        return $tableRows;
    }

    /**
     * fill rows that contains rowspan > 1.
     *
     * @param array $rows
     * @param int   $line
     *
     * @return array
     *
     * @throws InvalidArgumentException
     */
    private function fillNextRows(array $rows, $line)
    {
        $unmergedRows = array();
        foreach ($rows[$line] as $column => $cell) {
            if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(is_object($cell) && method_exists($cell, '__toString'))) {
                throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing __toString, %s given.', gettype($cell)));
            }
            if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
                $nbLines = $cell->getRowspan() - 1;
                $lines = array($cell);
                if (strstr($cell, "\n")) {
                    $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
                    $nbLines = count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;

                    $rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan()));
                    unset($lines[0]);
                }

                // create a two dimensional array (rowspan x colspan)
                $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows);
                foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
                    $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
                    $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan()));
                    if ($nbLines === $unmergedRowKey - $line) {
                        break;
                    }
                }
            }
        }

        foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
            // we need to know if $unmergedRow will be merged or inserted into $rows
            if (isset($rows[$unmergedRowKey]) && is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
                foreach ($unmergedRow as $cellKey => $cell) {
                    // insert cell into row at cellKey position
                    array_splice($rows[$unmergedRowKey], $cellKey, 0, array($cell));
                }
            } else {
                $row = $this->copyRow($rows, $unmergedRowKey - 1);
                foreach ($unmergedRow as $column => $cell) {
                    if (!empty($cell)) {
                        $row[$column] = $unmergedRow[$column];
                    }
                }
                array_splice($rows, $unmergedRowKey, 0, array($row));
            }
        }

        return $rows;
    }

    /**
     * fill cells for a row that contains colspan > 1.
     *
     * @return array
     */
    private function fillCells($row)
    {
        $newRow = array();
        foreach ($row as $column => $cell) {
            $newRow[] = $cell;
            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
                foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
                    // insert empty value at column position
                    $newRow[] = '';
                }
            }
        }

        return $newRow ?: $row;
    }

    /**
     * @param array $rows
     * @param int   $line
     *
     * @return array
     */
    private function copyRow(array $rows, $line)
    {
        $row = $rows[$line];
        foreach ($row as $cellKey => $cellValue) {
            $row[$cellKey] = '';
            if ($cellValue instanceof TableCell) {
                $row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan()));
            }
        }

        return $row;
    }

    /**
     * Gets number of columns by row.
     *
     * @return int
     */
    private function getNumberOfColumns(array $row)
    {
        $columns = count($row);
        foreach ($row as $column) {
            $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
        }

        return $columns;
    }

    /**
     * Gets list of columns for the given row.
     *
     * @return array
     */
    private function getRowColumns(array $row)
    {
        $columns = range(0, $this->numberOfColumns - 1);
        foreach ($row as $cellKey => $cell) {
            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
                // exclude grouped columns.
                $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
            }
        }

        return $columns;
    }

    /**
     * Calculates columns widths.
     */
    private function calculateColumnsWidth(array $rows)
    {
        for ($column = 0; $column < $this->numberOfColumns; ++$column) {
            $lengths = array();
            foreach ($rows as $row) {
                if ($row instanceof TableSeparator) {
                    continue;
                }

                foreach ($row as $i => $cell) {
                    if ($cell instanceof TableCell) {
                        $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
                        $textLength = Helper::strlen($textContent);
                        if ($textLength > 0) {
                            $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
                            foreach ($contentColumns as $position => $content) {
                                $row[$i + $position] = $content;
                            }
                        }
                    }
                }

                $lengths[] = $this->getCellWidth($row, $column);
            }

            $this->effectiveColumnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2;
        }
    }

    /**
     * Gets column width.
     *
     * @return int
     */
    private function getColumnSeparatorWidth()
    {
        return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()));
    }

    /**
     * Gets cell width.
     *
     * @param array $row
     * @param int   $column
     *
     * @return int
     */
    private function getCellWidth(array $row, $column)
    {
        $cellWidth = 0;

        if (isset($row[$column])) {
            $cell = $row[$column];
            $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
        }

        $columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;

        return max($cellWidth, $columnWidth);
    }

    /**
     * Called after rendering to cleanup cache data.
     */
    private function cleanup()
    {
        $this->effectiveColumnWidths = array();
        $this->numberOfColumns = null;
    }

    private static function initStyles()
    {
        $borderless = new TableStyle();
        $borderless
            ->setHorizontalBorderChar('=')
            ->setVerticalBorderChar(' ')
            ->setCrossingChar(' ')
        ;

        $compact = new TableStyle();
        $compact
            ->setHorizontalBorderChar('')
            ->setVerticalBorderChar(' ')
            ->setCrossingChar('')
            ->setCellRowContentFormat('%s')
        ;

        $styleGuide = new TableStyle();
        $styleGuide
            ->setHorizontalBorderChar('-')
            ->setVerticalBorderChar(' ')
            ->setCrossingChar(' ')
            ->setCellHeaderFormat('%s')
        ;

        return array(
            'default' => new TableStyle(),
            'borderless' => $borderless,
            'compact' => $compact,
            'symfony-style-guide' => $styleGuide,
        );
    }

    private function resolveStyle($name)
    {
        if ($name instanceof TableStyle) {
            return $name;
        }

        if (isset(self::$styles[$name])) {
            return self::$styles[$name];
        }

        throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * Helper is the base class for all helper classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Helper implements HelperInterface
{
    protected $helperSet = null;

    /**
     * {@inheritdoc}
     */
    public function setHelperSet(HelperSet $helperSet = null)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * {@inheritdoc}
     */
    public function getHelperSet()
    {
        return $this->helperSet;
    }

    /**
     * Returns the length of a string, using mb_strwidth if it is available.
     *
     * @param string $string The string to check its length
     *
     * @return int The length of the string
     */
    public static function strlen($string)
    {
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return strlen($string);
        }

        return mb_strwidth($string, $encoding);
    }

    /**
     * Returns the subset of a string, using mb_substr if it is available.
     *
     * @param string   $string String to subset
     * @param int      $from   Start offset
     * @param int|null $length Length to read
     *
     * @return string The string subset
     */
    public static function substr($string, $from, $length = null)
    {
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return substr($string, $from, $length);
        }

        return mb_substr($string, $from, $length, $encoding);
    }

    public static function formatTime($secs)
    {
        static $timeFormats = array(
            array(0, '< 1 sec'),
            array(1, '1 sec'),
            array(2, 'secs', 1),
            array(60, '1 min'),
            array(120, 'mins', 60),
            array(3600, '1 hr'),
            array(7200, 'hrs', 3600),
            array(86400, '1 day'),
            array(172800, 'days', 86400),
        );

        foreach ($timeFormats as $index => $format) {
            if ($secs >= $format[0]) {
                if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0])
                    || $index == count($timeFormats) - 1
                ) {
                    if (2 == count($format)) {
                        return $format[1];
                    }

                    return floor($secs / $format[2]).' '.$format[1];
                }
            }
        }
    }

    public static function formatMemory($memory)
    {
        if ($memory >= 1024 * 1024 * 1024) {
            return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
        }

        if ($memory >= 1024 * 1024) {
            return sprintf('%.1f MiB', $memory / 1024 / 1024);
        }

        if ($memory >= 1024) {
            return sprintf('%d KiB', $memory / 1024);
        }

        return sprintf('%d B', $memory);
    }

    public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string)
    {
        return self::strlen(self::removeDecoration($formatter, $string));
    }

    public static function removeDecoration(OutputFormatterInterface $formatter, $string)
    {
        $isDecorated = $formatter->isDecorated();
        $formatter->setDecorated(false);
        // remove <...> formatting
        $string = $formatter->format($string);
        // remove already formatted characters
        $string = preg_replace("/\033\[[^m]*m/", '', $string);
        $formatter->setDecorated($isDecorated);

        return $string;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Terminal;

/**
 * The ProgressBar provides helpers to display progress output.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Chris Jones <leeked@gmail.com>
 */
final class ProgressBar
{
    private $barWidth = 28;
    private $barChar;
    private $emptyBarChar = '-';
    private $progressChar = '>';
    private $format;
    private $internalFormat;
    private $redrawFreq = 1;
    private $output;
    private $step = 0;
    private $max;
    private $startTime;
    private $stepWidth;
    private $percent = 0.0;
    private $formatLineCount;
    private $messages = array();
    private $overwrite = true;
    private $terminal;
    private $firstRun = true;

    private static $formatters;
    private static $formats;

    /**
     * @param OutputInterface $output An OutputInterface instance
     * @param int             $max    Maximum steps (0 if unknown)
     */
    public function __construct(OutputInterface $output, $max = 0)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $this->output = $output;
        $this->setMaxSteps($max);
        $this->terminal = new Terminal();

        if (!$this->output->isDecorated()) {
            // disable overwrite when output does not support ANSI codes.
            $this->overwrite = false;

            // set a reasonable redraw frequency so output isn't flooded
            $this->setRedrawFrequency($max / 10);
        }

        $this->startTime = time();
    }

    /**
     * Sets a placeholder formatter for a given name.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @param string   $name     The placeholder name (including the delimiter char like %)
     * @param callable $callable A PHP callable
     */
    public static function setPlaceholderFormatterDefinition($name, callable $callable)
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        self::$formatters[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name.
     *
     * @param string $name The placeholder name (including the delimiter char like %)
     *
     * @return callable|null A PHP callable
     */
    public static function getPlaceholderFormatterDefinition($name)
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
    }

    /**
     * Sets a format for a given name.
     *
     * This method also allow you to override an existing format.
     *
     * @param string $name   The format name
     * @param string $format A format string
     */
    public static function setFormatDefinition($name, $format)
    {
        if (!self::$formats) {
            self::$formats = self::initFormats();
        }

        self::$formats[$name] = $format;
    }

    /**
     * Gets the format for a given name.
     *
     * @param string $name The format name
     *
     * @return string|null A format string
     */
    public static function getFormatDefinition($name)
    {
        if (!self::$formats) {
            self::$formats = self::initFormats();
        }

        return isset(self::$formats[$name]) ? self::$formats[$name] : null;
    }

    /**
     * Associates a text with a named placeholder.
     *
     * The text is displayed when the progress bar is rendered but only
     * when the corresponding placeholder is part of the custom format line
     * (by wrapping the name with %).
     *
     * @param string $message The text to associate with the placeholder
     * @param string $name    The name of the placeholder
     */
    public function setMessage($message, $name = 'message')
    {
        $this->messages[$name] = $message;
    }

    public function getMessage($name = 'message')
    {
        return $this->messages[$name];
    }

    /**
     * Gets the progress bar start time.
     *
     * @return int The progress bar start time
     */
    public function getStartTime()
    {
        return $this->startTime;
    }

    /**
     * Gets the progress bar maximal steps.
     *
     * @return int The progress bar max steps
     */
    public function getMaxSteps()
    {
        return $this->max;
    }

    /**
     * Gets the current step position.
     *
     * @return int The progress bar step
     */
    public function getProgress()
    {
        return $this->step;
    }

    /**
     * Gets the progress bar step width.
     *
     * @return int The progress bar step width
     */
    private function getStepWidth()
    {
        return $this->stepWidth;
    }

    /**
     * Gets the current progress bar percent.
     *
     * @return float The current progress bar percent
     */
    public function getProgressPercent()
    {
        return $this->percent;
    }

    /**
     * Sets the progress bar width.
     *
     * @param int $size The progress bar size
     */
    public function setBarWidth($size)
    {
        $this->barWidth = max(1, (int) $size);
    }

    /**
     * Gets the progress bar width.
     *
     * @return int The progress bar size
     */
    public function getBarWidth()
    {
        return $this->barWidth;
    }

    /**
     * Sets the bar character.
     *
     * @param string $char A character
     */
    public function setBarCharacter($char)
    {
        $this->barChar = $char;
    }

    /**
     * Gets the bar character.
     *
     * @return string A character
     */
    public function getBarCharacter()
    {
        if (null === $this->barChar) {
            return $this->max ? '=' : $this->emptyBarChar;
        }

        return $this->barChar;
    }

    /**
     * Sets the empty bar character.
     *
     * @param string $char A character
     */
    public function setEmptyBarCharacter($char)
    {
        $this->emptyBarChar = $char;
    }

    /**
     * Gets the empty bar character.
     *
     * @return string A character
     */
    public function getEmptyBarCharacter()
    {
        return $this->emptyBarChar;
    }

    /**
     * Sets the progress bar character.
     *
     * @param string $char A character
     */
    public function setProgressCharacter($char)
    {
        $this->progressChar = $char;
    }

    /**
     * Gets the progress bar character.
     *
     * @return string A character
     */
    public function getProgressCharacter()
    {
        return $this->progressChar;
    }

    /**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    public function setFormat($format)
    {
        $this->format = null;
        $this->internalFormat = $format;
    }

    /**
     * Sets the redraw frequency.
     *
     * @param int|float $freq The frequency in steps
     */
    public function setRedrawFrequency($freq)
    {
        $this->redrawFreq = max((int) $freq, 1);
    }

    /**
     * Starts the progress output.
     *
     * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
     */
    public function start($max = null)
    {
        $this->startTime = time();
        $this->step = 0;
        $this->percent = 0.0;

        if (null !== $max) {
            $this->setMaxSteps($max);
        }

        $this->display();
    }

    /**
     * Advances the progress output X steps.
     *
     * @param int $step Number of steps to advance
     */
    public function advance($step = 1)
    {
        $this->setProgress($this->step + $step);
    }

    /**
     * Sets whether to overwrite the progressbar, false for new line.
     *
     * @param bool $overwrite
     */
    public function setOverwrite($overwrite)
    {
        $this->overwrite = (bool) $overwrite;
    }

    /**
     * Sets the current progress.
     *
     * @param int $step The current progress
     */
    public function setProgress($step)
    {
        $step = (int) $step;

        if ($this->max && $step > $this->max) {
            $this->max = $step;
        } elseif ($step < 0) {
            $step = 0;
        }

        $prevPeriod = (int) ($this->step / $this->redrawFreq);
        $currPeriod = (int) ($step / $this->redrawFreq);
        $this->step = $step;
        $this->percent = $this->max ? (float) $this->step / $this->max : 0;
        if ($prevPeriod !== $currPeriod || $this->max === $step) {
            $this->display();
        }
    }

    /**
     * Finishes the progress output.
     */
    public function finish()
    {
        if (!$this->max) {
            $this->max = $this->step;
        }

        if ($this->step === $this->max && !$this->overwrite) {
            // prevent double 100% output
            return;
        }

        $this->setProgress($this->max);
    }

    /**
     * Outputs the current progress string.
     */
    public function display()
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite($this->buildLine());
    }

    /**
     * Removes the progress bar from the current line.
     *
     * This is useful if you wish to write some output
     * while a progress bar is running.
     * Call display() to show the progress bar again.
     */
    public function clear()
    {
        if (!$this->overwrite) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite('');
    }

    /**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    private function setRealFormat($format)
    {
        // try to use the _nomax variant if available
        if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
            $this->format = self::getFormatDefinition($format.'_nomax');
        } elseif (null !== self::getFormatDefinition($format)) {
            $this->format = self::getFormatDefinition($format);
        } else {
            $this->format = $format;
        }

        $this->formatLineCount = substr_count($this->format, "\n");
    }

    /**
     * Sets the progress bar maximal steps.
     *
     * @param int $max The progress bar max steps
     */
    private function setMaxSteps($max)
    {
        $this->max = max(0, (int) $max);
        $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4;
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param string $message The message
     */
    private function overwrite($message)
    {
        if ($this->overwrite) {
            if (!$this->firstRun) {
                // Move the cursor to the beginning of the line
                $this->output->write("\x0D");

                // Erase the line
                $this->output->write("\x1B[2K");

                // Erase previous lines
                if ($this->formatLineCount > 0) {
                    $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
                }
            }
        } elseif ($this->step > 0) {
            $this->output->writeln('');
        }

        $this->firstRun = false;

        $this->output->write($message);
    }

    private function determineBestFormat()
    {
        switch ($this->output->getVerbosity()) {
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            case OutputInterface::VERBOSITY_VERBOSE:
                return $this->max ? 'verbose' : 'verbose_nomax';
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
                return $this->max ? 'very_verbose' : 'very_verbose_nomax';
            case OutputInterface::VERBOSITY_DEBUG:
                return $this->max ? 'debug' : 'debug_nomax';
            default:
                return $this->max ? 'normal' : 'normal_nomax';
        }
    }

    private static function initPlaceholderFormatters()
    {
        return array(
            'bar' => function (ProgressBar $bar, OutputInterface $output) {
                $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
                $display = str_repeat($bar->getBarCharacter(), $completeBars);
                if ($completeBars < $bar->getBarWidth()) {
                    $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
                    $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
                }

                return $display;
            },
            'elapsed' => function (ProgressBar $bar) {
                return Helper::formatTime(time() - $bar->getStartTime());
            },
            'remaining' => function (ProgressBar $bar) {
                if (!$bar->getMaxSteps()) {
                    throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
                }

                if (!$bar->getProgress()) {
                    $remaining = 0;
                } else {
                    $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress()));
                }

                return Helper::formatTime($remaining);
            },
            'estimated' => function (ProgressBar $bar) {
                if (!$bar->getMaxSteps()) {
                    throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
                }

                if (!$bar->getProgress()) {
                    $estimated = 0;
                } else {
                    $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps());
                }

                return Helper::formatTime($estimated);
            },
            'memory' => function (ProgressBar $bar) {
                return Helper::formatMemory(memory_get_usage(true));
            },
            'current' => function (ProgressBar $bar) {
                return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
            },
            'max' => function (ProgressBar $bar) {
                return $bar->getMaxSteps();
            },
            'percent' => function (ProgressBar $bar) {
                return floor($bar->getProgressPercent() * 100);
            },
        );
    }

    private static function initFormats()
    {
        return array(
            'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
            'normal_nomax' => ' %current% [%bar%]',

            'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
            'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',

            'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
            'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',

            'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
            'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
        );
    }

    /**
     * @return string
     */
    private function buildLine()
    {
        $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
        $callback = function ($matches) {
            if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
                $text = call_user_func($formatter, $this, $this->output);
            } elseif (isset($this->messages[$matches[1]])) {
                $text = $this->messages[$matches[1]];
            } else {
                return $matches[0];
            }

            if (isset($matches[2])) {
                $text = sprintf('%'.$matches[2], $text);
            }

            return $text;
        };
        $line = preg_replace_callback($regex, $callback, $this->format);

        // gets string length for each sub line with multiline format
        $linesLength = array_map(function ($subLine) {
            return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r"));
        }, explode("\n", $line));

        $linesWidth = max($linesLength);

        $terminalWidth = $this->terminal->getWidth();
        if ($linesWidth <= $terminalWidth) {
            return $line;
        }

        $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);

        return preg_replace_callback($regex, $callback, $this->format);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Question\ChoiceQuestion;

/**
 * The QuestionHelper class provides helpers to interact with the user.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class QuestionHelper extends Helper
{
    private $inputStream;
    private static $shell;
    private static $stty;

    /**
     * Asks a question to the user.
     *
     * @return mixed The user answer
     *
     * @throws RuntimeException If there is no data to read in the input stream
     */
    public function ask(InputInterface $input, OutputInterface $output, Question $question)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        if (!$input->isInteractive()) {
            if ($question instanceof ChoiceQuestion) {
                $choices = $question->getChoices();

                return $choices[$question->getDefault()];
            }

            return $question->getDefault();
        }

        if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
            $this->inputStream = $stream;
        }

        if (!$question->getValidator()) {
            return $this->doAsk($output, $question);
        }

        $interviewer = function () use ($output, $question) {
            return $this->doAsk($output, $question);
        };

        return $this->validateAttempts($interviewer, $output, $question);
    }

    /**
     * Sets the input stream to read from when interacting with the user.
     *
     * This is mainly useful for testing purpose.
     *
     * @deprecated since version 3.2, to be removed in 4.0. Use
     *             StreamableInputInterface::setStream() instead.
     *
     * @param resource $stream The input stream
     *
     * @throws InvalidArgumentException In case the stream is not a resource
     */
    public function setInputStream($stream)
    {
        @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::setStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED);

        if (!is_resource($stream)) {
            throw new InvalidArgumentException('Input stream must be a valid resource.');
        }

        $this->inputStream = $stream;
    }

    /**
     * Returns the helper's input stream.
     *
     * @deprecated since version 3.2, to be removed in 4.0. Use
     *             StreamableInputInterface::getStream() instead.
     *
     * @return resource
     */
    public function getInputStream()
    {
        if (0 === func_num_args() || func_get_arg(0)) {
            @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::getStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED);
        }

        return $this->inputStream;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'question';
    }

    /**
     * Prevents usage of stty.
     */
    public static function disableStty()
    {
        self::$stty = false;
    }

    /**
     * Asks the question to the user.
     *
     * @return bool|mixed|null|string
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function doAsk(OutputInterface $output, Question $question)
    {
        $this->writePrompt($output, $question);

        $inputStream = $this->inputStream ?: STDIN;
        $autocomplete = $question->getAutocompleterValues();

        if (null === $autocomplete || !$this->hasSttyAvailable()) {
            $ret = false;
            if ($question->isHidden()) {
                try {
                    $ret = trim($this->getHiddenResponse($output, $inputStream));
                } catch (RuntimeException $e) {
                    if (!$question->isHiddenFallback()) {
                        throw $e;
                    }
                }
            }

            if (false === $ret) {
                $ret = fgets($inputStream, 4096);
                if (false === $ret) {
                    throw new RuntimeException('Aborted');
                }
                $ret = trim($ret);
            }
        } else {
            $ret = trim($this->autocomplete($output, $question, $inputStream, is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false)));
        }

        $ret = strlen($ret) > 0 ? $ret : $question->getDefault();

        if ($normalizer = $question->getNormalizer()) {
            return $normalizer($ret);
        }

        return $ret;
    }

    /**
     * Outputs the question prompt.
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $message = $question->getQuestion();

        if ($question instanceof ChoiceQuestion) {
            $maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices())));

            $messages = (array) $question->getQuestion();
            foreach ($question->getChoices() as $key => $value) {
                $width = $maxWidth - $this->strlen($key);
                $messages[] = '  [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value;
            }

            $output->writeln($messages);

            $message = $question->getPrompt();
        }

        $output->write($message);
    }

    /**
     * Outputs an error message.
     */
    protected function writeError(OutputInterface $output, \Exception $error)
    {
        if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
            $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
        } else {
            $message = '<error>'.$error->getMessage().'</error>';
        }

        $output->writeln($message);
    }

    /**
     * Autocompletes a question.
     *
     * @param OutputInterface $output
     * @param Question        $question
     * @param resource        $inputStream
     * @param array           $autocomplete
     *
     * @return string
     */
    private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete)
    {
        $ret = '';

        $i = 0;
        $ofs = -1;
        $matches = $autocomplete;
        $numMatches = count($matches);

        $sttyMode = shell_exec('stty -g');

        // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
        shell_exec('stty -icanon -echo');

        // Add highlighted text style
        $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));

        // Read a keypress
        while (!feof($inputStream)) {
            $c = fread($inputStream, 1);

            // Backspace Character
            if ("\177" === $c) {
                if (0 === $numMatches && 0 !== $i) {
                    --$i;
                    // Move cursor backwards
                    $output->write("\033[1D");
                }

                if (0 === $i) {
                    $ofs = -1;
                    $matches = $autocomplete;
                    $numMatches = count($matches);
                } else {
                    $numMatches = 0;
                }

                // Pop the last character off the end of our string
                $ret = substr($ret, 0, $i);
            } elseif ("\033" === $c) {
                // Did we read an escape sequence?
                $c .= fread($inputStream, 2);

                // A = Up Arrow. B = Down Arrow
                if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
                    if ('A' === $c[2] && -1 === $ofs) {
                        $ofs = 0;
                    }

                    if (0 === $numMatches) {
                        continue;
                    }

                    $ofs += ('A' === $c[2]) ? -1 : 1;
                    $ofs = ($numMatches + $ofs) % $numMatches;
                }
            } elseif (ord($c) < 32) {
                if ("\t" === $c || "\n" === $c) {
                    if ($numMatches > 0 && -1 !== $ofs) {
                        $ret = $matches[$ofs];
                        // Echo out remaining chars for current match
                        $output->write(substr($ret, $i));
                        $i = strlen($ret);
                    }

                    if ("\n" === $c) {
                        $output->write($c);
                        break;
                    }

                    $numMatches = 0;
                }

                continue;
            } else {
                $output->write($c);
                $ret .= $c;
                ++$i;

                $numMatches = 0;
                $ofs = 0;

                foreach ($autocomplete as $value) {
                    // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                    if (0 === strpos($value, $ret) && $i !== strlen($value)) {
                        $matches[$numMatches++] = $value;
                    }
                }
            }

            // Erase characters from cursor to end of line
            $output->write("\033[K");

            if ($numMatches > 0 && -1 !== $ofs) {
                // Save cursor position
                $output->write("\0337");
                // Write highlighted text
                $output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).'</hl>');
                // Restore cursor position
                $output->write("\0338");
            }
        }

        // Reset stty so it behaves normally again
        shell_exec(sprintf('stty %s', $sttyMode));

        return $ret;
    }

    /**
     * Gets a hidden response from user.
     *
     * @param OutputInterface $output      An Output instance
     * @param resource        $inputStream The handler resource
     *
     * @return string The answer
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function getHiddenResponse(OutputInterface $output, $inputStream)
    {
        if ('\\' === DIRECTORY_SEPARATOR) {
            $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';

            // handle code running from a phar
            if ('phar:' === substr(__FILE__, 0, 5)) {
                $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
                copy($exe, $tmpExe);
                $exe = $tmpExe;
            }

            $value = rtrim(shell_exec($exe));
            $output->writeln('');

            if (isset($tmpExe)) {
                unlink($tmpExe);
            }

            return $value;
        }

        if ($this->hasSttyAvailable()) {
            $sttyMode = shell_exec('stty -g');

            shell_exec('stty -echo');
            $value = fgets($inputStream, 4096);
            shell_exec(sprintf('stty %s', $sttyMode));

            if (false === $value) {
                throw new RuntimeException('Aborted');
            }

            $value = trim($value);
            $output->writeln('');

            return $value;
        }

        if (false !== $shell = $this->getShell()) {
            $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword';
            $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
            $value = rtrim(shell_exec($command));
            $output->writeln('');

            return $value;
        }

        throw new RuntimeException('Unable to hide the response.');
    }

    /**
     * Validates an attempt.
     *
     * @param callable        $interviewer A callable that will ask for a question and return the result
     * @param OutputInterface $output      An Output instance
     * @param Question        $question    A Question instance
     *
     * @return mixed The validated response
     *
     * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
     */
    private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
    {
        $error = null;
        $attempts = $question->getMaxAttempts();
        while (null === $attempts || $attempts--) {
            if (null !== $error) {
                $this->writeError($output, $error);
            }

            try {
                return call_user_func($question->getValidator(), $interviewer());
            } catch (RuntimeException $e) {
                throw $e;
            } catch (\Exception $error) {
            }
        }

        throw $error;
    }

    /**
     * Returns a valid unix shell.
     *
     * @return string|bool The valid shell name, false in case no valid shell is found
     */
    private function getShell()
    {
        if (null !== self::$shell) {
            return self::$shell;
        }

        self::$shell = false;

        if (file_exists('/usr/bin/env')) {
            // handle other OSs with bash/zsh/ksh/csh if available to hide the answer
            $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
            foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
                if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
                    self::$shell = $sh;
                    break;
                }
            }
        }

        return self::$shell;
    }

    /**
     * Returns whether Stty is available or not.
     *
     * @return bool
     */
    private function hasSttyAvailable()
    {
        if (null !== self::$stty) {
            return self::$stty;
        }

        exec('stty 2>&1', $output, $exitcode);

        return self::$stty = 0 === $exitcode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * This class adds helper method to describe objects in various formats.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class DescriptorHelper extends Helper
{
    /**
     * @var DescriptorInterface[]
     */
    private $descriptors = array();

    public function __construct()
    {
        $this
            ->register('txt', new TextDescriptor())
            ->register('xml', new XmlDescriptor())
            ->register('json', new JsonDescriptor())
            ->register('md', new MarkdownDescriptor())
        ;
    }

    /**
     * Describes an object if supported.
     *
     * Available options are:
     * * format: string, the output format name
     * * raw_text: boolean, sets output type as raw
     *
     * @param OutputInterface $output
     * @param object          $object
     * @param array           $options
     *
     * @throws InvalidArgumentException when the given format is not supported
     */
    public function describe(OutputInterface $output, $object, array $options = array())
    {
        $options = array_merge(array(
            'raw_text' => false,
            'format' => 'txt',
        ), $options);

        if (!isset($this->descriptors[$options['format']])) {
            throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
        }

        $descriptor = $this->descriptors[$options['format']];
        $descriptor->describe($output, $object, $options);
    }

    /**
     * Registers a descriptor.
     *
     * @param string              $format
     * @param DescriptorInterface $descriptor
     *
     * @return $this
     */
    public function register($format, DescriptorInterface $descriptor)
    {
        $this->descriptors[$format] = $descriptor;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'descriptor';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * The Formatter class provides helpers to format messages.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FormatterHelper extends Helper
{
    /**
     * Formats a message within a section.
     *
     * @param string $section The section name
     * @param string $message The message
     * @param string $style   The style to apply to the section
     *
     * @return string The format section
     */
    public function formatSection($section, $message, $style = 'info')
    {
        return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
    }

    /**
     * Formats a message as a block of text.
     *
     * @param string|array $messages The message to write in the block
     * @param string       $style    The style to apply to the whole block
     * @param bool         $large    Whether to return a large block
     *
     * @return string The formatter message
     */
    public function formatBlock($messages, $style, $large = false)
    {
        if (!is_array($messages)) {
            $messages = array($messages);
        }

        $len = 0;
        $lines = array();
        foreach ($messages as $message) {
            $message = OutputFormatter::escape($message);
            $lines[] = sprintf($large ? '  %s  ' : ' %s ', $message);
            $len = max($this->strlen($message) + ($large ? 4 : 2), $len);
        }

        $messages = $large ? array(str_repeat(' ', $len)) : array();
        for ($i = 0; isset($lines[$i]); ++$i) {
            $messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i]));
        }
        if ($large) {
            $messages[] = str_repeat(' ', $len);
        }

        for ($i = 0; isset($messages[$i]); ++$i) {
            $messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
        }

        return implode("\n", $messages);
    }

    /**
     * Truncates a message to the given length.
     *
     * @param string $message
     * @param int    $length
     * @param string $suffix
     *
     * @return string
     */
    public function truncate($message, $length, $suffix = '...')
    {
        $computedLength = $length - $this->strlen($suffix);

        if ($computedLength > $this->strlen($message)) {
            return $message;
        }

        if (false === $encoding = mb_detect_encoding($message, null, true)) {
            return substr($message, 0, $length).$suffix;
        }

        return mb_substr($message, 0, $length, $encoding).$suffix;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * A simple command loader using factories to instantiate commands lazily.
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class FactoryCommandLoader implements CommandLoaderInterface
{
    private $factories;

    /**
     * @param callable[] $factories Indexed by command names
     */
    public function __construct(array $factories)
    {
        $this->factories = $factories;
    }

    /**
     * {@inheritdoc}
     */
    public function has($name)
    {
        return isset($this->factories[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function get($name)
    {
        if (!isset($this->factories[$name])) {
            throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
        }

        $factory = $this->factories[$name];

        return $factory();
    }

    /**
     * {@inheritdoc}
     */
    public function getNames()
    {
        return array_keys($this->factories);
    }
}
<?php

namespace Symfony\Component\Console\CommandLoader;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
interface CommandLoaderInterface
{
    /**
     * Loads a command.
     *
     * @param string $name
     *
     * @return Command
     *
     * @throws CommandNotFoundException
     */
    public function get($name);

    /**
     * Checks if a command exists.
     *
     * @param string $name
     *
     * @return bool
     */
    public function has($name);

    /**
     * @return string[] All registered command names
     */
    public function getNames();
}
<?php

namespace Symfony\Component\Console\CommandLoader;

use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * Loads commands from a PSR-11 container.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class ContainerCommandLoader implements CommandLoaderInterface
{
    private $container;
    private $commandMap;

    /**
     * @param ContainerInterface $container  A container from which to load command services
     * @param array              $commandMap An array with command names as keys and service ids as values
     */
    public function __construct(ContainerInterface $container, array $commandMap)
    {
        $this->container = $container;
        $this->commandMap = $commandMap;
    }

    /**
     * {@inheritdoc}
     */
    public function get($name)
    {
        if (!$this->has($name)) {
            throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
        }

        return $this->container->get($this->commandMap[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function has($name)
    {
        return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function getNames()
    {
        return array_keys($this->commandMap);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * Base class for output classes.
 *
 * There are five levels of verbosity:
 *
 *  * normal: no option passed (normal output)
 *  * verbose: -v (more output)
 *  * very verbose: -vv (highly extended output)
 *  * debug: -vvv (all debug output)
 *  * quiet: -q (no output)
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Output implements OutputInterface
{
    private $verbosity;
    private $formatter;

    /**
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool                          $decorated Whether to decorate messages
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     */
    public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null)
    {
        $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
        $this->formatter = $formatter ?: new OutputFormatter();
        $this->formatter->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        $this->formatter = $formatter;
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        return $this->formatter;
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated($decorated)
    {
        $this->formatter->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return $this->formatter->isDecorated();
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity($level)
    {
        $this->verbosity = (int) $level;
    }

    /**
     * {@inheritdoc}
     */
    public function getVerbosity()
    {
        return $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isQuiet()
    {
        return self::VERBOSITY_QUIET === $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isVerbose()
    {
        return self::VERBOSITY_VERBOSE <= $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isVeryVerbose()
    {
        return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isDebug()
    {
        return self::VERBOSITY_DEBUG <= $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, $options = self::OUTPUT_NORMAL)
    {
        $this->write($messages, true, $options);
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL)
    {
        $messages = (array) $messages;

        $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN;
        $type = $types & $options ?: self::OUTPUT_NORMAL;

        $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG;
        $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL;

        if ($verbosity > $this->getVerbosity()) {
            return;
        }

        foreach ($messages as $message) {
            switch ($type) {
                case OutputInterface::OUTPUT_NORMAL:
                    $message = $this->formatter->format($message);
                    break;
                case OutputInterface::OUTPUT_RAW:
                    break;
                case OutputInterface::OUTPUT_PLAIN:
                    $message = strip_tags($this->formatter->format($message));
                    break;
            }

            $this->doWrite($message, $newline);
        }
    }

    /**
     * Writes a message to the output.
     *
     * @param string $message A message to write to the output
     * @param bool   $newline Whether to add a newline or not
     */
    abstract protected function doWrite($message, $newline);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * NullOutput suppresses all output.
 *
 *     $output = new NullOutput();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Tobias Schultze <http://tobion.de>
 */
class NullOutput implements OutputInterface
{
    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        // to comply with the interface we must return a OutputFormatterInterface
        return new OutputFormatter();
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated($decorated)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity($level)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function getVerbosity()
    {
        return self::VERBOSITY_QUIET;
    }

    /**
     * {@inheritdoc}
     */
    public function isQuiet()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function isVerbose()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function isVeryVerbose()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function isDebug()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, $options = self::OUTPUT_NORMAL)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL)
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
 *
 * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
 *
 *     $output = new ConsoleOutput();
 *
 * This is equivalent to:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *     $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
    private $stderr;

    /**
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool|null                     $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     */
    public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null)
    {
        parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);

        $actualDecorated = $this->isDecorated();
        $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());

        if (null === $decorated) {
            $this->setDecorated($actualDecorated && $this->stderr->isDecorated());
        }
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated($decorated)
    {
        parent::setDecorated($decorated);
        $this->stderr->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        parent::setFormatter($formatter);
        $this->stderr->setFormatter($formatter);
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity($level)
    {
        parent::setVerbosity($level);
        $this->stderr->setVerbosity($level);
    }

    /**
     * {@inheritdoc}
     */
    public function getErrorOutput()
    {
        return $this->stderr;
    }

    /**
     * {@inheritdoc}
     */
    public function setErrorOutput(OutputInterface $error)
    {
        $this->stderr = $error;
    }

    /**
     * Returns true if current environment supports writing console output to
     * STDOUT.
     *
     * @return bool
     */
    protected function hasStdoutSupport()
    {
        return false === $this->isRunningOS400();
    }

    /**
     * Returns true if current environment supports writing console output to
     * STDERR.
     *
     * @return bool
     */
    protected function hasStderrSupport()
    {
        return false === $this->isRunningOS400();
    }

    /**
     * Checks if current executing environment is IBM iSeries (OS400), which
     * doesn't properly convert character-encodings between ASCII to EBCDIC.
     *
     * @return bool
     */
    private function isRunningOS400()
    {
        $checks = array(
            function_exists('php_uname') ? php_uname('s') : '',
            getenv('OSTYPE'),
            PHP_OS,
        );

        return false !== stripos(implode(';', $checks), 'OS400');
    }

    /**
     * @return resource
     */
    private function openOutputStream()
    {
        if (!$this->hasStdoutSupport()) {
            return fopen('php://output', 'w');
        }

        return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
    }

    /**
     * @return resource
     */
    private function openErrorStream()
    {
        return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * StreamOutput writes the output to a given stream.
 *
 * Usage:
 *
 * $output = new StreamOutput(fopen('php://stdout', 'w'));
 *
 * As `StreamOutput` can use any stream, you can also use a file:
 *
 * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StreamOutput extends Output
{
    private $stream;

    /**
     * @param resource                      $stream    A stream resource
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool|null                     $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     *
     * @throws InvalidArgumentException When first argument is not a real stream
     */
    public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null)
    {
        if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) {
            throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
        }

        $this->stream = $stream;

        if (null === $decorated) {
            $decorated = $this->hasColorSupport();
        }

        parent::__construct($verbosity, $decorated, $formatter);
    }

    /**
     * Gets the stream attached to this StreamOutput instance.
     *
     * @return resource A stream resource
     */
    public function getStream()
    {
        return $this->stream;
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite($message, $newline)
    {
        if (false === @fwrite($this->stream, $message) || ($newline && (false === @fwrite($this->stream, PHP_EOL)))) {
            // should never happen
            throw new RuntimeException('Unable to write output.');
        }

        fflush($this->stream);
    }

    /**
     * Returns true if the stream supports colorization.
     *
     * Colorization is disabled if not supported by the stream:
     *
     *  -  Windows != 10.0.10586 without Ansicon, ConEmu or Mintty
     *  -  non tty consoles
     *
     * @return bool true if the stream supports colorization, false otherwise
     */
    protected function hasColorSupport()
    {
        if (DIRECTORY_SEPARATOR === '\\') {
            return
                function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support($this->stream)
                || '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD
                || false !== getenv('ANSICON')
                || 'ON' === getenv('ConEmuANSI')
                || 'xterm' === getenv('TERM');
        }

        return function_exists('posix_isatty') && @posix_isatty($this->stream);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class BufferedOutput extends Output
{
    private $buffer = '';

    /**
     * Empties buffer and returns its content.
     *
     * @return string
     */
    public function fetch()
    {
        $content = $this->buffer;
        $this->buffer = '';

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite($message, $newline)
    {
        $this->buffer .= $message;

        if ($newline) {
            $this->buffer .= PHP_EOL;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * OutputInterface is the interface implemented by all Output classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface OutputInterface
{
    const VERBOSITY_QUIET = 16;
    const VERBOSITY_NORMAL = 32;
    const VERBOSITY_VERBOSE = 64;
    const VERBOSITY_VERY_VERBOSE = 128;
    const VERBOSITY_DEBUG = 256;

    const OUTPUT_NORMAL = 1;
    const OUTPUT_RAW = 2;
    const OUTPUT_PLAIN = 4;

    /**
     * Writes a message to the output.
     *
     * @param string|array $messages The message as an array of lines or a single string
     * @param bool         $newline  Whether to add a newline
     * @param int          $options  A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
     */
    public function write($messages, $newline = false, $options = 0);

    /**
     * Writes a message to the output and adds a newline at the end.
     *
     * @param string|array $messages The message as an array of lines of a single string
     * @param int          $options  A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
     */
    public function writeln($messages, $options = 0);

    /**
     * Sets the verbosity of the output.
     *
     * @param int $level The level of verbosity (one of the VERBOSITY constants)
     */
    public function setVerbosity($level);

    /**
     * Gets the current verbosity of the output.
     *
     * @return int The current level of verbosity (one of the VERBOSITY constants)
     */
    public function getVerbosity();

    /**
     * Returns whether verbosity is quiet (-q).
     *
     * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
     */
    public function isQuiet();

    /**
     * Returns whether verbosity is verbose (-v).
     *
     * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
     */
    public function isVerbose();

    /**
     * Returns whether verbosity is very verbose (-vv).
     *
     * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
     */
    public function isVeryVerbose();

    /**
     * Returns whether verbosity is debug (-vvv).
     *
     * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
     */
    public function isDebug();

    /**
     * Sets the decorated flag.
     *
     * @param bool $decorated Whether to decorate the messages
     */
    public function setDecorated($decorated);

    /**
     * Gets the decorated flag.
     *
     * @return bool true if the output will decorate messages, false otherwise
     */
    public function isDecorated();

    public function setFormatter(OutputFormatterInterface $formatter);

    /**
     * Returns current output formatter instance.
     *
     * @return OutputFormatterInterface
     */
    public function getFormatter();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
 * This adds information about stderr output stream.
 *
 * @author Dariusz Górecki <darek.krk@gmail.com>
 */
interface ConsoleOutputInterface extends OutputInterface
{
    /**
     * Gets the OutputInterface for errors.
     *
     * @return OutputInterface
     */
    public function getErrorOutput();

    public function setErrorOutput(OutputInterface $error);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

/**
 * Allows to do things before the command is executed, like skipping the command or changing the input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleCommandEvent extends ConsoleEvent
{
    /**
     * The return code for skipped commands, this will also be passed into the terminate event.
     */
    const RETURN_CODE_DISABLED = 113;

    /**
     * Indicates if the command should be run or skipped.
     */
    private $commandShouldRun = true;

    /**
     * Disables the command, so it won't be run.
     *
     * @return bool
     */
    public function disableCommand()
    {
        return $this->commandShouldRun = false;
    }

    /**
     * Enables the command.
     *
     * @return bool
     */
    public function enableCommand()
    {
        return $this->commandShouldRun = true;
    }

    /**
     * Returns true if the command is runnable, false otherwise.
     *
     * @return bool
     */
    public function commandShouldRun()
    {
        return $this->commandShouldRun;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to handle throwables thrown while running a command.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class ConsoleErrorEvent extends ConsoleEvent
{
    private $error;
    private $exitCode;

    public function __construct(InputInterface $input, OutputInterface $output, $error, Command $command = null)
    {
        parent::__construct($command, $input, $output);

        $this->setError($error);
    }

    /**
     * Returns the thrown error/exception.
     *
     * @return \Throwable
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * Replaces the thrown error/exception.
     *
     * @param \Throwable $error
     */
    public function setError($error)
    {
        if (!$error instanceof \Throwable && !$error instanceof \Exception) {
            throw new InvalidArgumentException(sprintf('The error passed to ConsoleErrorEvent must be an instance of \Throwable or \Exception, "%s" was passed instead.', is_object($error) ? get_class($error) : gettype($error)));
        }

        $this->error = $error;
    }

    /**
     * Sets the exit code.
     *
     * @param int $exitCode The command exit code
     */
    public function setExitCode($exitCode)
    {
        $this->exitCode = (int) $exitCode;

        $r = new \ReflectionProperty($this->error, 'code');
        $r->setAccessible(true);
        $r->setValue($this->error, $this->exitCode);
    }

    /**
     * Gets the exit code.
     *
     * @return int The command exit code
     */
    public function getExitCode()
    {
        return null !== $this->exitCode ? $this->exitCode : (is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\Event;

/**
 * Allows to inspect input and output of a command.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
class ConsoleEvent extends Event
{
    protected $command;

    private $input;
    private $output;

    public function __construct(Command $command = null, InputInterface $input, OutputInterface $output)
    {
        $this->command = $command;
        $this->input = $input;
        $this->output = $output;
    }

    /**
     * Gets the command that is executed.
     *
     * @return Command|null A Command instance
     */
    public function getCommand()
    {
        return $this->command;
    }

    /**
     * Gets the input instance.
     *
     * @return InputInterface An InputInterface instance
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Gets the output instance.
     *
     * @return OutputInterface An OutputInterface instance
     */
    public function getOutput()
    {
        return $this->output;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

@trigger_error(sprintf('The "%s" class is deprecated since Symfony 3.3 and will be removed in 4.0. Use the ConsoleErrorEvent instead.', ConsoleExceptionEvent::class), E_USER_DEPRECATED);

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to handle exception thrown in a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @deprecated since version 3.3, to be removed in 4.0. Use ConsoleErrorEvent instead.
 */
class ConsoleExceptionEvent extends ConsoleEvent
{
    private $exception;
    private $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode)
    {
        parent::__construct($command, $input, $output);

        $this->setException($exception);
        $this->exitCode = (int) $exitCode;
    }

    /**
     * Returns the thrown exception.
     *
     * @return \Exception The thrown exception
     */
    public function getException()
    {
        return $this->exception;
    }

    /**
     * Replaces the thrown exception.
     *
     * This exception will be thrown if no response is set in the event.
     *
     * @param \Exception $exception The thrown exception
     */
    public function setException(\Exception $exception)
    {
        $this->exception = $exception;
    }

    /**
     * Gets the exit code.
     *
     * @return int The command exit code
     */
    public function getExitCode()
    {
        return $this->exitCode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to manipulate the exit code of a command after its execution.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
class ConsoleTerminateEvent extends ConsoleEvent
{
    /**
     * The exit code of the command.
     *
     * @var int
     */
    private $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode)
    {
        parent::__construct($command, $input, $output);

        $this->setExitCode($exitCode);
    }

    /**
     * Sets the exit code.
     *
     * @param int $exitCode The command exit code
     */
    public function setExitCode($exitCode)
    {
        $this->exitCode = (int) $exitCode;
    }

    /**
     * Gets the exit code.
     *
     * @return int The command exit code
     */
    public function getExitCode()
    {
        return $this->exitCode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

class Terminal
{
    private static $width;
    private static $height;

    /**
     * Gets the terminal width.
     *
     * @return int
     */
    public function getWidth()
    {
        $width = getenv('COLUMNS');
        if (false !== $width) {
            return (int) trim($width);
        }

        if (null === self::$width) {
            self::initDimensions();
        }

        return self::$width ?: 80;
    }

    /**
     * Gets the terminal height.
     *
     * @return int
     */
    public function getHeight()
    {
        $height = getenv('LINES');
        if (false !== $height) {
            return (int) trim($height);
        }

        if (null === self::$height) {
            self::initDimensions();
        }

        return self::$height ?: 50;
    }

    private static function initDimensions()
    {
        if ('\\' === DIRECTORY_SEPARATOR) {
            if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) {
                // extract [w, H] from "wxh (WxH)"
                // or [w, h] from "wxh"
                self::$width = (int) $matches[1];
                self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
            } elseif (null !== $dimensions = self::getConsoleMode()) {
                // extract [w, h] from "wxh"
                self::$width = (int) $dimensions[0];
                self::$height = (int) $dimensions[1];
            }
        } elseif ($sttyString = self::getSttyColumns()) {
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
                // extract [w, h] from "rows h; columns w;"
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
                // extract [w, h] from "; h rows; w columns"
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            }
        }
    }

    /**
     * Runs and parses mode CON if it's available, suppressing any error output.
     *
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
     */
    private static function getConsoleMode()
    {
        if (!function_exists('proc_open')) {
            return;
        }

        $descriptorspec = array(
            1 => array('pipe', 'w'),
            2 => array('pipe', 'w'),
        );
        $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
        if (is_resource($process)) {
            $info = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);

            if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
                return array((int) $matches[2], (int) $matches[1]);
            }
        }
    }

    /**
     * Runs and parses stty -a if it's available, suppressing any error output.
     *
     * @return string|null
     */
    private static function getSttyColumns()
    {
        if (!function_exists('proc_open')) {
            return;
        }

        $descriptorspec = array(
            1 => array('pipe', 'w'),
            2 => array('pipe', 'w'),
        );

        $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
        if (is_resource($process)) {
            $info = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);

            return $info;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Undefined Function Exception.
 *
 * @author Konstanton Myakshin <koc-dp@yandex.ru>
 */
class UndefinedFunctionException extends FatalErrorException
{
    public function __construct($message, \ErrorException $previous)
    {
        parent::__construct(
            $message,
            $previous->getCode(),
            $previous->getSeverity(),
            $previous->getFile(),
            $previous->getLine(),
            $previous->getPrevious()
        );
        $this->setTrace($previous->getTrace());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Fatal Error Exception.
 *
 * @author Konstanton Myakshin <koc-dp@yandex.ru>
 */
class FatalErrorException extends \ErrorException
{
    public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null)
    {
        parent::__construct($message, $code, $severity, $filename, $lineno);

        if (null !== $trace) {
            if (!$traceArgs) {
                foreach ($trace as &$frame) {
                    unset($frame['args'], $frame['this'], $frame);
                }
            }

            $this->setTrace($trace);
        } elseif (null !== $traceOffset) {
            if (function_exists('xdebug_get_function_stack')) {
                $trace = xdebug_get_function_stack();
                if (0 < $traceOffset) {
                    array_splice($trace, -$traceOffset);
                }

                foreach ($trace as &$frame) {
                    if (!isset($frame['type'])) {
                        // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
                        if (isset($frame['class'])) {
                            $frame['type'] = '::';
                        }
                    } elseif ('dynamic' === $frame['type']) {
                        $frame['type'] = '->';
                    } elseif ('static' === $frame['type']) {
                        $frame['type'] = '::';
                    }

                    // XDebug also has a different name for the parameters array
                    if (!$traceArgs) {
                        unset($frame['params'], $frame['args']);
                    } elseif (isset($frame['params']) && !isset($frame['args'])) {
                        $frame['args'] = $frame['params'];
                        unset($frame['params']);
                    }
                }

                unset($frame);
                $trace = array_reverse($trace);
            } elseif (function_exists('symfony_debug_backtrace')) {
                $trace = symfony_debug_backtrace();
                if (0 < $traceOffset) {
                    array_splice($trace, 0, $traceOffset);
                }
            } else {
                $trace = array();
            }

            $this->setTrace($trace);
        }
    }

    protected function setTrace($trace)
    {
        $traceReflector = new \ReflectionProperty('Exception', 'trace');
        $traceReflector->setAccessible(true);
        $traceReflector->setValue($this, $trace);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Class (or Trait or Interface) Not Found Exception.
 *
 * @author Konstanton Myakshin <koc-dp@yandex.ru>
 */
class ClassNotFoundException extends FatalErrorException
{
    public function __construct($message, \ErrorException $previous)
    {
        parent::__construct(
            $message,
            $previous->getCode(),
            $previous->getSeverity(),
            $previous->getFile(),
            $previous->getLine(),
            $previous->getPrevious()
        );
        $this->setTrace($previous->getTrace());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Fatal Throwable Error.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class FatalThrowableError extends FatalErrorException
{
    public function __construct(\Throwable $e)
    {
        if ($e instanceof \ParseError) {
            $message = 'Parse error: '.$e->getMessage();
            $severity = E_PARSE;
        } elseif ($e instanceof \TypeError) {
            $message = 'Type error: '.$e->getMessage();
            $severity = E_RECOVERABLE_ERROR;
        } else {
            $message = $e->getMessage();
            $severity = E_ERROR;
        }

        \ErrorException::__construct(
            $message,
            $e->getCode(),
            $severity,
            $e->getFile(),
            $e->getLine(),
            $e->getPrevious()
        );

        $this->setTrace($e->getTrace());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Error Exception with Variable Context.
 *
 * @author Christian Sciberras <uuf6429@gmail.com>
 *
 * @deprecated since version 3.3. Instead, \ErrorException will be used directly in 4.0.
 */
class ContextErrorException extends \ErrorException
{
    private $context = array();

    public function __construct($message, $code, $severity, $filename, $lineno, $context = array())
    {
        parent::__construct($message, $code, $severity, $filename, $lineno);
        $this->context = $context;
    }

    /**
     * @return array Array of variables that existed when the exception occurred
     */
    public function getContext()
    {
        @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED);

        return $this->context;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Undefined Method Exception.
 *
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class UndefinedMethodException extends FatalErrorException
{
    public function __construct($message, \ErrorException $previous)
    {
        parent::__construct(
            $message,
            $previous->getCode(),
            $previous->getSeverity(),
            $previous->getFile(),
            $previous->getLine(),
            $previous->getPrevious()
        );
        $this->setTrace($previous->getTrace());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

/**
 * FlattenException wraps a PHP Exception to be able to serialize it.
 *
 * Basically, this class removes all objects from the trace.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FlattenException
{
    private $message;
    private $code;
    private $previous;
    private $trace;
    private $class;
    private $statusCode;
    private $headers;
    private $file;
    private $line;

    public static function create(\Exception $exception, $statusCode = null, array $headers = array())
    {
        $e = new static();
        $e->setMessage($exception->getMessage());
        $e->setCode($exception->getCode());

        if ($exception instanceof HttpExceptionInterface) {
            $statusCode = $exception->getStatusCode();
            $headers = array_merge($headers, $exception->getHeaders());
        } elseif ($exception instanceof RequestExceptionInterface) {
            $statusCode = 400;
        }

        if (null === $statusCode) {
            $statusCode = 500;
        }

        $e->setStatusCode($statusCode);
        $e->setHeaders($headers);
        $e->setTraceFromException($exception);
        $e->setClass(get_class($exception));
        $e->setFile($exception->getFile());
        $e->setLine($exception->getLine());

        $previous = $exception->getPrevious();

        if ($previous instanceof \Exception) {
            $e->setPrevious(static::create($previous));
        } elseif ($previous instanceof \Throwable) {
            $e->setPrevious(static::create(new FatalThrowableError($previous)));
        }

        return $e;
    }

    public function toArray()
    {
        $exceptions = array();
        foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) {
            $exceptions[] = array(
                'message' => $exception->getMessage(),
                'class' => $exception->getClass(),
                'trace' => $exception->getTrace(),
            );
        }

        return $exceptions;
    }

    public function getStatusCode()
    {
        return $this->statusCode;
    }

    public function setStatusCode($code)
    {
        $this->statusCode = $code;
    }

    public function getHeaders()
    {
        return $this->headers;
    }

    public function setHeaders(array $headers)
    {
        $this->headers = $headers;
    }

    public function getClass()
    {
        return $this->class;
    }

    public function setClass($class)
    {
        $this->class = $class;
    }

    public function getFile()
    {
        return $this->file;
    }

    public function setFile($file)
    {
        $this->file = $file;
    }

    public function getLine()
    {
        return $this->line;
    }

    public function setLine($line)
    {
        $this->line = $line;
    }

    public function getMessage()
    {
        return $this->message;
    }

    public function setMessage($message)
    {
        $this->message = $message;
    }

    public function getCode()
    {
        return $this->code;
    }

    public function setCode($code)
    {
        $this->code = $code;
    }

    public function getPrevious()
    {
        return $this->previous;
    }

    public function setPrevious(FlattenException $previous)
    {
        $this->previous = $previous;
    }

    public function getAllPrevious()
    {
        $exceptions = array();
        $e = $this;
        while ($e = $e->getPrevious()) {
            $exceptions[] = $e;
        }

        return $exceptions;
    }

    public function getTrace()
    {
        return $this->trace;
    }

    public function setTraceFromException(\Exception $exception)
    {
        $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
    }

    public function setTrace($trace, $file, $line)
    {
        $this->trace = array();
        $this->trace[] = array(
            'namespace' => '',
            'short_class' => '',
            'class' => '',
            'type' => '',
            'function' => '',
            'file' => $file,
            'line' => $line,
            'args' => array(),
        );
        foreach ($trace as $entry) {
            $class = '';
            $namespace = '';
            if (isset($entry['class'])) {
                $parts = explode('\\', $entry['class']);
                $class = array_pop($parts);
                $namespace = implode('\\', $parts);
            }

            $this->trace[] = array(
                'namespace' => $namespace,
                'short_class' => $class,
                'class' => isset($entry['class']) ? $entry['class'] : '',
                'type' => isset($entry['type']) ? $entry['type'] : '',
                'function' => isset($entry['function']) ? $entry['function'] : null,
                'file' => isset($entry['file']) ? $entry['file'] : null,
                'line' => isset($entry['line']) ? $entry['line'] : null,
                'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(),
            );
        }
    }

    private function flattenArgs($args, $level = 0, &$count = 0)
    {
        $result = array();
        foreach ($args as $key => $value) {
            if (++$count > 1e4) {
                return array('array', '*SKIPPED over 10000 entries*');
            }
            if ($value instanceof \__PHP_Incomplete_Class) {
                // is_object() returns false on PHP<=7.1
                $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value));
            } elseif (is_object($value)) {
                $result[$key] = array('object', get_class($value));
            } elseif (is_array($value)) {
                if ($level > 10) {
                    $result[$key] = array('array', '*DEEP NESTED ARRAY*');
                } else {
                    $result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count));
                }
            } elseif (null === $value) {
                $result[$key] = array('null', null);
            } elseif (is_bool($value)) {
                $result[$key] = array('boolean', $value);
            } elseif (is_int($value)) {
                $result[$key] = array('integer', $value);
            } elseif (is_float($value)) {
                $result[$key] = array('float', $value);
            } elseif (is_resource($value)) {
                $result[$key] = array('resource', get_resource_type($value));
            } else {
                $result[$key] = array('string', (string) $value);
            }
        }

        return $result;
    }

    private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
    {
        $array = new \ArrayObject($value);

        return $array['__PHP_Incomplete_Class_Name'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Out of memory exception.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class OutOfMemoryException extends FatalErrorException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Data Object that represents a Silenced Error.
 *
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class SilencedErrorContext implements \JsonSerializable
{
    public $count = 1;

    private $severity;
    private $file;
    private $line;
    private $trace;

    public function __construct($severity, $file, $line, array $trace = array(), $count = 1)
    {
        $this->severity = $severity;
        $this->file = $file;
        $this->line = $line;
        $this->trace = $trace;
        $this->count = $count;
    }

    public function getSeverity()
    {
        return $this->severity;
    }

    public function getFile()
    {
        return $this->file;
    }

    public function getLine()
    {
        return $this->line;
    }

    public function getTrace()
    {
        return $this->trace;
    }

    public function JsonSerialize()
    {
        return array(
            'severity' => $this->severity,
            'file' => $this->file,
            'line' => $this->line,
            'trace' => $this->trace,
            'count' => $this->count,
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Tests;

use Symfony\Component\Debug\ExceptionHandler;

class MockExceptionHandler extends ExceptionHandler
{
    public $e;

    public function handle(\Exception $e)
    {
        $this->e = $e;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Tests\Exception;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
use Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException;
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;

class FlattenExceptionTest extends TestCase
{
    public function testStatusCode()
    {
        $flattened = FlattenException::create(new \RuntimeException(), 403);
        $this->assertEquals('403', $flattened->getStatusCode());

        $flattened = FlattenException::create(new \RuntimeException());
        $this->assertEquals('500', $flattened->getStatusCode());

        $flattened = FlattenException::create(new NotFoundHttpException());
        $this->assertEquals('404', $flattened->getStatusCode());

        $flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"'));
        $this->assertEquals('401', $flattened->getStatusCode());

        $flattened = FlattenException::create(new BadRequestHttpException());
        $this->assertEquals('400', $flattened->getStatusCode());

        $flattened = FlattenException::create(new NotAcceptableHttpException());
        $this->assertEquals('406', $flattened->getStatusCode());

        $flattened = FlattenException::create(new ConflictHttpException());
        $this->assertEquals('409', $flattened->getStatusCode());

        $flattened = FlattenException::create(new MethodNotAllowedHttpException(array('POST')));
        $this->assertEquals('405', $flattened->getStatusCode());

        $flattened = FlattenException::create(new AccessDeniedHttpException());
        $this->assertEquals('403', $flattened->getStatusCode());

        $flattened = FlattenException::create(new GoneHttpException());
        $this->assertEquals('410', $flattened->getStatusCode());

        $flattened = FlattenException::create(new LengthRequiredHttpException());
        $this->assertEquals('411', $flattened->getStatusCode());

        $flattened = FlattenException::create(new PreconditionFailedHttpException());
        $this->assertEquals('412', $flattened->getStatusCode());

        $flattened = FlattenException::create(new PreconditionRequiredHttpException());
        $this->assertEquals('428', $flattened->getStatusCode());

        $flattened = FlattenException::create(new ServiceUnavailableHttpException());
        $this->assertEquals('503', $flattened->getStatusCode());

        $flattened = FlattenException::create(new TooManyRequestsHttpException());
        $this->assertEquals('429', $flattened->getStatusCode());

        $flattened = FlattenException::create(new UnsupportedMediaTypeHttpException());
        $this->assertEquals('415', $flattened->getStatusCode());

        if (class_exists(SuspiciousOperationException::class)) {
            $flattened = FlattenException::create(new SuspiciousOperationException());
            $this->assertEquals('400', $flattened->getStatusCode());
        }
    }

    public function testHeadersForHttpException()
    {
        $flattened = FlattenException::create(new MethodNotAllowedHttpException(array('POST')));
        $this->assertEquals(array('Allow' => 'POST'), $flattened->getHeaders());

        $flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"'));
        $this->assertEquals(array('WWW-Authenticate' => 'Basic realm="My Realm"'), $flattened->getHeaders());

        $flattened = FlattenException::create(new ServiceUnavailableHttpException('Fri, 31 Dec 1999 23:59:59 GMT'));
        $this->assertEquals(array('Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'), $flattened->getHeaders());

        $flattened = FlattenException::create(new ServiceUnavailableHttpException(120));
        $this->assertEquals(array('Retry-After' => 120), $flattened->getHeaders());

        $flattened = FlattenException::create(new TooManyRequestsHttpException('Fri, 31 Dec 1999 23:59:59 GMT'));
        $this->assertEquals(array('Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'), $flattened->getHeaders());

        $flattened = FlattenException::create(new TooManyRequestsHttpException(120));
        $this->assertEquals(array('Retry-After' => 120), $flattened->getHeaders());
    }

    /**
     * @dataProvider flattenDataProvider
     */
    public function testFlattenHttpException(\Exception $exception)
    {
        $flattened = FlattenException::create($exception);
        $flattened2 = FlattenException::create($exception);

        $flattened->setPrevious($flattened2);

        $this->assertEquals($exception->getMessage(), $flattened->getMessage(), 'The message is copied from the original exception.');
        $this->assertEquals($exception->getCode(), $flattened->getCode(), 'The code is copied from the original exception.');
        $this->assertInstanceOf($flattened->getClass(), $exception, 'The class is set to the class of the original exception');
    }

    /**
     * @dataProvider flattenDataProvider
     */
    public function testPrevious(\Exception $exception)
    {
        $flattened = FlattenException::create($exception);
        $flattened2 = FlattenException::create($exception);

        $flattened->setPrevious($flattened2);

        $this->assertSame($flattened2, $flattened->getPrevious());

        $this->assertSame(array($flattened2), $flattened->getAllPrevious());
    }

    /**
     * @requires PHP 7.0
     */
    public function testPreviousError()
    {
        $exception = new \Exception('test', 123, new \ParseError('Oh noes!', 42));

        $flattened = FlattenException::create($exception)->getPrevious();

        $this->assertEquals($flattened->getMessage(), 'Parse error: Oh noes!', 'The message is copied from the original exception.');
        $this->assertEquals($flattened->getCode(), 42, 'The code is copied from the original exception.');
        $this->assertEquals($flattened->getClass(), 'Symfony\Component\Debug\Exception\FatalThrowableError', 'The class is set to the class of the original exception');
    }

    /**
     * @dataProvider flattenDataProvider
     */
    public function testLine(\Exception $exception)
    {
        $flattened = FlattenException::create($exception);
        $this->assertSame($exception->getLine(), $flattened->getLine());
    }

    /**
     * @dataProvider flattenDataProvider
     */
    public function testFile(\Exception $exception)
    {
        $flattened = FlattenException::create($exception);
        $this->assertSame($exception->getFile(), $flattened->getFile());
    }

    /**
     * @dataProvider flattenDataProvider
     */
    public function testToArray(\Exception $exception)
    {
        $flattened = FlattenException::create($exception);
        $flattened->setTrace(array(), 'foo.php', 123);

        $this->assertEquals(array(
            array(
                'message' => 'test',
                'class' => 'Exception',
                'trace' => array(array(
                    'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123,
                    'args' => array(),
                )),
            ),
        ), $flattened->toArray());
    }

    public function flattenDataProvider()
    {
        return array(
            array(new \Exception('test', 123)),
        );
    }

    public function testArguments()
    {
        $dh = opendir(__DIR__);
        $fh = tmpfile();

        $incomplete = unserialize('O:14:"BogusTestClass":0:{}');

        $exception = $this->createException(array(
            (object) array('foo' => 1),
            new NotFoundHttpException(),
            $incomplete,
            $dh,
            $fh,
            function () {},
            array(1, 2),
            array('foo' => 123),
            null,
            true,
            false,
            0,
            0.0,
            '0',
            '',
            INF,
            NAN,
        ));

        $flattened = FlattenException::create($exception);
        $trace = $flattened->getTrace();
        $args = $trace[1]['args'];
        $array = $args[0][1];

        closedir($dh);
        fclose($fh);

        $i = 0;
        $this->assertSame(array('object', 'stdClass'), $array[$i++]);
        $this->assertSame(array('object', 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException'), $array[$i++]);
        $this->assertSame(array('incomplete-object', 'BogusTestClass'), $array[$i++]);
        $this->assertSame(array('resource', defined('HHVM_VERSION') ? 'Directory' : 'stream'), $array[$i++]);
        $this->assertSame(array('resource', 'stream'), $array[$i++]);

        $args = $array[$i++];
        $this->assertSame($args[0], 'object');
        $this->assertTrue('Closure' === $args[1] || is_subclass_of($args[1], '\Closure'), 'Expect object class name to be Closure or a subclass of Closure.');

        $this->assertSame(array('array', array(array('integer', 1), array('integer', 2))), $array[$i++]);
        $this->assertSame(array('array', array('foo' => array('integer', 123))), $array[$i++]);
        $this->assertSame(array('null', null), $array[$i++]);
        $this->assertSame(array('boolean', true), $array[$i++]);
        $this->assertSame(array('boolean', false), $array[$i++]);
        $this->assertSame(array('integer', 0), $array[$i++]);
        $this->assertSame(array('float', 0.0), $array[$i++]);
        $this->assertSame(array('string', '0'), $array[$i++]);
        $this->assertSame(array('string', ''), $array[$i++]);
        $this->assertSame(array('float', INF), $array[$i++]);

        // assertEquals() does not like NAN values.
        $this->assertEquals($array[$i][0], 'float');
        $this->assertTrue(is_nan($array[$i++][1]));
    }

    public function testRecursionInArguments()
    {
        $a = null;
        $a = array('foo', array(2, &$a));
        $exception = $this->createException($a);

        $flattened = FlattenException::create($exception);
        $trace = $flattened->getTrace();
        $this->assertContains('*DEEP NESTED ARRAY*', serialize($trace));
    }

    public function testTooBigArray()
    {
        $a = array();
        for ($i = 0; $i < 20; ++$i) {
            for ($j = 0; $j < 50; ++$j) {
                for ($k = 0; $k < 10; ++$k) {
                    $a[$i][$j][$k] = 'value';
                }
            }
        }
        $a[20] = 'value';
        $a[21] = 'value1';
        $exception = $this->createException($a);

        $flattened = FlattenException::create($exception);
        $trace = $flattened->getTrace();

        $this->assertSame($trace[1]['args'][0], array('array', array('array', '*SKIPPED over 10000 entries*')));

        $serializeTrace = serialize($trace);

        $this->assertContains('*SKIPPED over 10000 entries*', $serializeTrace);
        $this->assertNotContains('*value1*', $serializeTrace);
    }

    private function createException($foo)
    {
        return new \Exception();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Tests;

use PHPUnit\Framework\TestCase;
use Psr\Log\LogLevel;
use Symfony\Component\Debug\BufferingLogger;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\SilencedErrorContext;

/**
 * ErrorHandlerTest.
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ErrorHandlerTest extends TestCase
{
    public function testRegister()
    {
        $handler = ErrorHandler::register();

        try {
            $this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
            $this->assertSame($handler, ErrorHandler::register());

            $newHandler = new ErrorHandler();

            $this->assertSame($handler, ErrorHandler::register($newHandler, false));
            $h = set_error_handler('var_dump');
            restore_error_handler();
            $this->assertSame(array($handler, 'handleError'), $h);

            try {
                $this->assertSame($newHandler, ErrorHandler::register($newHandler, true));
                $h = set_error_handler('var_dump');
                restore_error_handler();
                $this->assertSame(array($newHandler, 'handleError'), $h);
            } catch (\Exception $e) {
            }

            restore_error_handler();
            restore_exception_handler();

            if (isset($e)) {
                throw $e;
            }
        } catch (\Exception $e) {
        }

        restore_error_handler();
        restore_exception_handler();

        if (isset($e)) {
            throw $e;
        }
    }

    public function testNotice()
    {
        ErrorHandler::register();

        try {
            self::triggerNotice($this);
            $this->fail('ErrorException expected');
        } catch (\ErrorException $exception) {
            // if an exception is thrown, the test passed
            $this->assertEquals(E_NOTICE, $exception->getSeverity());
            $this->assertEquals(__FILE__, $exception->getFile());
            $this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());

            $trace = $exception->getTrace();

            $this->assertEquals(__FILE__, $trace[0]['file']);
            $this->assertEquals(__CLASS__, $trace[0]['class']);
            $this->assertEquals('triggerNotice', $trace[0]['function']);
            $this->assertEquals('::', $trace[0]['type']);

            $this->assertEquals(__FILE__, $trace[0]['file']);
            $this->assertEquals(__CLASS__, $trace[1]['class']);
            $this->assertEquals(__FUNCTION__, $trace[1]['function']);
            $this->assertEquals('->', $trace[1]['type']);
        } finally {
            restore_error_handler();
            restore_exception_handler();
        }
    }

    // dummy function to test trace in error handler.
    private static function triggerNotice($that)
    {
        $that->assertSame('', $foo.$foo.$bar);
    }

    public function testConstruct()
    {
        try {
            $handler = ErrorHandler::register();
            $handler->throwAt(3, true);
            $this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, $handler->throwAt(0));
        } finally {
            restore_error_handler();
            restore_exception_handler();
        }
    }

    public function testDefaultLogger()
    {
        try {
            $handler = ErrorHandler::register();

            $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();

            $handler->setDefaultLogger($logger, E_NOTICE);
            $handler->setDefaultLogger($logger, array(E_USER_NOTICE => LogLevel::CRITICAL));

            $loggers = array(
                E_DEPRECATED => array(null, LogLevel::INFO),
                E_USER_DEPRECATED => array(null, LogLevel::INFO),
                E_NOTICE => array($logger, LogLevel::WARNING),
                E_USER_NOTICE => array($logger, LogLevel::CRITICAL),
                E_STRICT => array(null, LogLevel::WARNING),
                E_WARNING => array(null, LogLevel::WARNING),
                E_USER_WARNING => array(null, LogLevel::WARNING),
                E_COMPILE_WARNING => array(null, LogLevel::WARNING),
                E_CORE_WARNING => array(null, LogLevel::WARNING),
                E_USER_ERROR => array(null, LogLevel::CRITICAL),
                E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL),
                E_COMPILE_ERROR => array(null, LogLevel::CRITICAL),
                E_PARSE => array(null, LogLevel::CRITICAL),
                E_ERROR => array(null, LogLevel::CRITICAL),
                E_CORE_ERROR => array(null, LogLevel::CRITICAL),
            );
            $this->assertSame($loggers, $handler->setLoggers(array()));
        } finally {
            restore_error_handler();
            restore_exception_handler();
        }
    }

    public function testHandleError()
    {
        try {
            $handler = ErrorHandler::register();
            $handler->throwAt(0, true);
            $this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, array()));

            restore_error_handler();
            restore_exception_handler();

            $handler = ErrorHandler::register();
            $handler->throwAt(3, true);
            $this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, array()));

            restore_error_handler();
            restore_exception_handler();

            $handler = ErrorHandler::register();
            $handler->throwAt(3, true);
            try {
                $handler->handleError(4, 'foo', 'foo.php', 12, array());
            } catch (\ErrorException $e) {
                $this->assertSame('Parse Error: foo', $e->getMessage());
                $this->assertSame(4, $e->getSeverity());
                $this->assertSame('foo.php', $e->getFile());
                $this->assertSame(12, $e->getLine());
            }

            restore_error_handler();
            restore_exception_handler();

            $handler = ErrorHandler::register();
            $handler->throwAt(E_USER_DEPRECATED, true);
            $this->assertFalse($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));

            restore_error_handler();
            restore_exception_handler();

            $handler = ErrorHandler::register();
            $handler->throwAt(E_DEPRECATED, true);
            $this->assertFalse($handler->handleError(E_DEPRECATED, 'foo', 'foo.php', 12, array()));

            restore_error_handler();
            restore_exception_handler();

            $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();

            $warnArgCheck = function ($logLevel, $message, $context) {
                $this->assertEquals('info', $logLevel);
                $this->assertEquals('User Deprecated: foo', $message);
                $this->assertArrayHasKey('exception', $context);
                $exception = $context['exception'];
                $this->assertInstanceOf(\ErrorException::class, $exception);
                $this->assertSame('User Deprecated: foo', $exception->getMessage());
                $this->assertSame(E_USER_DEPRECATED, $exception->getSeverity());
            };

            $logger
                ->expects($this->once())
                ->method('log')
                ->will($this->returnCallback($warnArgCheck))
            ;

            $handler = ErrorHandler::register();
            $handler->setDefaultLogger($logger, E_USER_DEPRECATED);
            $this->assertTrue($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array()));

            restore_error_handler();
            restore_exception_handler();

            $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();

            $line = null;
            $logArgCheck = function ($level, $message, $context) use (&$line) {
                $this->assertEquals('Notice: Undefined variable: undefVar', $message);
                $this->assertArrayHasKey('exception', $context);
                $exception = $context['exception'];
                $this->assertInstanceOf(SilencedErrorContext::class, $exception);
                $this->assertSame(E_NOTICE, $exception->getSeverity());
                $this->assertSame(__FILE__, $exception->getFile());
                $this->assertSame($line, $exception->getLine());
                $this->assertNotEmpty($exception->getTrace());
                $this->assertSame(1, $exception->count);
            };

            $logger
                ->expects($this->once())
                ->method('log')
                ->will($this->returnCallback($logArgCheck))
            ;

            $handler = ErrorHandler::register();
            $handler->setDefaultLogger($logger, E_NOTICE);
            $handler->screamAt(E_NOTICE);
            unset($undefVar);
            $line = __LINE__ + 1;
            @$undefVar++;

            restore_error_handler();
            restore_exception_handler();
        } catch (\Exception $e) {
            restore_error_handler();
            restore_exception_handler();

            throw $e;
        }
    }

    public function testHandleUserError()
    {
        try {
            $handler = ErrorHandler::register();
            $handler->throwAt(0, true);

            $e = null;
            $x = new \Exception('Foo');

            try {
                $f = new Fixtures\ToStringThrower($x);
                $f .= ''; // Trigger $f->__toString()
            } catch (\Exception $e) {
            }

            $this->assertSame($x, $e);
        } finally {
            restore_error_handler();
            restore_exception_handler();
        }
    }

    public function testHandleDeprecation()
    {
        $logArgCheck = function ($level, $message, $context) {
            $this->assertEquals(LogLevel::INFO, $level);
            $this->assertArrayHasKey('exception', $context);
            $exception = $context['exception'];
            $this->assertInstanceOf(\ErrorException::class, $exception);
            $this->assertSame('User Deprecated: Foo deprecation', $exception->getMessage());
        };

        $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
        $logger
            ->expects($this->once())
            ->method('log')
            ->will($this->returnCallback($logArgCheck))
        ;

        $handler = new ErrorHandler();
        $handler->setDefaultLogger($logger);
        @$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array());
    }

    /**
     * @group no-hhvm
     */
    public function testHandleException()
    {
        try {
            $handler = ErrorHandler::register();

            $exception = new \Exception('foo');

            $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();

            $logArgCheck = function ($level, $message, $context) {
                $this->assertSame('Uncaught Exception: foo', $message);
                $this->assertArrayHasKey('exception', $context);
                $this->assertInstanceOf(\Exception::class, $context['exception']);
            };

            $logger
                ->expects($this->exactly(2))
                ->method('log')
                ->will($this->returnCallback($logArgCheck))
            ;

            $handler->setDefaultLogger($logger, E_ERROR);

            try {
                $handler->handleException($exception);
                $this->fail('Exception expected');
            } catch (\Exception $e) {
                $this->assertSame($exception, $e);
            }

            $handler->setExceptionHandler(function ($e) use ($exception) {
                $this->assertSame($exception, $e);
            });

            $handler->handleException($exception);
        } finally {
            restore_error_handler();
            restore_exception_handler();
        }
    }

    /**
     * @group legacy
     */
    public function testErrorStacking()
    {
        try {
            $handler = ErrorHandler::register();
            $handler->screamAt(E_USER_WARNING);

            $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();

            $logger
                ->expects($this->exactly(2))
                ->method('log')
                ->withConsecutive(
                    array($this->equalTo(LogLevel::WARNING), $this->equalTo('Dummy log')),
                    array($this->equalTo(LogLevel::DEBUG), $this->equalTo('User Warning: Silenced warning'))
                )
            ;

            $handler->setDefaultLogger($logger, array(E_USER_WARNING => LogLevel::WARNING));

            ErrorHandler::stackErrors();
            @trigger_error('Silenced warning', E_USER_WARNING);
            $logger->log(LogLevel::WARNING, 'Dummy log');
            ErrorHandler::unstackErrors();
        } finally {
            restore_error_handler();
            restore_exception_handler();
        }
    }

    public function testBootstrappingLogger()
    {
        $bootLogger = new BufferingLogger();
        $handler = new ErrorHandler($bootLogger);

        $loggers = array(
            E_DEPRECATED => array($bootLogger, LogLevel::INFO),
            E_USER_DEPRECATED => array($bootLogger, LogLevel::INFO),
            E_NOTICE => array($bootLogger, LogLevel::WARNING),
            E_USER_NOTICE => array($bootLogger, LogLevel::WARNING),
            E_STRICT => array($bootLogger, LogLevel::WARNING),
            E_WARNING => array($bootLogger, LogLevel::WARNING),
            E_USER_WARNING => array($bootLogger, LogLevel::WARNING),
            E_COMPILE_WARNING => array($bootLogger, LogLevel::WARNING),
            E_CORE_WARNING => array($bootLogger, LogLevel::WARNING),
            E_USER_ERROR => array($bootLogger, LogLevel::CRITICAL),
            E_RECOVERABLE_ERROR => array($bootLogger, LogLevel::CRITICAL),
            E_COMPILE_ERROR => array($bootLogger, LogLevel::CRITICAL),
            E_PARSE => array($bootLogger, LogLevel::CRITICAL),
            E_ERROR => array($bootLogger, LogLevel::CRITICAL),
            E_CORE_ERROR => array($bootLogger, LogLevel::CRITICAL),
        );

        $this->assertSame($loggers, $handler->setLoggers(array()));

        $handler->handleError(E_DEPRECATED, 'Foo message', __FILE__, 123, array());

        $logs = $bootLogger->cleanLogs();

        $this->assertCount(1, $logs);
        $log = $logs[0];
        $this->assertSame('info', $log[0]);
        $this->assertSame('Deprecated: Foo message', $log[1]);
        $this->assertArrayHasKey('exception', $log[2]);
        $exception = $log[2]['exception'];
        $this->assertInstanceOf(\ErrorException::class, $exception);
        $this->assertSame('Deprecated: Foo message', $exception->getMessage());
        $this->assertSame(__FILE__, $exception->getFile());
        $this->assertSame(123, $exception->getLine());
        $this->assertSame(E_DEPRECATED, $exception->getSeverity());

        $bootLogger->log(LogLevel::WARNING, 'Foo message', array('exception' => $exception));

        $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
        $mockLogger->expects($this->once())
            ->method('log')
            ->with(LogLevel::WARNING, 'Foo message', array('exception' => $exception));

        $handler->setLoggers(array(E_DEPRECATED => array($mockLogger, LogLevel::WARNING)));
    }

    /**
     * @group no-hhvm
     */
    public function testSettingLoggerWhenExceptionIsBuffered()
    {
        $bootLogger = new BufferingLogger();
        $handler = new ErrorHandler($bootLogger);

        $exception = new \Exception('Foo message');

        $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
        $mockLogger->expects($this->once())
            ->method('log')
            ->with(LogLevel::CRITICAL, 'Uncaught Exception: Foo message', array('exception' => $exception));

        $handler->setExceptionHandler(function () use ($handler, $mockLogger) {
            $handler->setDefaultLogger($mockLogger);
        });

        $handler->handleException($exception);
    }

    /**
     * @group no-hhvm
     */
    public function testHandleFatalError()
    {
        try {
            $handler = ErrorHandler::register();

            $error = array(
                'type' => E_PARSE,
                'message' => 'foo',
                'file' => 'bar',
                'line' => 123,
            );

            $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();

            $logArgCheck = function ($level, $message, $context) {
                $this->assertEquals('Fatal Parse Error: foo', $message);
                $this->assertArrayHasKey('exception', $context);
                $this->assertInstanceOf(\Exception::class, $context['exception']);
            };

            $logger
                ->expects($this->once())
                ->method('log')
                ->will($this->returnCallback($logArgCheck))
            ;

            $handler->setDefaultLogger($logger, E_PARSE);

            $handler->handleFatalError($error);

            restore_error_handler();
            restore_exception_handler();
        } catch (\Exception $e) {
            restore_error_handler();
            restore_exception_handler();

            throw $e;
        }
    }

    /**
     * @requires PHP 7
     */
    public function testHandleErrorException()
    {
        $exception = new \Error("Class 'Foo' not found");

        $handler = new ErrorHandler();
        $handler->setExceptionHandler(function () use (&$args) {
            $args = func_get_args();
        });

        $handler->handleException($exception);

        $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]);
        $this->assertStringStartsWith("Attempted to load class \"Foo\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
    }

    /**
     * @group no-hhvm
     */
    public function testHandleFatalErrorOnHHVM()
    {
        try {
            $handler = ErrorHandler::register();

            $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
            $logger
                ->expects($this->once())
                ->method('log')
                ->with(
                    $this->equalTo(LogLevel::CRITICAL),
                    $this->equalTo('Fatal Error: foo')
                )
            ;

            $handler->setDefaultLogger($logger, E_ERROR);

            $error = array(
                'type' => E_ERROR + 0x1000000, // This error level is used by HHVM for fatal errors
                'message' => 'foo',
                'file' => 'bar',
                'line' => 123,
                'context' => array(123),
                'backtrace' => array(456),
            );

            call_user_func_array(array($handler, 'handleError'), $error);
            $handler->handleFatalError($error);
        } finally {
            restore_error_handler();
            restore_exception_handler();
        }
    }
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

class AnnotatedClass
{
    /**
     * @deprecated since version 3.4.
     */
    public function deprecatedMethod()
    {
    }
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

/**
 * @final since version 3.3.
 */
class FinalClass
{
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

class NotPSR0bis
{
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

/**
 * @internal
 */
interface InternalInterface
{
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

class ToStringThrower
{
    private $exception;

    public function __construct(\Exception $e)
    {
        $this->exception = $e;
    }

    public function __toString()
    {
        try {
            throw $this->exception;
        } catch (\Exception $e) {
            // Using user_error() here is on purpose so we do not forget
            // that this alias also should work alongside with trigger_error().
            return user_error($e, E_USER_ERROR);
        }
    }
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

/**
 * @internal since version 3.4.
 */
class InternalClass
{
    use InternalTrait2;

    public function usedInInternalClass()
    {
    }
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

class ExtendedFinalMethod extends FinalMethod
{
    /**
     * {@inheritdoc}
     */
    public function finalMethod()
    {
    }

    public function anotherMethod()
    {
    }
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

/**
 * @internal
 */
trait InternalTrait
{
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

/**
 * @deprecated but this is a test
 *             deprecation notice
 * @foobar
 */
class DeprecatedClass
{
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

class CaseMismatch
{
}
<?php

class Symfony_Component_Debug_Tests_Fixtures_PEARClass
{
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

class NotPSR0
{
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

class PSR4CaseMismatch
{
}
<?php

throw new \Exception('boo');
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

/**
 * @internal
 */
trait InternalTrait2
{
    /**
     * @internal since version 3.4
     */
    public function internalMethod()
    {
    }

    /**
     * @internal but should not trigger a deprecation
     */
    public function usedInInternalClass()
    {
    }
}
<?php

class_alias('Symfony\Component\Debug\Tests\Fixtures\NotPSR0bis', 'Symfony\Component\Debug\Tests\Fixtures\ClassAlias');
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

interface NonDeprecatedInterface extends DeprecatedInterface
{
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

class FinalMethod
{
    /**
     * @final since version 3.3.
     */
    public function finalMethod()
    {
    }

    public function anotherMethod()
    {
    }
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures;

/**
 * @deprecated but this is a test
 *             deprecation notice
 * @foobar
 */
interface DeprecatedInterface
{
}
<?php

namespace Symfony\Component\Debug\Tests\Fixtures2;

class RequiredTwice
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Tests;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\Debug\ErrorHandler;

class DebugClassLoaderTest extends TestCase
{
    /**
     * @var int Error reporting level before running tests
     */
    private $errorReporting;

    private $loader;

    protected function setUp()
    {
        $this->errorReporting = error_reporting(E_ALL);
        $this->loader = new ClassLoader();
        spl_autoload_register(array($this->loader, 'loadClass'), true, true);
        DebugClassLoader::enable();
    }

    protected function tearDown()
    {
        DebugClassLoader::disable();
        spl_autoload_unregister(array($this->loader, 'loadClass'));
        error_reporting($this->errorReporting);
    }

    public function testIdempotence()
    {
        DebugClassLoader::enable();

        $functions = spl_autoload_functions();
        foreach ($functions as $function) {
            if (is_array($function) && $function[0] instanceof DebugClassLoader) {
                $reflClass = new \ReflectionClass($function[0]);
                $reflProp = $reflClass->getProperty('classLoader');
                $reflProp->setAccessible(true);

                $this->assertNotInstanceOf('Symfony\Component\Debug\DebugClassLoader', $reflProp->getValue($function[0]));

                return;
            }
        }

        $this->fail('DebugClassLoader did not register');
    }

    /**
     * @expectedException \Exception
     * @expectedExceptionMessage boo
     */
    public function testThrowingClass()
    {
        try {
            class_exists(__NAMESPACE__.'\Fixtures\Throwing');
            $this->fail('Exception expected');
        } catch (\Exception $e) {
            $this->assertSame('boo', $e->getMessage());
        }

        // the second call also should throw
        class_exists(__NAMESPACE__.'\Fixtures\Throwing');
    }

    public function testUnsilencing()
    {
        if (\PHP_VERSION_ID >= 70000) {
            $this->markTestSkipped('PHP7 throws exceptions, unsilencing is not required anymore.');
        }
        if (defined('HHVM_VERSION')) {
            $this->markTestSkipped('HHVM is not handled in this test case.');
        }

        ob_start();

        $this->iniSet('log_errors', 0);
        $this->iniSet('display_errors', 1);

        // See below: this will fail with parse error
        // but this should not be @-silenced.
        @class_exists(__NAMESPACE__.'\TestingUnsilencing', true);

        $output = ob_get_clean();

        $this->assertStringMatchesFormat('%aParse error%a', $output);
    }

    public function testStacking()
    {
        // the ContextErrorException must not be loaded to test the workaround
        // for https://bugs.php.net/65322.
        if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) {
            $this->markTestSkipped('The ContextErrorException class is already loaded.');
        }
        if (defined('HHVM_VERSION')) {
            $this->markTestSkipped('HHVM is not handled in this test case.');
        }

        ErrorHandler::register();

        try {
            // Trigger autoloading + E_STRICT at compile time
            // which in turn triggers $errorHandler->handle()
            // that again triggers autoloading for ContextErrorException.
            // Error stacking works around the bug above and everything is fine.

            eval('
                namespace '.__NAMESPACE__.';
                class ChildTestingStacking extends TestingStacking { function foo($bar) {} }
            ');
            $this->fail('ContextErrorException expected');
        } catch (\ErrorException $exception) {
            // if an exception is thrown, the test passed
            $this->assertStringStartsWith(__FILE__, $exception->getFile());
            if (\PHP_VERSION_ID < 70000) {
                $this->assertRegExp('/^Runtime Notice: Declaration/', $exception->getMessage());
                $this->assertEquals(E_STRICT, $exception->getSeverity());
            } else {
                $this->assertRegExp('/^Warning: Declaration/', $exception->getMessage());
                $this->assertEquals(E_WARNING, $exception->getSeverity());
            }
        } finally {
            restore_error_handler();
            restore_exception_handler();
        }
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Case mismatch between loaded and declared class names
     */
    public function testNameCaseMismatch()
    {
        class_exists(__NAMESPACE__.'\TestingCaseMismatch', true);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Case mismatch between class and real file names
     */
    public function testFileCaseMismatch()
    {
        if (!file_exists(__DIR__.'/Fixtures/CaseMismatch.php')) {
            $this->markTestSkipped('Can only be run on case insensitive filesystems');
        }

        class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Case mismatch between loaded and declared class names
     */
    public function testPsr4CaseMismatch()
    {
        class_exists(__NAMESPACE__.'\Fixtures\Psr4CaseMismatch', true);
    }

    public function testNotPsr0()
    {
        $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0', true));
    }

    public function testNotPsr0Bis()
    {
        $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0bis', true));
    }

    public function testClassAlias()
    {
        $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\ClassAlias', true));
    }

    /**
     * @dataProvider provideDeprecatedSuper
     */
    public function testDeprecatedSuper($class, $super, $type)
    {
        set_error_handler(function () { return false; });
        $e = error_reporting(0);
        trigger_error('', E_USER_DEPRECATED);

        class_exists('Test\\'.__NAMESPACE__.'\\'.$class, true);

        error_reporting($e);
        restore_error_handler();

        $lastError = error_get_last();
        unset($lastError['file'], $lastError['line']);

        $xError = array(
            'type' => E_USER_DEPRECATED,
            'message' => 'The "Test\Symfony\Component\Debug\Tests\\'.$class.'" class '.$type.' "Symfony\Component\Debug\Tests\Fixtures\\'.$super.'" that is deprecated but this is a test deprecation notice.',
        );

        $this->assertSame($xError, $lastError);
    }

    public function provideDeprecatedSuper()
    {
        return array(
            array('DeprecatedInterfaceClass', 'DeprecatedInterface', 'implements'),
            array('DeprecatedParentClass', 'DeprecatedClass', 'extends'),
        );
    }

    public function testInterfaceExtendsDeprecatedInterface()
    {
        set_error_handler(function () { return false; });
        $e = error_reporting(0);
        trigger_error('', E_USER_NOTICE);

        class_exists('Test\\'.__NAMESPACE__.'\\NonDeprecatedInterfaceClass', true);

        error_reporting($e);
        restore_error_handler();

        $lastError = error_get_last();
        unset($lastError['file'], $lastError['line']);

        $xError = array(
            'type' => E_USER_NOTICE,
            'message' => '',
        );

        $this->assertSame($xError, $lastError);
    }

    public function testDeprecatedSuperInSameNamespace()
    {
        set_error_handler(function () { return false; });
        $e = error_reporting(0);
        trigger_error('', E_USER_NOTICE);

        class_exists('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent', true);

        error_reporting($e);
        restore_error_handler();

        $lastError = error_get_last();
        unset($lastError['file'], $lastError['line']);

        $xError = array(
            'type' => E_USER_NOTICE,
            'message' => '',
        );

        $this->assertSame($xError, $lastError);
    }

    public function testReservedForPhp7()
    {
        if (\PHP_VERSION_ID >= 70000) {
            $this->markTestSkipped('PHP7 already prevents using reserved names.');
        }

        set_error_handler(function () { return false; });
        $e = error_reporting(0);
        trigger_error('', E_USER_NOTICE);

        class_exists('Test\\'.__NAMESPACE__.'\\Float', true);

        error_reporting($e);
        restore_error_handler();

        $lastError = error_get_last();
        unset($lastError['file'], $lastError['line']);

        $xError = array(
            'type' => E_USER_DEPRECATED,
            'message' => 'The "Test\Symfony\Component\Debug\Tests\Float" class uses the reserved name "Float", it will break on PHP 7 and higher',
        );

        $this->assertSame($xError, $lastError);
    }

    public function testExtendedFinalClass()
    {
        set_error_handler(function () { return false; });
        $e = error_reporting(0);
        trigger_error('', E_USER_NOTICE);

        class_exists('Test\\'.__NAMESPACE__.'\\ExtendsFinalClass', true);

        error_reporting($e);
        restore_error_handler();

        $lastError = error_get_last();
        unset($lastError['file'], $lastError['line']);

        $xError = array(
            'type' => E_USER_DEPRECATED,
            'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass" class is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass".',
        );

        $this->assertSame($xError, $lastError);
    }

    public function testExtendedFinalMethod()
    {
        set_error_handler(function () { return false; });
        $e = error_reporting(0);
        trigger_error('', E_USER_NOTICE);

        class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true);

        error_reporting($e);
        restore_error_handler();

        $lastError = error_get_last();
        unset($lastError['file'], $lastError['line']);

        $xError = array(
            'type' => E_USER_DEPRECATED,
            'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".',
        );

        $this->assertSame($xError, $lastError);
    }

    public function testExtendedDeprecatedMethodDoesntTriggerAnyNotice()
    {
        set_error_handler(function () { return false; });
        $e = error_reporting(0);
        trigger_error('', E_USER_NOTICE);

        class_exists('Test\\'.__NAMESPACE__.'\\ExtendsAnnotatedClass', true);

        error_reporting($e);
        restore_error_handler();

        $lastError = error_get_last();
        unset($lastError['file'], $lastError['line']);

        $this->assertSame(array('type' => E_USER_NOTICE, 'message' => ''), $lastError);
    }

    public function testInternalsUse()
    {
        $deprecations = array();
        set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
        $e = error_reporting(E_USER_DEPRECATED);

        class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true);

        error_reporting($e);
        restore_error_handler();

        $this->assertSame($deprecations, array(
            'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass" class is considered internal since version 3.4. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".',
            'The "Symfony\Component\Debug\Tests\Fixtures\InternalInterface" interface is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".',
            'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait" trait is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
            'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait2::internalMethod()" method is considered internal since version 3.4. It may change without further notice. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
        ));
    }
}

class ClassLoader
{
    public function loadClass($class)
    {
    }

    public function getClassMap()
    {
        return array(__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php');
    }

    public function findFile($class)
    {
        $fixtureDir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR;

        if (__NAMESPACE__.'\TestingUnsilencing' === $class) {
            eval('-- parse error --');
        } elseif (__NAMESPACE__.'\TestingStacking' === $class) {
            eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }');
        } elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
            eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
        } elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
            return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php';
        } elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
            return $fixtureDir.'reallyNotPsr0.php';
        } elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
            return $fixtureDir.'notPsr0Bis.php';
        } elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) {
            eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
        } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) {
            eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedParentClass extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
        } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedInterfaceClass' === $class) {
            eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\DeprecatedInterface {}');
        } elseif ('Test\\'.__NAMESPACE__.'\NonDeprecatedInterfaceClass' === $class) {
            eval('namespace Test\\'.__NAMESPACE__.'; class NonDeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\NonDeprecatedInterface {}');
        } elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) {
            eval('namespace Test\\'.__NAMESPACE__.'; class Float {}');
        } elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) {
            eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsFinalClass extends \\'.__NAMESPACE__.'\Fixtures\FinalClass {}');
        } elseif ('Test\\'.__NAMESPACE__.'\ExtendsAnnotatedClass' === $class) {
            eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsAnnotatedClass extends \\'.__NAMESPACE__.'\Fixtures\AnnotatedClass {
                public function deprecatedMethod() { }
            }');
        } elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternals' === $class) {
            eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternals extends ExtendsInternalsParent {
                use \\'.__NAMESPACE__.'\Fixtures\InternalTrait;

                public function internalMethod() { }
            }');
        } elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternalsParent' === $class) {
            eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternalsParent extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { }');
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Tests;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;

require_once __DIR__.'/HeaderMock.php';

class ExceptionHandlerTest extends TestCase
{
    protected function setUp()
    {
        testHeader();
    }

    protected function tearDown()
    {
        testHeader();
    }

    public function testDebug()
    {
        $handler = new ExceptionHandler(false);

        ob_start();
        $handler->sendPhpResponse(new \RuntimeException('Foo'));
        $response = ob_get_clean();

        $this->assertContains('Whoops, looks like something went wrong.', $response);
        $this->assertNotContains('<div class="trace trace-as-html">', $response);

        $handler = new ExceptionHandler(true);

        ob_start();
        $handler->sendPhpResponse(new \RuntimeException('Foo'));
        $response = ob_get_clean();

        $this->assertContains('Whoops, looks like something went wrong.', $response);
        $this->assertContains('<div class="trace trace-as-html">', $response);
    }

    public function testStatusCode()
    {
        $handler = new ExceptionHandler(false, 'iso8859-1');

        ob_start();
        $handler->sendPhpResponse(new NotFoundHttpException('Foo'));
        $response = ob_get_clean();

        $this->assertContains('Sorry, the page you are looking for could not be found.', $response);

        $expectedHeaders = array(
            array('HTTP/1.0 404', true, null),
            array('Content-Type: text/html; charset=iso8859-1', true, null),
        );

        $this->assertSame($expectedHeaders, testHeader());
    }

    public function testHeaders()
    {
        $handler = new ExceptionHandler(false, 'iso8859-1');

        ob_start();
        $handler->sendPhpResponse(new MethodNotAllowedHttpException(array('POST')));
        $response = ob_get_clean();

        $expectedHeaders = array(
            array('HTTP/1.0 405', true, null),
            array('Allow: POST', false, null),
            array('Content-Type: text/html; charset=iso8859-1', true, null),
        );

        $this->assertSame($expectedHeaders, testHeader());
    }

    public function testNestedExceptions()
    {
        $handler = new ExceptionHandler(true);
        ob_start();
        $handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
        $response = ob_get_clean();

        $this->assertStringMatchesFormat('%A<p class="break-long-words trace-message">Foo</p>%A<p class="break-long-words trace-message">Bar</p>%A', $response);
    }

    public function testHandle()
    {
        $exception = new \Exception('foo');

        $handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(array('sendPhpResponse'))->getMock();
        $handler
            ->expects($this->exactly(2))
            ->method('sendPhpResponse');

        $handler->handle($exception);

        $handler->setHandler(function ($e) use ($exception) {
            $this->assertSame($exception, $e);
        });

        $handler->handle($exception);
    }

    public function testHandleOutOfMemoryException()
    {
        $exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);

        $handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(array('sendPhpResponse'))->getMock();
        $handler
            ->expects($this->once())
            ->method('sendPhpResponse');

        $handler->setHandler(function ($e) {
            $this->fail('OutOfMemoryException should bypass the handler');
        });

        $handler->handle($exception);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Tests\FatalErrorHandler;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\DebugClassLoader;
use Composer\Autoload\ClassLoader as ComposerClassLoader;

class ClassNotFoundFatalErrorHandlerTest extends TestCase
{
    public static function setUpBeforeClass()
    {
        foreach (spl_autoload_functions() as $function) {
            if (!is_array($function)) {
                continue;
            }

            // get class loaders wrapped by DebugClassLoader
            if ($function[0] instanceof DebugClassLoader) {
                $function = $function[0]->getClassLoader();
            }

            if ($function[0] instanceof ComposerClassLoader) {
                $function[0]->add('Symfony_Component_Debug_Tests_Fixtures', dirname(dirname(dirname(dirname(dirname(__DIR__))))));
                break;
            }
        }
    }

    /**
     * @dataProvider provideClassNotFoundData
     */
    public function testHandleClassNotFound($error, $translatedMessage, $autoloader = null)
    {
        if ($autoloader) {
            // Unregister all autoloaders to ensure the custom provided
            // autoloader is the only one to be used during the test run.
            $autoloaders = spl_autoload_functions();
            array_map('spl_autoload_unregister', $autoloaders);
            spl_autoload_register($autoloader);
        }

        $handler = new ClassNotFoundFatalErrorHandler();

        $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));

        if ($autoloader) {
            spl_autoload_unregister($autoloader);
            array_map('spl_autoload_register', $autoloaders);
        }

        $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
        $this->assertSame($translatedMessage, $exception->getMessage());
        $this->assertSame($error['type'], $exception->getSeverity());
        $this->assertSame($error['file'], $exception->getFile());
        $this->assertSame($error['line'], $exception->getLine());
    }

    public function provideClassNotFoundData()
    {
        $autoloader = new ComposerClassLoader();
        $autoloader->add('Symfony\Component\Debug\Exception\\', realpath(__DIR__.'/../../Exception'));

        $debugClassLoader = new DebugClassLoader(array($autoloader, 'loadClass'));

        return array(
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Class \'WhizBangFactory\' not found',
                ),
                "Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?",
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found',
                ),
                "Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Class \'UndefinedFunctionException\' not found',
                ),
                "Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Class \'PEARClass\' not found',
                ),
                "Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"?",
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
                ),
                "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
                ),
                "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
                array($autoloader, 'loadClass'),
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
                ),
                "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
                array($debugClassLoader, 'loadClass'),
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
                ),
                "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
                function ($className) { /* do nothing here */ },
            ),
        );
    }

    public function testCannotRedeclareClass()
    {
        if (!file_exists(__DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP')) {
            $this->markTestSkipped('Can only be run on case insensitive filesystems');
        }

        require_once __DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP';

        $error = array(
            'type' => 1,
            'line' => 12,
            'file' => 'foo.php',
            'message' => 'Class \'Foo\\Bar\\RequiredTwice\' not found',
        );

        $handler = new ClassNotFoundFatalErrorHandler();
        $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));

        $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Tests\FatalErrorHandler;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;

class UndefinedMethodFatalErrorHandlerTest extends TestCase
{
    /**
     * @dataProvider provideUndefinedMethodData
     */
    public function testUndefinedMethod($error, $translatedMessage)
    {
        $handler = new UndefinedMethodFatalErrorHandler();
        $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));

        $this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedMethodException', $exception);
        $this->assertSame($translatedMessage, $exception->getMessage());
        $this->assertSame($error['type'], $exception->getSeverity());
        $this->assertSame($error['file'], $exception->getFile());
        $this->assertSame($error['line'], $exception->getLine());
    }

    public function provideUndefinedMethodData()
    {
        return array(
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Call to undefined method SplObjectStorage::what()',
                ),
                'Attempted to call an undefined method named "what" of class "SplObjectStorage".',
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Call to undefined method SplObjectStorage::walid()',
                ),
                "Attempted to call an undefined method named \"walid\" of class \"SplObjectStorage\".\nDid you mean to call \"valid\"?",
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Call to undefined method SplObjectStorage::offsetFet()',
                ),
                "Attempted to call an undefined method named \"offsetFet\" of class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?",
            ),
            array(
                array(
                  'type' => 1,
                  'message' => 'Call to undefined method class@anonymous::test()',
                  'file' => '/home/possum/work/symfony/test.php',
                  'line' => 11,
                ),
                'Attempted to call an undefined method named "test" of class "class@anonymous".',
            ),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Tests\FatalErrorHandler;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;

class UndefinedFunctionFatalErrorHandlerTest extends TestCase
{
    /**
     * @dataProvider provideUndefinedFunctionData
     */
    public function testUndefinedFunction($error, $translatedMessage)
    {
        $handler = new UndefinedFunctionFatalErrorHandler();
        $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));

        $this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedFunctionException', $exception);
        // class names are case insensitive and PHP/HHVM do not return the same
        $this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage()));
        $this->assertSame($error['type'], $exception->getSeverity());
        $this->assertSame($error['file'], $exception->getFile());
        $this->assertSame($error['line'], $exception->getLine());
    }

    public function provideUndefinedFunctionData()
    {
        return array(
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Call to undefined function test_namespaced_function()',
                ),
                "Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()',
                ),
                "Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Call to undefined function foo()',
                ),
                'Attempted to call function "foo" from the global namespace.',
            ),
            array(
                array(
                    'type' => 1,
                    'line' => 12,
                    'file' => 'foo.php',
                    'message' => 'Call to undefined function Foo\\Bar\\Baz\\foo()',
                ),
                'Attempted to call function "foo" from namespace "Foo\Bar\Baz".',
            ),
        );
    }
}

function test_namespaced_function()
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

function headers_sent()
{
    return false;
}

function header($str, $replace = true, $status = null)
{
    Tests\testHeader($str, $replace, $status);
}

namespace Symfony\Component\Debug\Tests;

function testHeader()
{
    static $headers = array();

    if (!$h = func_get_args()) {
        $h = $headers;
        $headers = array();

        return $h;
    }

    $headers[] = func_get_args();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

/**
 * Autoloader checking if the class is really defined in the file found.
 *
 * The ClassLoader will wrap all registered autoloaders
 * and will throw an exception if a file is found but does
 * not declare the class.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Christophe Coevoet <stof@notk.org>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class DebugClassLoader
{
    private $classLoader;
    private $isFinder;
    private $loaded = array();
    private static $caseCheck;
    private static $checkedClasses = array();
    private static $final = array();
    private static $finalMethods = array();
    private static $deprecated = array();
    private static $internal = array();
    private static $internalMethods = array();
    private static $php7Reserved = array('int' => 1, 'float' => 1, 'bool' => 1, 'string' => 1, 'true' => 1, 'false' => 1, 'null' => 1);
    private static $darwinCache = array('/' => array('/', array()));

    public function __construct(callable $classLoader)
    {
        $this->classLoader = $classLoader;
        $this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile');

        if (!isset(self::$caseCheck)) {
            $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR);
            $i = strrpos($file, DIRECTORY_SEPARATOR);
            $dir = substr($file, 0, 1 + $i);
            $file = substr($file, 1 + $i);
            $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
            $test = realpath($dir.$test);

            if (false === $test || false === $i) {
                // filesystem is case sensitive
                self::$caseCheck = 0;
            } elseif (substr($test, -strlen($file)) === $file) {
                // filesystem is case insensitive and realpath() normalizes the case of characters
                self::$caseCheck = 1;
            } elseif (false !== stripos(PHP_OS, 'darwin')) {
                // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
                self::$caseCheck = 2;
            } else {
                // filesystem case checks failed, fallback to disabling them
                self::$caseCheck = 0;
            }
        }
    }

    /**
     * Gets the wrapped class loader.
     *
     * @return callable The wrapped class loader
     */
    public function getClassLoader()
    {
        return $this->classLoader;
    }

    /**
     * Wraps all autoloaders.
     */
    public static function enable()
    {
        // Ensures we don't hit https://bugs.php.net/42098
        class_exists('Symfony\Component\Debug\ErrorHandler');
        class_exists('Psr\Log\LogLevel');

        if (!is_array($functions = spl_autoload_functions())) {
            return;
        }

        foreach ($functions as $function) {
            spl_autoload_unregister($function);
        }

        foreach ($functions as $function) {
            if (!is_array($function) || !$function[0] instanceof self) {
                $function = array(new static($function), 'loadClass');
            }

            spl_autoload_register($function);
        }
    }

    /**
     * Disables the wrapping.
     */
    public static function disable()
    {
        if (!is_array($functions = spl_autoload_functions())) {
            return;
        }

        foreach ($functions as $function) {
            spl_autoload_unregister($function);
        }

        foreach ($functions as $function) {
            if (is_array($function) && $function[0] instanceof self) {
                $function = $function[0]->getClassLoader();
            }

            spl_autoload_register($function);
        }
    }

    /**
     * Loads the given class or interface.
     *
     * @param string $class The name of the class
     *
     * @return bool|null True, if loaded
     *
     * @throws \RuntimeException
     */
    public function loadClass($class)
    {
        $e = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);

        try {
            if ($this->isFinder && !isset($this->loaded[$class])) {
                $this->loaded[$class] = true;
                if ($file = $this->classLoader[0]->findFile($class) ?: false) {
                    $wasCached = \function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file);

                    require $file;

                    if ($wasCached) {
                        return;
                    }
                }
            } else {
                call_user_func($this->classLoader, $class);
                $file = false;
            }
        } finally {
            error_reporting($e);
        }

        $this->checkClass($class, $file);
    }

    private function checkClass($class, $file = null)
    {
        $exists = null === $file || \class_exists($class, false) || \interface_exists($class, false) || \trait_exists($class, false);

        if (null !== $file && $class && '\\' === $class[0]) {
            $class = substr($class, 1);
        }

        if ($exists) {
            if (isset(self::$checkedClasses[$class])) {
                return;
            }
            self::$checkedClasses[$class] = true;

            $refl = new \ReflectionClass($class);
            if (null === $file && $refl->isInternal()) {
                return;
            }
            $name = $refl->getName();

            if ($name !== $class && 0 === \strcasecmp($name, $class)) {
                throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
            }

            // Don't trigger deprecations for classes in the same vendor
            if (2 > $len = 1 + (\strpos($name, '\\') ?: \strpos($name, '_'))) {
                $len = 0;
                $ns = '';
            } else {
                $ns = \substr($name, 0, $len);
            }

            // Detect annotations on the class
            if (false !== $doc = $refl->getDocComment()) {
                foreach (array('final', 'deprecated', 'internal') as $annotation) {
                    if (false !== \strpos($doc, $annotation) && preg_match('#\n \* @'.$annotation.'(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
                        self::${$annotation}[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
                    }
                }
            }

            $parentAndTraits = \class_uses($name, false);
            if ($parent = \get_parent_class($class)) {
                $parentAndTraits[] = $parent;

                if (!isset(self::$checkedClasses[$parent])) {
                    $this->checkClass($parent);
                }

                if (isset(self::$final[$parent])) {
                    @trigger_error(sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $name), E_USER_DEPRECATED);
                }
            }

            // Detect if the parent is annotated
            foreach ($parentAndTraits + $this->getOwnInterfaces($name, $parent) as $use) {
                if (!isset(self::$checkedClasses[$use])) {
                    $this->checkClass($use);
                }
                if (isset(self::$deprecated[$use]) && \strncmp($ns, $use, $len)) {
                    $type = class_exists($name, false) ? 'class' : (interface_exists($name, false) ? 'interface' : 'trait');
                    $verb = class_exists($use, false) || interface_exists($name, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');

                    @trigger_error(sprintf('The "%s" %s %s "%s" that is deprecated%s.', $name, $type, $verb, $use, self::$deprecated[$use]), E_USER_DEPRECATED);
                }
                if (isset(self::$internal[$use]) && \strncmp($ns, $use, $len)) {
                    @trigger_error(sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $name), E_USER_DEPRECATED);
                }
            }

            // Inherit @final and @internal annotations for methods
            self::$finalMethods[$name] = array();
            self::$internalMethods[$name] = array();
            foreach ($parentAndTraits as $use) {
                foreach (array('finalMethods', 'internalMethods') as $property) {
                    if (isset(self::${$property}[$use])) {
                        self::${$property}[$name] = self::${$property}[$name] ? self::${$property}[$use] + self::${$property}[$name] : self::${$property}[$use];
                    }
                }
            }

            $isClass = \class_exists($name, false);
            foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
                if ($method->class !== $name) {
                    continue;
                }

                // Method from a trait
                if ($method->getFilename() !== $refl->getFileName()) {
                    continue;
                }

                if ($isClass && $parent && isset(self::$finalMethods[$parent][$method->name])) {
                    list($declaringClass, $message) = self::$finalMethods[$parent][$method->name];
                    @trigger_error(sprintf('The "%s::%s()" method is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $name), E_USER_DEPRECATED);
                }

                foreach ($parentAndTraits as $use) {
                    if (isset(self::$internalMethods[$use][$method->name])) {
                        list($declaringClass, $message) = self::$internalMethods[$use][$method->name];
                        if (\strncmp($ns, $declaringClass, $len)) {
                            @trigger_error(sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $name), E_USER_DEPRECATED);
                        }
                    }
                }

                // Detect method annotations
                if (false === $doc = $method->getDocComment()) {
                    continue;
                }

                foreach (array('final', 'internal') as $annotation) {
                    if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
                        $message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
                        self::${$annotation.'Methods'}[$name][$method->name] = array($name, $message);
                    }
                }
            }

            if (isset(self::$php7Reserved[\strtolower($refl->getShortName())])) {
                @trigger_error(sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
            }
        }

        if ($file) {
            if (!$exists) {
                if (false !== strpos($class, '/')) {
                    throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
                }

                throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
            }
            if (self::$caseCheck) {
                $real = explode('\\', $class.strrchr($file, '.'));
                $tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file));

                $i = count($tail) - 1;
                $j = count($real) - 1;

                while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
                    --$i;
                    --$j;
                }

                array_splice($tail, 0, $i + 1);
            }
            if (self::$caseCheck && $tail) {
                $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail);
                $tailLen = strlen($tail);
                $real = $refl->getFileName();

                if (2 === self::$caseCheck) {
                    // realpath() on MacOSX doesn't normalize the case of characters

                    $i = 1 + strrpos($real, '/');
                    $file = substr($real, $i);
                    $real = substr($real, 0, $i);

                    if (isset(self::$darwinCache[$real])) {
                        $kDir = $real;
                    } else {
                        $kDir = strtolower($real);

                        if (isset(self::$darwinCache[$kDir])) {
                            $real = self::$darwinCache[$kDir][0];
                        } else {
                            $dir = getcwd();
                            chdir($real);
                            $real = getcwd().'/';
                            chdir($dir);

                            $dir = $real;
                            $k = $kDir;
                            $i = strlen($dir) - 1;
                            while (!isset(self::$darwinCache[$k])) {
                                self::$darwinCache[$k] = array($dir, array());
                                self::$darwinCache[$dir] = &self::$darwinCache[$k];

                                while ('/' !== $dir[--$i]) {
                                }
                                $k = substr($k, 0, ++$i);
                                $dir = substr($dir, 0, $i--);
                            }
                        }
                    }

                    $dirFiles = self::$darwinCache[$kDir][1];

                    if (isset($dirFiles[$file])) {
                        $kFile = $file;
                    } else {
                        $kFile = strtolower($file);

                        if (!isset($dirFiles[$kFile])) {
                            foreach (scandir($real, 2) as $f) {
                                if ('.' !== $f[0]) {
                                    $dirFiles[$f] = $f;
                                    if ($f === $file) {
                                        $kFile = $k = $file;
                                    } elseif ($f !== $k = strtolower($f)) {
                                        $dirFiles[$k] = $f;
                                    }
                                }
                            }
                            self::$darwinCache[$kDir][1] = $dirFiles;
                        }
                    }

                    $real .= $dirFiles[$kFile];
                }

                if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
                  && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
                ) {
                    throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)));
                }
            }
        }
    }

    /**
     * `class_implements` includes interfaces from the parents so we have to manually exclude them.
     *
     * @param string       $class
     * @param string|false $parent
     *
     * @return string[]
     */
    private function getOwnInterfaces($class, $parent)
    {
        $ownInterfaces = class_implements($class, false);

        if ($parent) {
            foreach (class_implements($parent, false) as $interface) {
                unset($ownInterfaces[$interface]);
            }
        }

        foreach ($ownInterfaces as $interface) {
            foreach (class_implements($interface) as $interface) {
                unset($ownInterfaces[$interface]);
            }
        }

        return $ownInterfaces;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;

/**
 * ExceptionHandler converts an exception to a Response object.
 *
 * It is mostly useful in debug mode to replace the default PHP/XDebug
 * output with something prettier and more useful.
 *
 * As this class is mainly used during Kernel boot, where nothing is yet
 * available, the Response content is always HTML.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ExceptionHandler
{
    private $debug;
    private $charset;
    private $handler;
    private $caughtBuffer;
    private $caughtLength;
    private $fileLinkFormat;

    public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
    {
        $this->debug = $debug;
        $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
        $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
    }

    /**
     * Registers the exception handler.
     *
     * @param bool        $debug          Enable/disable debug mode, where the stack trace is displayed
     * @param string|null $charset        The charset used by exception messages
     * @param string|null $fileLinkFormat The IDE link template
     *
     * @return static
     */
    public static function register($debug = true, $charset = null, $fileLinkFormat = null)
    {
        $handler = new static($debug, $charset, $fileLinkFormat);

        $prev = set_exception_handler(array($handler, 'handle'));
        if (is_array($prev) && $prev[0] instanceof ErrorHandler) {
            restore_exception_handler();
            $prev[0]->setExceptionHandler(array($handler, 'handle'));
        }

        return $handler;
    }

    /**
     * Sets a user exception handler.
     *
     * @param callable $handler An handler that will be called on Exception
     *
     * @return callable|null The previous exception handler if any
     */
    public function setHandler(callable $handler = null)
    {
        $old = $this->handler;
        $this->handler = $handler;

        return $old;
    }

    /**
     * Sets the format for links to source files.
     *
     * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
     *
     * @return string The previous file link format
     */
    public function setFileLinkFormat($fileLinkFormat)
    {
        $old = $this->fileLinkFormat;
        $this->fileLinkFormat = $fileLinkFormat;

        return $old;
    }

    /**
     * Sends a response for the given Exception.
     *
     * To be as fail-safe as possible, the exception is first handled
     * by our simple exception handler, then by the user exception handler.
     * The latter takes precedence and any output from the former is cancelled,
     * if and only if nothing bad happens in this handling path.
     */
    public function handle(\Exception $exception)
    {
        if (null === $this->handler || $exception instanceof OutOfMemoryException) {
            $this->sendPhpResponse($exception);

            return;
        }

        $caughtLength = $this->caughtLength = 0;

        ob_start(function ($buffer) {
            $this->caughtBuffer = $buffer;

            return '';
        });

        $this->sendPhpResponse($exception);
        while (null === $this->caughtBuffer && ob_end_flush()) {
            // Empty loop, everything is in the condition
        }
        if (isset($this->caughtBuffer[0])) {
            ob_start(function ($buffer) {
                if ($this->caughtLength) {
                    // use substr_replace() instead of substr() for mbstring overloading resistance
                    $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
                    if (isset($cleanBuffer[0])) {
                        $buffer = $cleanBuffer;
                    }
                }

                return $buffer;
            });

            echo $this->caughtBuffer;
            $caughtLength = ob_get_length();
        }
        $this->caughtBuffer = null;

        try {
            call_user_func($this->handler, $exception);
            $this->caughtLength = $caughtLength;
        } catch (\Exception $e) {
            if (!$caughtLength) {
                // All handlers failed. Let PHP handle that now.
                throw $exception;
            }
        }
    }

    /**
     * Sends the error associated with the given Exception as a plain PHP response.
     *
     * This method uses plain PHP functions like header() and echo to output
     * the response.
     *
     * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
     */
    public function sendPhpResponse($exception)
    {
        if (!$exception instanceof FlattenException) {
            $exception = FlattenException::create($exception);
        }

        if (!headers_sent()) {
            header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
            foreach ($exception->getHeaders() as $name => $value) {
                header($name.': '.$value, false);
            }
            header('Content-Type: text/html; charset='.$this->charset);
        }

        echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
    }

    /**
     * Gets the full HTML content associated with the given exception.
     *
     * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
     *
     * @return string The HTML content as a string
     */
    public function getHtml($exception)
    {
        if (!$exception instanceof FlattenException) {
            $exception = FlattenException::create($exception);
        }

        return $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
    }

    /**
     * Gets the HTML content associated with the given exception.
     *
     * @return string The content as a string
     */
    public function getContent(FlattenException $exception)
    {
        switch ($exception->getStatusCode()) {
            case 404:
                $title = 'Sorry, the page you are looking for could not be found.';
                break;
            default:
                $title = 'Whoops, looks like something went wrong.';
        }

        $content = '';
        if ($this->debug) {
            try {
                $count = count($exception->getAllPrevious());
                $total = $count + 1;
                foreach ($exception->toArray() as $position => $e) {
                    $ind = $count - $position + 1;
                    $class = $this->formatClass($e['class']);
                    $message = nl2br($this->escapeHtml($e['message']));
                    $content .= sprintf(<<<'EOF'
                        <div class="trace trace-as-html">
                            <table class="trace-details">
                                <thead class="trace-head"><tr><th>
                                    <h3 class="trace-class">
                                        <span class="text-muted">(%d/%d)</span>
                                        <span class="exception_title">%s</span>
                                    </h3>
                                    <p class="break-long-words trace-message">%s</p>
                                </th></tr></thead>
                                <tbody>
EOF
                        , $ind, $total, $class, $message);
                    foreach ($e['trace'] as $trace) {
                        $content .= '<tr><td>';
                        if ($trace['function']) {
                            $content .= sprintf('at <span class="trace-class">%s</span><span class="trace-type">%s</span><span class="trace-method">%s</span>(<span class="trace-arguments">%s</span>)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
                        }
                        if (isset($trace['file']) && isset($trace['line'])) {
                            $content .= $this->formatPath($trace['file'], $trace['line']);
                        }
                        $content .= "</td></tr>\n";
                    }

                    $content .= "</tbody>\n</table>\n</div>\n";
                }
            } catch (\Exception $e) {
                // something nasty happened and we cannot throw an exception anymore
                if ($this->debug) {
                    $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $this->escapeHtml($e->getMessage()));
                } else {
                    $title = 'Whoops, looks like something went wrong.';
                }
            }
        }

        $symfonyGhostImageContents = $this->getSymfonyGhostAsSvg();

        return <<<EOF
            <div class="exception-summary">
                <div class="container">
                    <div class="exception-message-wrapper">
                        <h1 class="break-long-words exception-message">$title</h1>
                        <div class="exception-illustration hidden-xs-down">$symfonyGhostImageContents</div>
                    </div>
                </div>
            </div>

            <div class="container">
                $content
            </div>
EOF;
    }

    /**
     * Gets the stylesheet associated with the given exception.
     *
     * @return string The stylesheet as a string
     */
    public function getStylesheet(FlattenException $exception)
    {
        return <<<'EOF'
            body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; }

            a { cursor: pointer; text-decoration: none; }
            a:hover { text-decoration: underline; }
            abbr[title] { border-bottom: none; cursor: help; text-decoration: none; }

            code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; }

            table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; }
            table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
            table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; }
            table th { background-color: #E0E0E0; font-weight: bold; text-align: left; }

            .hidden-xs-down { display: none; }
            .block { display: block; }
            .break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; }
            .text-muted { color: #999; }

            .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
            .container::after { content: ""; display: table; clear: both; }

            .exception-summary { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; }

            .exception-message-wrapper { display: flex; align-items: center; min-height: 70px; }
            .exception-message { flex-grow: 1; padding: 30px 0; }
            .exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; }
            .exception-message.long { font-size: 18px; }
            .exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; }
            .exception-message a:hover { border-bottom-color: #ffffff; }

            .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; }

            .trace + .trace { margin-top: 30px; }
            .trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; }

            .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; }

            .trace-file-path, .trace-file-path a { color: #222; margin-top: 3px; font-size: 13px; }
            .trace-class { color: #B0413E; }
            .trace-type { padding: 0 2px; }
            .trace-method { color: #B0413E; font-weight: bold; }
            .trace-arguments { color: #777; font-weight: normal; padding-left: 2px; }

            @media (min-width: 575px) {
                .hidden-xs-down { display: initial; }
            }
EOF;
    }

    private function decorate($content, $css)
    {
        return <<<EOF
<!DOCTYPE html>
<html>
    <head>
        <meta charset="{$this->charset}" />
        <meta name="robots" content="noindex,nofollow" />
        <style>$css</style>
    </head>
    <body>
        $content
    </body>
</html>
EOF;
    }

    private function formatClass($class)
    {
        $parts = explode('\\', $class);

        return sprintf('<abbr title="%s">%s</abbr>', $class, array_pop($parts));
    }

    private function formatPath($path, $line)
    {
        $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path);
        $fmt = $this->fileLinkFormat;

        if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $path, '%l' => $line)) : $fmt->format($path, $line)) {
            return sprintf('<span class="block trace-file-path">in <a href="%s" title="Go to source">%s (line %d)</a></span>', $this->escapeHtml($link), $file, $line);
        }

        return sprintf('<span class="block trace-file-path">in <a title="%s line %3$d"><strong>%s</strong> (line %d)</a></span>', $this->escapeHtml($path), $file, $line);
    }

    /**
     * Formats an array as a string.
     *
     * @param array $args The argument array
     *
     * @return string
     */
    private function formatArgs(array $args)
    {
        $result = array();
        foreach ($args as $key => $item) {
            if ('object' === $item[0]) {
                $formattedValue = sprintf('<em>object</em>(%s)', $this->formatClass($item[1]));
            } elseif ('array' === $item[0]) {
                $formattedValue = sprintf('<em>array</em>(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
            } elseif ('null' === $item[0]) {
                $formattedValue = '<em>null</em>';
            } elseif ('boolean' === $item[0]) {
                $formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>';
            } elseif ('resource' === $item[0]) {
                $formattedValue = '<em>resource</em>';
            } else {
                $formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true)));
            }

            $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue);
        }

        return implode(', ', $result);
    }

    /**
     * HTML-encodes a string.
     */
    private function escapeHtml($str)
    {
        return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset);
    }

    private function getSymfonyGhostAsSvg()
    {
        return '<svg viewBox="0 0 136 81" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M92.358 20.403a23.168 23.168 0 0 1 9.003 1.881 23.67 23.67 0 0 1 5.197 3.079 24.257 24.257 0 0 1 3.457 3.296 24.771 24.771 0 0 1 5.042 9.396c.486 1.72.78 3.492.895 5.28l.008.142.028.158.015.246v13.875c.116.034.232.065.348.098.193.054.383.116.577.168.487.125.989.191 1.49.215.338.016.689.023 1.021-.059.021-.005.032-.029.048-.044l.095-.1c.243-.265.461-.552.663-.851.277-.408.523-.837.746-1.279l.042-.087c-.066-.012-.131-.026-.197-.04l-.099-.023a5.536 5.536 0 0 1-.694-.242 5.649 5.649 0 0 1-2.374-1.845 5.694 5.694 0 0 1-.824-1.594 6.514 6.514 0 0 1-.267-2.781c.045-.394.126-.779.233-1.159.079-.278.162-.562.307-.812.094-.163.129-.196.247-.341l.79-.882c.143-.143.174-.186.34-.303.249-.174.536-.289.834-.333.074-.011.15-.014.224-.02l1.188-.037c.173.004.217-.002.388.028s.211.05.375.105l.018.007c.059.026.119.05.176.079.151.076.179.104.313.2l.006-.021c.073-.187.084-.238.187-.41.077-.129.167-.249.27-.357.051-.054.108-.103.162-.154l1.124-.95c.14-.107.172-.14.327-.224.155-.085.199-.094.363-.154l.019-.006c.169-.043.211-.06.385-.077.174-.016.218-.007.392.003l1.446.158c.193.033.244.033.43.098.278.097.534.259.744.47.053.053.1.112.149.167l.923 1.158.149.213.028.054.017-.014.184-.125c.196-.104.196-.104.402-.184l1.386-.451c.064-.018.126-.038.19-.052.129-.028.259-.042.39-.043.16-.002.321.017.478.047.364.069.711.21 1.032.396.162.094.316.199.469.308.088.063.176.132.27.188l.021.011c.19.123.245.146.409.305.185.178.336.393.443.63.035.079.061.162.091.243l.439 1.428c.045.175.062.219.081.4.02.193.006.381-.015.573a7.79 7.79 0 0 1-.101.645c-.09.455-.212.901-.365 1.339-.128.366-.273.73-.445 1.077-.658 1.335-1.652 2.512-2.917 3.265a6.399 6.399 0 0 1-1.019.489 6.097 6.097 0 0 1-.631.203c-.226.058-.455.1-.686.134l-.096.012-.061.007c-.01.176-.022.352-.036.528-.034.39-.082.778-.153 1.163a14.258 14.258 0 0 1-.574 2.114c-.229.654-.484 1.306-.806 1.918a9.16 9.16 0 0 1-.386.656c-.219.348-.451.686-.697 1.013-.448.594-.946 1.148-1.521 1.614-.255.207-.52.397-.808.553-.9.489-1.919.648-2.921.735-.493.038-.986.059-1.478.099-.162.015-.324.033-.486.049-.145.011-.289.022-.434.03a15.768 15.768 0 0 1-2.778-.118c0 1.416.007 2.832-.001 4.248a9.737 9.737 0 0 1-.684 3.479 9.615 9.615 0 0 1-1.72 2.804 9.326 9.326 0 0 1-3.04 2.279 9.046 9.046 0 0 1-5.33.715 9.064 9.064 0 0 1-2.988-1.079 9.363 9.363 0 0 1-2.761-2.429 10.078 10.078 0 0 1-1.05 1.16 9.281 9.281 0 0 1-1.871 1.358 9.033 9.033 0 0 1-2.495.926 9.04 9.04 0 0 1-6.462-1.072 9.395 9.395 0 0 1-2.602-2.292l-.062-.08a10.896 10.896 0 0 1-.53.635 9.266 9.266 0 0 1-2.671 2.032 9.028 9.028 0 0 1-6.044.751 9.048 9.048 0 0 1-2.436-.934 9.343 9.343 0 0 1-2.286-1.803 9.572 9.572 0 0 1-1.783-2.757 9.705 9.705 0 0 1-.773-3.693V67.244c-.157.024-.314.047-.472.067-.487.06-.977.103-1.469.109-.313.004-.627-.009-.94-.028-.426-.025-.85-.065-1.273-.125-1.833-.264-3.65-.92-5.109-2.117a8.172 8.172 0 0 1-1.064-1.049 10.155 10.155 0 0 1-.878-1.236 15.277 15.277 0 0 1-.7-1.274 20.835 20.835 0 0 1-1.889-6.194l-.018-.142-.008-.061a6.47 6.47 0 0 1-.99-.297 6.135 6.135 0 0 1-.61-.285 6.587 6.587 0 0 1-.889-.562c-1.228-.924-2.124-2.259-2.668-3.711a9.947 9.947 0 0 1-.307-.99 10.288 10.288 0 0 1-.318-1.923c-.009-.147-.011-.293-.015-.44v-.037c.008-.175.004-.22.037-.393.033-.173.053-.213.11-.378l.561-1.417c.031-.068.06-.139.095-.206a2.028 2.028 0 0 1 .771-.803c.093-.054.194-.095.289-.145l.311-.179c.352-.194.714-.358 1.107-.44.213-.044.426-.061.643-.061l.034.001c.177.014.223.01.396.052.174.041.214.065.379.132l1.347.635c.073.04.15.076.221.121.142.091.272.2.388.325.154.166.176.222.297.414l.022.047.722-.762.168-.158c.165-.122.202-.161.385-.253.206-.102.429-.168.656-.193.076-.008.152-.008.228-.011l1.46.013c.177.011.223.007.397.046.175.038.215.061.381.126l.018.008c.154.08.196.094.338.196.142.102.169.137.294.259l.853.912.152-.067.191-.063.019-.005.196-.042c.177-.019.222-.031.401-.022.066.003.133.013.199.02l1.185.182c.073.016.147.027.219.047.288.08.558.227.784.428.151.135.177.181.303.339l.714 1.004c.097.152.127.187.201.352.077.172.123.352.164.536.029.134.056.269.08.404.063.361.102.725.112 1.091.021.78-.08 1.566-.321 2.307a5.906 5.906 0 0 1-.532 1.183 5.463 5.463 0 0 1-3.257 2.489l-.03.008c.195.584.433 1.155.712 1.701.215.422.453.833.735 1.211.026.035.026.034.053.068l.058.072c.056.024.113.042.171.06.319.09.653.121.982.14.488.027.978.013 1.461-.06.167-.028.333-.062.499-.089.134-.022.267-.042.401-.066l.28-.056c.154-.023.308-.049.462-.076l.115-.021V43.881c.011-.203.006-.203.042-.404a26.66 26.66 0 0 1 .226-2.241 24.737 24.737 0 0 1 5.72-12.577 24.204 24.204 0 0 1 3.457-3.296 23.653 23.653 0 0 1 4.937-2.966 23.215 23.215 0 0 1 5.604-1.681 23.703 23.703 0 0 1 3.958-.313zm-.287 2.042a21.169 21.169 0 0 0-8.012 1.622 21.636 21.636 0 0 0-4.799 2.766 22.233 22.233 0 0 0-3.205 2.985 22.705 22.705 0 0 0-4.897 9.196 23.383 23.383 0 0 0-.737 4.867h-.025v15.744c-.258.053-.258.052-.517.101-.28.051-.56.1-.841.144-.211.04-.421.079-.632.115l-.232.037-.411.078c-.116.02-.233.035-.348.057-.305.056-.609.11-.917.14a9.929 9.929 0 0 1-1.883-.017c-.514-.056-1.044-.155-1.51-.397a1.762 1.762 0 0 1-.33-.218 1.925 1.925 0 0 1-.234-.252 5.248 5.248 0 0 1-.174-.22 8.97 8.97 0 0 1-.582-.883 13.806 13.806 0 0 1-.941-1.971 14.348 14.348 0 0 1-.608-1.954 14.04 14.04 0 0 1-.169-.86l-.015-.11-.015-.109c.161-.007.16-.007.321-.016a12.793 12.793 0 0 0 1.413-.182 4.43 4.43 0 0 0 .28-.074 3.56 3.56 0 0 0 1.199-.616c.309-.244.576-.543.786-.88.163-.261.292-.544.387-.838.123-.378.192-.774.214-1.172a5.102 5.102 0 0 0-.024-.865 7.192 7.192 0 0 0-.145-.799l-.714-1.005-1.184-.182-.019.005-.946.758-.12 1.229a4.953 4.953 0 0 1 .111.455c.032.181.052.36.043.544a1.04 1.04 0 0 1-.056.303c-.11.301-.419.451-.696.548-.402.142-.813.25-1.229.339l.07-.648c.022-.191.047-.381.08-.57.036-.207.079-.413.152-.61.077-.211.182-.412.296-.605.044-.074.092-.146.135-.222.029-.048.031-.047.055-.098.016-.033.031-.064.045-.098l-.026-1.551-1.042-1.116-.018-.008-1.459-.014-1.022 1.079c-.049.128-.08.258-.111.393a5.274 5.274 0 0 0-.1.651 5.55 5.55 0 0 0-.031.466c-.009.687.104 1.37.294 2.028.11.382.262.753.402 1.123-.115-.029-.228-.06-.342-.092a9.526 9.526 0 0 1-1.176-.446c-.108-.05-.111-.048-.191-.097a1.921 1.921 0 0 1-.327-.249c-.416-.4-.589-.986-.671-1.55a5.643 5.643 0 0 1-.057-.549c-.007-.143-.006-.286-.007-.429-.001-.186.005-.372.011-.558l.001-.039-.567-1.446-1.347-.634c-.316-.008-.599.144-.867.299-.109.063-.218.126-.33.185a2.058 2.058 0 0 1-.125.061l-.042.019-.561 1.416c0 .209.014.416.036.624.04.377.106.75.196 1.118.076.309.164.616.275.913.415 1.109 1.093 2.146 2.043 2.838.234.171.485.317.746.442.183.088.371.161.565.22.263.079.532.13.803.17.296.045.594.075.892.095l.108.007c.004.151.01.302.017.453.011.177.023.353.038.529a18.13 18.13 0 0 0 .762 3.752c.239.76.522 1.505.857 2.225.23.494.483.977.767 1.44.288.469.608.915.989 1.308 1.001 1.028 2.324 1.648 3.687 1.976.643.155 1.298.243 1.955.287.311.021.622.036.933.033.418-.006.835-.041 1.25-.094.238-.03.477-.064.713-.11.117-.023.232-.053.348-.081.196-.048.392-.097.586-.151.147-.041.291-.094.436-.144.204-.069.408-.139.608-.217l.006-.003c0 2.207-.013 4.414.001 6.62a7.942 7.942 0 0 0 .13 1.32 7.545 7.545 0 0 0 2.383 4.243 7.23 7.23 0 0 0 2.258 1.372 7.094 7.094 0 0 0 7.012-1.164 7.504 7.504 0 0 0 2.035-2.613 7.727 7.727 0 0 0 .676-2.401l.009-.088.038-.765a8.16 8.16 0 0 0 .113 1.324c.121.694.338 1.37.643 2.001a7.49 7.49 0 0 0 1.692 2.275 7.266 7.266 0 0 0 2.24 1.399 7.11 7.11 0 0 0 4.615.19 7.212 7.212 0 0 0 2.351-1.218 7.501 7.501 0 0 0 2.128-2.64 7.763 7.763 0 0 0 .702-2.39l.01-.088.009-.088.038-.765a9.339 9.339 0 0 0 .021.575 7.626 7.626 0 0 0 .621 2.504 7.507 7.507 0 0 0 2.35 2.972 7.1 7.1 0 0 0 7.026.881 7.275 7.275 0 0 0 2.268-1.515 7.525 7.525 0 0 0 1.612-2.338 7.58 7.58 0 0 0 .572-2.033c.048-.347.069-.696.071-1.046v-6.721c.136.051.271.101.408.148a12.153 12.153 0 0 0 1.976.443c.264.035.529.055.794.071.33.02.66.031.991.027.245-.002.49-.012.735-.031.245-.018.49-.048.735-.068.407-.03.814-.051 1.221-.079a9.493 9.493 0 0 0 1.384-.188c.315-.073.626-.174.912-.329a3.53 3.53 0 0 0 .586-.418c.46-.386.85-.85 1.205-1.337a12.178 12.178 0 0 0 .801-1.246c.122-.232.229-.471.33-.712a15.873 15.873 0 0 0 .681-1.988c.136-.525.23-1.058.282-1.598.035-.41.052-.822.088-1.232.03-.317.078-.632.121-.947l.018-.145.016-.145c.144.009.287.016.431.021.459.009.924.007 1.378-.07a4.456 4.456 0 0 0 1.353-.482c.989-.55 1.752-1.466 2.258-2.488.116-.235.214-.48.304-.727a7.58 7.58 0 0 0 .377-1.43c.016-.109.027-.218.039-.328l.001-.009-.438-1.428a5.206 5.206 0 0 1-.16-.096c-.158-.105-.311-.219-.467-.326a3.829 3.829 0 0 0-.159-.1 1.356 1.356 0 0 0-.509-.18l-.01-.001-1.386.452-.681 1.323c-.016.212-.023.424-.043.636a5.66 5.66 0 0 1-.139.873c-.118.494-.316.999-.702 1.338a1.865 1.865 0 0 1-.496.301l-.272.087a9.57 9.57 0 0 1-.83.205 8.797 8.797 0 0 1-.582.091l.229-.462c.079-.163.158-.325.229-.492.051-.118.096-.239.139-.36.036-.103.076-.209.103-.315.019-.075.031-.153.041-.229.017-.132.031-.263.043-.395.035-.368.06-.737.094-1.104.02-.187.048-.372.067-.559.015-.167.015-.336.012-.505a4.76 4.76 0 0 0-.074-.826c-.012-.065-.03-.13-.045-.194l-.003-.009-.923-1.157-1.446-.159-.019.006-1.124.95-.154 1.489c.011.034.024.066.037.099.044.115.107.221.161.331.046.096.088.193.13.29l.031.076c.013.033.017.07.023.105.012.096.022.191.031.287.031.364.047.73.081 1.093.013.102.028.202.04.303.014.145.027.29.033.435.014.28.016.561.023.841a9.588 9.588 0 0 1-.862-.323c-.063-.027-.128-.062-.193-.084a1.325 1.325 0 0 0-.067-.013c-.081-.01-.162-.017-.243-.025-.245-.02-.49-.037-.734-.061-.066-.007-.132-.014-.198-.028l-.017-.005c-.03-.013-.029-.014-.067-.038a1.614 1.614 0 0 1-.161-.108.863.863 0 0 1-.22-.242c-.089-.155-.102-.34-.09-.517.02-.299.117-.591.228-.866l.004-.009-.018-1.197-.874-.84-.018-.007-1.188.036-.79.882c-.037.112-.074.224-.106.338a4.756 4.756 0 0 0-.171 1.906c.039.329.115.654.233.963a3.542 3.542 0 0 0 1.263 1.636c.313.222.659.393 1.019.517.237.082.487.111.734.145.479.06.959.106 1.438.166.121.017.241.037.362.058l.158.026a12.12 12.12 0 0 1-.923 2.565 13.221 13.221 0 0 1-.829 1.474 9.474 9.474 0 0 1-.984 1.286c-.08.087-.163.17-.248.252a1.655 1.655 0 0 1-.329.262 2.376 2.376 0 0 1-.722.247c-.457.089-.927.093-1.39.071-.391-.018-.781-.06-1.168-.123a7.817 7.817 0 0 1-.609-.124c-.226-.056-.448-.124-.671-.191-.065-.019-.131-.035-.197-.054a14.75 14.75 0 0 1-.543-.165 23.384 23.384 0 0 1-.453-.128c-.196-.059-.195-.059-.39-.12l-.276-.077V43.881h-.025a34.633 34.633 0 0 0-.031-.557 23.606 23.606 0 0 0-.4-2.994 22.743 22.743 0 0 0-1.492-4.708 22.567 22.567 0 0 0-4.593-6.748 21.865 21.865 0 0 0-6.882-4.706 21.175 21.175 0 0 0-8.115-1.722l-.411-.001zm9.15 33.69c.109.015.214.038.315.085a1.012 1.012 0 0 1 .574.771c.021.132.013.268.009.4a8.38 8.38 0 0 1-.026.476 8.767 8.767 0 0 1-1.564 4.282c-.306.437-.65.846-1.024 1.222a10.09 10.09 0 0 1-4.612 2.627c-1.32.343-2.704.427-4.055.254a10.422 10.422 0 0 1-2.67-.709 9.917 9.917 0 0 1-3.57-2.503 9.312 9.312 0 0 1-.775-.984 8.933 8.933 0 0 1-.731-1.288 8.648 8.648 0 0 1-.795-3.377c-.003-.104-.008-.211 0-.316a1.042 1.042 0 0 1 .254-.609.98.98 0 0 1 1.337-.125 1.023 1.023 0 0 1 .385.719c.007.151.006.303.014.454a6.547 6.547 0 0 0 .524 2.217c.257.595.599 1.15 1.006 1.648.325.398.691.759 1.087 1.081.312.253.642.482.987.684 2.592 1.522 5.945 1.538 8.553.047a7.982 7.982 0 0 0 1.069-.731 7.619 7.619 0 0 0 1.142-1.15 6.949 6.949 0 0 0 1.018-1.741 6.538 6.538 0 0 0 .467-2.425l.004-.084a1.012 1.012 0 0 1 .672-.876c.08-.028.158-.04.241-.05.082-.003.082-.003.164.001zm-70.51-12.426c-15.5.93-28.544-5.922-30.126-16.443C-1.156 15.689 11.64 4.024 29.14 1.235c17.501-2.79 33.123 4.345 34.864 15.922 1.575 10.475-8.749 21.021-23.691 25.001l.001.099a31.185 31.185 0 0 0 .042.833c.007.094.019.188.021.282.006.178.013.356.024.534.011.16.024.32.039.48.017.154.038.306.058.459.036.273.077.544.144.811a4.723 4.723 0 0 0 .449 1.128c.192.332.434.628.702.898l.047.05c.151.139.302.275.461.403.24.192.492.367.748.537.474.314.962.6 1.457.877l.041.023.588.735-.729.586c-.376.112-.755.216-1.135.309a11.193 11.193 0 0 1-2.562.355 8.575 8.575 0 0 1-2.995-.486 8.461 8.461 0 0 1-.96-.413 11.194 11.194 0 0 1-1.836-1.152 13.345 13.345 0 0 1-1.07-.934c-.23-.221-.454-.448-.672-.681-.121-.129-.246-.258-.36-.395a23.448 23.448 0 0 1-1.328-1.773c-.051-.076-.049-.077-.095-.155l-.277-.477-.072-.13c-.081-.177-.159-.357-.238-.535l-.003-.01-.092-.707zm52.409-7.804c3.557 0 6.444 3.201 6.444 7.145 0 3.944-2.887 7.146-6.444 7.146s-6.444-3.202-6.444-7.146 2.887-7.145 6.444-7.145zm18.062 0c3.557 0 6.444 3.201 6.444 7.145 0 3.944-2.887 7.146-6.444 7.146s-6.444-3.202-6.444-7.146 2.887-7.145 6.444-7.145zM83.12 42.029c1.915 0 3.47 1.601 3.47 3.573s-1.555 3.573-3.47 3.573c-1.915 0-3.47-1.601-3.47-3.573s1.555-3.573 3.47-3.573zm17.846 0c1.915 0 3.47 1.601 3.47 3.573s-1.555 3.573-3.47 3.573c-1.915 0-3.47-1.601-3.47-3.573s1.555-3.573 3.47-3.573zM17.019 28c-.368 1.65-1.848 5.008-5.178 5.799-2.572.611-4.153-.815-4.544-2.559-.424-1.891.722-3.532 2.121-4.575a3.473 3.473 0 0 1-1.446-2.099c-.421-1.875.867-3.637 3.184-4.187 1.917-.455 3.185.248 3.462 1.482.265 1.184-.534 2.275-1.828 2.582-.878.209-1.574-.042-1.718-.683a1.4 1.4 0 0 1 .044-.704s.287.227.894.083c.751-.179 1.086-.709.972-1.219-.14-.625-.892-.827-1.739-.626-1.054.25-2.06 1.096-1.713 2.642.232 1.036.871 1.56 1.483 1.813.245-.11.481-.183.688-.233.943-.224 1.48-.005 1.587.472.092.411-.144.935-1.166 1.178a3.255 3.255 0 0 1-1.548.004c-.837.771-1.58 1.883-1.27 3.264.276 1.234 1.267 2.125 2.944 1.726 2.598-.617 3.861-3.638 4.277-4.883-.353-.574-.615-1.153-.732-1.676-.107-.477.145-1.005.863-1.175.48-.114.702.127.846.769a2.77 2.77 0 0 1-.03.995c.209.331.443.622.735.951.616-1.983 1.369-3.877 1.737-3.964.591-.141 1.492.65 1.492.65-.815.644-1.689 2.376-2.333 4.158.804.658 1.627 1.103 2.139.982.43-.102.735-.577.95-1.151-.323-2.226.975-4.331 2.31-4.648.703-.167 1.257.204 1.39.796.114.51-.044 1.379-.854 1.745-.236-1.053-.672-1.348-.944-1.283-.495.117-.844 1.413-.538 2.778.232 1.037.712 1.529 1.351 1.377.756-.179 1.333-1.176 1.699-2.128-.265-2.095.877-4.166 2.221-4.486.671-.159 1.214.162 1.391.952.332 1.48-.986 2.885-2.173 3.444.265.734.673 1.053 1.281.909.96-.229 1.578-1.465 1.923-2.506-.125-1.267-.26-2.385-.406-3.035l-.055-.247s1.568-.286 1.778.652l.019.082c.238-.663.67-1.216 1.309-1.368.83-.197 1.526.504 1.755 1.524.497 2.22-.556 4.428-1.834 4.732-.368.087-.642.066-.883-.033.121 1.288.292 2.651.542 3.77.126.559.272 1.061.448 1.47-.464.11-1.797.392-1.978-.414-.16-.716-.342-3.206-.554-5.612-.504 1.107-1.311 2.192-2.441 2.46-1.008.24-1.685-.303-2.055-1.182-.491 1.082-1.281 2.148-2.381 2.409-.817.194-1.554-.117-1.988-1.013-.36.843-.875 1.555-1.54 1.713-.639.152-1.53-.295-2.4-1.024-.239.888-.384 1.668-.39 2.241 0 0-.701.028-.804-.433-.096-.427.065-1.436.341-2.61a10.315 10.315 0 0 1-.713-.848zm38.163-17.803c.068.157.185.527.266.889.424 1.892.37 4.451.739 6.42-.065.61-.387 3.077-1.352 3.307-.192.045-.333-.06-.422-.454-.14-.626-.091-1.607-.293-2.512-.258-1.152-.782-1.686-1.517-1.511-.767.182-1.287 1.016-1.643 2.054-.022-.099-.053-.386-.093-.567-.211-.938-1.779-.652-1.779-.652a6.2 6.2 0 0 1 .457 1.364c.07.31.119.618.155.921-.246.495-.637.996-1.225 1.135-.064.015-.128.031-.195.029a6.977 6.977 0 0 0-.126-.784c-.258-1.152-.871-2.011-1.526-1.855a.712.712 0 0 0-.423.291c-1.337.317-2.358 2.107-2.118 3.919-.214.889-.551 1.757-1.059 1.877-.415.099-.724-.452-1.03-1.817-.059-.263-.09-.706-.122-1.149.142-.64.177-1.237.081-1.665-.107-.477-.417-.733-.816-.638-.715.17-.909 1.75-.52 3.801-.238.92-.639 1.915-1.278 2.067-.464.11-.835-.27-1.012-1.059-.158-.708-.196-1.929-.236-3.08 1.201-.424 1.911-1.009 1.775-1.617-.114-.51-.739-.743-.739-.743s-.124.722-1.064 1.258c-.029-.582-.064-1.111-.137-1.44-.137-.609-.458-.914-1.688-.622.158.327.274.698.359 1.076.103.46.162.949.189 1.445-.611.128-.947.052-.947.052s-.1.457-.041.72c.078.345.432.348 1.026.224.02 1.364-.067 2.701.143 3.639.306 1.365 1.231 1.89 2.046 1.697.907-.216 1.539-1.275 1.914-2.36.407 1.245 1.031 1.955 1.951 1.736.731-.174 1.261-1.142 1.587-2.195.431.765 1.15 1.129 1.983.931 1.214-.289 1.742-1.54 1.835-2.775 0 0 .147-.018.243-.04.526-.125.949-.488 1.26-.915.04.788.053 1.518.194 2.146.111.493.339.612.595.552.495-.118 1.081-.881 1.081-.881a3.93 3.93 0 0 1-.383-1.035c-.284-1.267.317-3.541.988-3.7.208-.049.377.257.492.767.057.255.092.504.115.751l.098 1.469c.024.246.059.496.116.751.158.707.63 1.236 1.381 1.058 1.317-.313 2.07-2.634 2.178-3.956.228.157.536.175.909.086-.505-2.253.089-6.136-.298-7.864-.1-.444-1.001-.58-1.607-.583l-.467.037zM33.729 22.293c.415-.099.711.246.885 1.02.287 1.283-.222 2.616-.797 2.753-.191.045-.695-.025-.961-1.21-.025-.115-.051-.23-.061-.349.05-1.277.439-2.097.934-2.214zm-5.187.955c.271-.065.511.104.588.449.137.609-.338 1.345-1.275 1.966-.255-1.36.159-2.29.687-2.415zm18.032-.403c-.607.144-1.062-.458-1.239-1.248-.217-.97.001-2.097.644-2.457.001.155.038.32.075.484.147.658.554 1.497 1.268 1.83-.017.749-.253 1.273-.748 1.391zm9.877-1.654c.103.461.496.714 1.039.585.799-.19.973-.993.847-1.553-.125-.559-.461-.93-.988-.805-.543.13-1.108.836-.898 1.773zm-14.21-5.442c-.104-.461-.497-.714-1.056-.581-.783.186-.972.993-.847 1.552.126.56.461.93.908.824.56-.133 1.172-1.006.995-1.795z" fill="#fff" fill-opacity=".6"></path></svg>';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Symfony\Component\Debug\Exception\ContextErrorException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\Debug\Exception\SilencedErrorContext;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;

/**
 * A generic ErrorHandler for the PHP engine.
 *
 * Provides five bit fields that control how errors are handled:
 * - thrownErrors: errors thrown as \ErrorException
 * - loggedErrors: logged errors, when not @-silenced
 * - scopedErrors: errors thrown or logged with their local context
 * - tracedErrors: errors logged with their stack trace
 * - screamedErrors: never @-silenced errors
 *
 * Each error level can be logged by a dedicated PSR-3 logger object.
 * Screaming only applies to logging.
 * Throwing takes precedence over logging.
 * Uncaught exceptions are logged as E_ERROR.
 * E_DEPRECATED and E_USER_DEPRECATED levels never throw.
 * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
 * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
 * As errors have a performance cost, repeated errors are all logged, so that the developer
 * can see them and weight them as more important to fix than others of the same level.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class ErrorHandler
{
    private $levels = array(
        E_DEPRECATED => 'Deprecated',
        E_USER_DEPRECATED => 'User Deprecated',
        E_NOTICE => 'Notice',
        E_USER_NOTICE => 'User Notice',
        E_STRICT => 'Runtime Notice',
        E_WARNING => 'Warning',
        E_USER_WARNING => 'User Warning',
        E_COMPILE_WARNING => 'Compile Warning',
        E_CORE_WARNING => 'Core Warning',
        E_USER_ERROR => 'User Error',
        E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
        E_COMPILE_ERROR => 'Compile Error',
        E_PARSE => 'Parse Error',
        E_ERROR => 'Error',
        E_CORE_ERROR => 'Core Error',
    );

    private $loggers = array(
        E_DEPRECATED => array(null, LogLevel::INFO),
        E_USER_DEPRECATED => array(null, LogLevel::INFO),
        E_NOTICE => array(null, LogLevel::WARNING),
        E_USER_NOTICE => array(null, LogLevel::WARNING),
        E_STRICT => array(null, LogLevel::WARNING),
        E_WARNING => array(null, LogLevel::WARNING),
        E_USER_WARNING => array(null, LogLevel::WARNING),
        E_COMPILE_WARNING => array(null, LogLevel::WARNING),
        E_CORE_WARNING => array(null, LogLevel::WARNING),
        E_USER_ERROR => array(null, LogLevel::CRITICAL),
        E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL),
        E_COMPILE_ERROR => array(null, LogLevel::CRITICAL),
        E_PARSE => array(null, LogLevel::CRITICAL),
        E_ERROR => array(null, LogLevel::CRITICAL),
        E_CORE_ERROR => array(null, LogLevel::CRITICAL),
    );

    private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
    private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
    private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
    private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
    private $loggedErrors = 0;
    private $traceReflector;

    private $isRecursive = 0;
    private $isRoot = false;
    private $exceptionHandler;
    private $bootstrappingLogger;

    private static $reservedMemory;
    private static $stackedErrors = array();
    private static $stackedErrorLevels = array();
    private static $toStringException = null;
    private static $silencedErrorCache = array();
    private static $silencedErrorCount = 0;
    private static $exitCode = 0;

    /**
     * Registers the error handler.
     *
     * @param self|null $handler The handler to register
     * @param bool      $replace Whether to replace or not any existing handler
     *
     * @return self The registered error handler
     */
    public static function register(self $handler = null, $replace = true)
    {
        if (null === self::$reservedMemory) {
            self::$reservedMemory = str_repeat('x', 10240);
            register_shutdown_function(__CLASS__.'::handleFatalError');
        }

        if ($handlerIsNew = null === $handler) {
            $handler = new static();
        }

        if (null === $prev = set_error_handler(array($handler, 'handleError'))) {
            restore_error_handler();
            // Specifying the error types earlier would expose us to https://bugs.php.net/63206
            set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors);
            $handler->isRoot = true;
        }

        if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) {
            $handler = $prev[0];
            $replace = false;
        }
        if (!$replace && $prev) {
            restore_error_handler();
            $handlerIsRegistered = is_array($prev) && $handler === $prev[0];
        } else {
            $handlerIsRegistered = true;
        }
        if (is_array($prev = set_exception_handler(array($handler, 'handleException'))) && $prev[0] instanceof self) {
            restore_exception_handler();
            if (!$handlerIsRegistered) {
                $handler = $prev[0];
            } elseif ($handler !== $prev[0] && $replace) {
                set_exception_handler(array($handler, 'handleException'));
                $p = $prev[0]->setExceptionHandler(null);
                $handler->setExceptionHandler($p);
                $prev[0]->setExceptionHandler($p);
            }
        } else {
            $handler->setExceptionHandler($prev);
        }

        $handler->throwAt(E_ALL & $handler->thrownErrors, true);

        return $handler;
    }

    public function __construct(BufferingLogger $bootstrappingLogger = null)
    {
        if ($bootstrappingLogger) {
            $this->bootstrappingLogger = $bootstrappingLogger;
            $this->setDefaultLogger($bootstrappingLogger);
        }
        $this->traceReflector = new \ReflectionProperty('Exception', 'trace');
        $this->traceReflector->setAccessible(true);
    }

    /**
     * Sets a logger to non assigned errors levels.
     *
     * @param LoggerInterface $logger  A PSR-3 logger to put as default for the given levels
     * @param array|int       $levels  An array map of E_* to LogLevel::* or an integer bit field of E_* constants
     * @param bool            $replace Whether to replace or not any existing logger
     */
    public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false)
    {
        $loggers = array();

        if (is_array($levels)) {
            foreach ($levels as $type => $logLevel) {
                if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
                    $loggers[$type] = array($logger, $logLevel);
                }
            }
        } else {
            if (null === $levels) {
                $levels = E_ALL;
            }
            foreach ($this->loggers as $type => $log) {
                if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
                    $log[0] = $logger;
                    $loggers[$type] = $log;
                }
            }
        }

        $this->setLoggers($loggers);
    }

    /**
     * Sets a logger for each error level.
     *
     * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
     *
     * @return array The previous map
     *
     * @throws \InvalidArgumentException
     */
    public function setLoggers(array $loggers)
    {
        $prevLogged = $this->loggedErrors;
        $prev = $this->loggers;
        $flush = array();

        foreach ($loggers as $type => $log) {
            if (!isset($prev[$type])) {
                throw new \InvalidArgumentException('Unknown error type: '.$type);
            }
            if (!is_array($log)) {
                $log = array($log);
            } elseif (!array_key_exists(0, $log)) {
                throw new \InvalidArgumentException('No logger provided');
            }
            if (null === $log[0]) {
                $this->loggedErrors &= ~$type;
            } elseif ($log[0] instanceof LoggerInterface) {
                $this->loggedErrors |= $type;
            } else {
                throw new \InvalidArgumentException('Invalid logger provided');
            }
            $this->loggers[$type] = $log + $prev[$type];

            if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) {
                $flush[$type] = $type;
            }
        }
        $this->reRegister($prevLogged | $this->thrownErrors);

        if ($flush) {
            foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
                $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR;
                if (!isset($flush[$type])) {
                    $this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
                } elseif ($this->loggers[$type][0]) {
                    $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]);
                }
            }
        }

        return $prev;
    }

    /**
     * Sets a user exception handler.
     *
     * @param callable $handler A handler that will be called on Exception
     *
     * @return callable|null The previous exception handler
     */
    public function setExceptionHandler(callable $handler = null)
    {
        $prev = $this->exceptionHandler;
        $this->exceptionHandler = $handler;

        return $prev;
    }

    /**
     * Sets the PHP error levels that throw an exception when a PHP error occurs.
     *
     * @param int  $levels  A bit field of E_* constants for thrown errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function throwAt($levels, $replace = false)
    {
        $prev = $this->thrownErrors;
        $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
        if (!$replace) {
            $this->thrownErrors |= $prev;
        }
        $this->reRegister($prev | $this->loggedErrors);

        return $prev;
    }

    /**
     * Sets the PHP error levels for which local variables are preserved.
     *
     * @param int  $levels  A bit field of E_* constants for scoped errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function scopeAt($levels, $replace = false)
    {
        $prev = $this->scopedErrors;
        $this->scopedErrors = (int) $levels;
        if (!$replace) {
            $this->scopedErrors |= $prev;
        }

        return $prev;
    }

    /**
     * Sets the PHP error levels for which the stack trace is preserved.
     *
     * @param int  $levels  A bit field of E_* constants for traced errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function traceAt($levels, $replace = false)
    {
        $prev = $this->tracedErrors;
        $this->tracedErrors = (int) $levels;
        if (!$replace) {
            $this->tracedErrors |= $prev;
        }

        return $prev;
    }

    /**
     * Sets the error levels where the @-operator is ignored.
     *
     * @param int  $levels  A bit field of E_* constants for screamed errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function screamAt($levels, $replace = false)
    {
        $prev = $this->screamedErrors;
        $this->screamedErrors = (int) $levels;
        if (!$replace) {
            $this->screamedErrors |= $prev;
        }

        return $prev;
    }

    /**
     * Re-registers as a PHP error handler if levels changed.
     */
    private function reRegister($prev)
    {
        if ($prev !== $this->thrownErrors | $this->loggedErrors) {
            $handler = set_error_handler('var_dump');
            $handler = is_array($handler) ? $handler[0] : null;
            restore_error_handler();
            if ($handler === $this) {
                restore_error_handler();
                if ($this->isRoot) {
                    set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors);
                } else {
                    set_error_handler(array($this, 'handleError'));
                }
            }
        }
    }

    /**
     * Handles errors by filtering then logging them according to the configured bit fields.
     *
     * @param int    $type    One of the E_* constants
     * @param string $message
     * @param string $file
     * @param int    $line
     *
     * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself
     *
     * @throws \ErrorException When $this->thrownErrors requests so
     *
     * @internal
     */
    public function handleError($type, $message, $file, $line)
    {
        // Level is the current error reporting level to manage silent error.
        // Strong errors are not authorized to be silenced.
        $level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
        $log = $this->loggedErrors & $type;
        $throw = $this->thrownErrors & $type & $level;
        $type &= $level | $this->screamedErrors;

        if (!$type || (!$log && !$throw)) {
            return $type && $log;
        }
        $scope = $this->scopedErrors & $type;

        if (4 < $numArgs = func_num_args()) {
            $context = $scope ? (func_get_arg(4) ?: array()) : array();
            $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM
        } else {
            $context = array();
            $backtrace = null;
        }

        if (isset($context['GLOBALS']) && $scope) {
            $e = $context;                  // Whatever the signature of the method,
            unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
            $context = $e;
        }

        if (null !== $backtrace && $type & E_ERROR) {
            // E_ERROR fatal errors are triggered on HHVM when
            // hhvm.error_handling.call_user_handler_on_fatals=1
            // which is the way to get their backtrace.
            $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace'));

            return true;
        }

        $logMessage = $this->levels[$type].': '.$message;

        if (null !== self::$toStringException) {
            $errorAsException = self::$toStringException;
            self::$toStringException = null;
        } elseif (!$throw && !($type & $level)) {
            if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) {
                $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), $type, $file, $line, false) : array();
                $errorAsException = new SilencedErrorContext($type, $file, $line, $lightTrace);
            } elseif (isset(self::$silencedErrorCache[$id][$message])) {
                $lightTrace = null;
                $errorAsException = self::$silencedErrorCache[$id][$message];
                ++$errorAsException->count;
            } else {
                $lightTrace = array();
                $errorAsException = null;
            }

            if (100 < ++self::$silencedErrorCount) {
                self::$silencedErrorCache = $lightTrace = array();
                self::$silencedErrorCount = 1;
            }
            if ($errorAsException) {
                self::$silencedErrorCache[$id][$message] = $errorAsException;
            }
            if (null === $lightTrace) {
                return;
            }
        } else {
            if ($scope) {
                $errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context);
            } else {
                $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
            }

            // Clean the trace by removing function arguments and the first frames added by the error handler itself.
            if ($throw || $this->tracedErrors & $type) {
                $backtrace = $backtrace ?: $errorAsException->getTrace();
                $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
                $this->traceReflector->setValue($errorAsException, $lightTrace);
            } else {
                $this->traceReflector->setValue($errorAsException, array());
            }
        }

        if ($throw) {
            if (E_USER_ERROR & $type) {
                for ($i = 1; isset($backtrace[$i]); ++$i) {
                    if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
                        && '__toString' === $backtrace[$i]['function']
                        && '->' === $backtrace[$i]['type']
                        && !isset($backtrace[$i - 1]['class'])
                        && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
                    ) {
                        // Here, we know trigger_error() has been called from __toString().
                        // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
                        // A small convention allows working around the limitation:
                        // given a caught $e exception in __toString(), quitting the method with
                        // `return trigger_error($e, E_USER_ERROR);` allows this error handler
                        // to make $e get through the __toString() barrier.

                        foreach ($context as $e) {
                            if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
                                if (1 === $i) {
                                    // On HHVM
                                    $errorAsException = $e;
                                    break;
                                }
                                self::$toStringException = $e;

                                return true;
                            }
                        }

                        if (1 < $i) {
                            // On PHP (not on HHVM), display the original error message instead of the default one.
                            $this->handleException($errorAsException);

                            // Stop the process by giving back the error to the native handler.
                            return false;
                        }
                    }
                }
            }

            throw $errorAsException;
        }

        if ($this->isRecursive) {
            $log = 0;
        } elseif (self::$stackedErrorLevels) {
            self::$stackedErrors[] = array(
                $this->loggers[$type][0],
                ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG,
                $logMessage,
                $errorAsException ? array('exception' => $errorAsException) : array(),
            );
        } else {
            try {
                $this->isRecursive = true;
                $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG;
                $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? array('exception' => $errorAsException) : array());
            } finally {
                $this->isRecursive = false;
            }
        }

        return $type && $log;
    }

    /**
     * Handles an exception by logging then forwarding it to another handler.
     *
     * @param \Exception|\Throwable $exception An exception to handle
     * @param array                 $error     An array as returned by error_get_last()
     *
     * @internal
     */
    public function handleException($exception, array $error = null)
    {
        if (null === $error) {
            self::$exitCode = 255;
        }
        if (!$exception instanceof \Exception) {
            $exception = new FatalThrowableError($exception);
        }
        $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
        $handlerException = null;

        if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
            if ($exception instanceof FatalErrorException) {
                if ($exception instanceof FatalThrowableError) {
                    $error = array(
                        'type' => $type,
                        'message' => $message = $exception->getMessage(),
                        'file' => $exception->getFile(),
                        'line' => $exception->getLine(),
                    );
                } else {
                    $message = 'Fatal '.$exception->getMessage();
                }
            } elseif ($exception instanceof \ErrorException) {
                $message = 'Uncaught '.$exception->getMessage();
            } else {
                $message = 'Uncaught Exception: '.$exception->getMessage();
            }
        }
        if ($this->loggedErrors & $type) {
            try {
                $this->loggers[$type][0]->log($this->loggers[$type][1], $message, array('exception' => $exception));
            } catch (\Exception $handlerException) {
            } catch (\Throwable $handlerException) {
            }
        }
        if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
            foreach ($this->getFatalErrorHandlers() as $handler) {
                if ($e = $handler->handleError($error, $exception)) {
                    $exception = $e;
                    break;
                }
            }
        }
        try {
            if (null !== $this->exceptionHandler) {
                return \call_user_func($this->exceptionHandler, $exception);
            }
            $handlerException = $handlerException ?: $exception;
        } catch (\Exception $handlerException) {
        } catch (\Throwable $handlerException) {
        }
        $this->exceptionHandler = null;
        if ($exception === $handlerException) {
            self::$reservedMemory = null; // Disable the fatal error handler
            throw $exception; // Give back $exception to the native handler
        }
        $this->handleException($handlerException);
    }

    /**
     * Shutdown registered function for handling PHP fatal errors.
     *
     * @param array $error An array as returned by error_get_last()
     *
     * @internal
     */
    public static function handleFatalError(array $error = null)
    {
        if (null === self::$reservedMemory) {
            return;
        }

        $handler = self::$reservedMemory = null;
        $handlers = array();
        $previousHandler = null;
        $sameHandlerLimit = 10;

        while (!is_array($handler) || !$handler[0] instanceof self) {
            $handler = set_exception_handler('var_dump');
            restore_exception_handler();

            if (!$handler) {
                break;
            }
            restore_exception_handler();

            if ($handler !== $previousHandler) {
                array_unshift($handlers, $handler);
                $previousHandler = $handler;
            } elseif (0 === --$sameHandlerLimit) {
                $handler = null;
                break;
            }
        }
        foreach ($handlers as $h) {
            set_exception_handler($h);
        }
        if (!$handler) {
            return;
        }
        if ($handler !== $h) {
            $handler[0]->setExceptionHandler($h);
        }
        $handler = $handler[0];
        $handlers = array();

        if ($exit = null === $error) {
            $error = error_get_last();
        }

        try {
            while (self::$stackedErrorLevels) {
                static::unstackErrors();
            }
        } catch (\Exception $exception) {
            // Handled below
        } catch (\Throwable $exception) {
            // Handled below
        }

        if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
            // Let's not throw anymore but keep logging
            $handler->throwAt(0, true);
            $trace = isset($error['backtrace']) ? $error['backtrace'] : null;

            if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
                $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace);
            } else {
                $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
            }
        }

        try {
            if (isset($exception)) {
                self::$exitCode = 255;
                $handler->handleException($exception, $error);
            }
        } catch (FatalErrorException $e) {
            // Ignore this re-throw
        }

        if ($exit && self::$exitCode) {
            $exitCode = self::$exitCode;
            register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
        }
    }

    /**
     * Configures the error handler for delayed handling.
     * Ensures also that non-catchable fatal errors are never silenced.
     *
     * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
     * PHP has a compile stage where it behaves unusually. To workaround it,
     * we plug an error handler that only stacks errors for later.
     *
     * The most important feature of this is to prevent
     * autoloading until unstackErrors() is called.
     *
     * @deprecated since version 3.4, to be removed in 4.0.
     */
    public static function stackErrors()
    {
        @trigger_error('Support for stacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED);

        self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
    }

    /**
     * Unstacks stacked errors and forwards to the logger.
     *
     * @deprecated since version 3.4, to be removed in 4.0.
     */
    public static function unstackErrors()
    {
        @trigger_error('Support for unstacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED);

        $level = array_pop(self::$stackedErrorLevels);

        if (null !== $level) {
            $errorReportingLevel = error_reporting($level);
            if ($errorReportingLevel !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
                // If the user changed the error level, do not overwrite it
                error_reporting($errorReportingLevel);
            }
        }

        if (empty(self::$stackedErrorLevels)) {
            $errors = self::$stackedErrors;
            self::$stackedErrors = array();

            foreach ($errors as $error) {
                $error[0]->log($error[1], $error[2], $error[3]);
            }
        }
    }

    /**
     * Gets the fatal error handlers.
     *
     * Override this method if you want to define more fatal error handlers.
     *
     * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
     */
    protected function getFatalErrorHandlers()
    {
        return array(
            new UndefinedFunctionFatalErrorHandler(),
            new UndefinedMethodFatalErrorHandler(),
            new ClassNotFoundFatalErrorHandler(),
        );
    }

    private function cleanTrace($backtrace, $type, $file, $line, $throw)
    {
        $lightTrace = $backtrace;

        for ($i = 0; isset($backtrace[$i]); ++$i) {
            if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) {
                $lightTrace = array_slice($lightTrace, 1 + $i);
                break;
            }
        }
        if (!($throw || $this->scopedErrors & $type)) {
            for ($i = 0; isset($lightTrace[$i]); ++$i) {
                unset($lightTrace[$i]['args'], $lightTrace[$i]['object']);
            }
        }

        return $lightTrace;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

/**
 * Registers all the debug tools.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Debug
{
    private static $enabled = false;

    /**
     * Enables the debug tools.
     *
     * This method registers an error handler and an exception handler.
     *
     * If the Symfony ClassLoader component is available, a special
     * class loader is also registered.
     *
     * @param int  $errorReportingLevel The level of error reporting you want
     * @param bool $displayErrors       Whether to display errors (for development) or just log them (for production)
     */
    public static function enable($errorReportingLevel = E_ALL, $displayErrors = true)
    {
        if (static::$enabled) {
            return;
        }

        static::$enabled = true;

        if (null !== $errorReportingLevel) {
            error_reporting($errorReportingLevel);
        } else {
            error_reporting(E_ALL);
        }

        if ('cli' !== PHP_SAPI) {
            ini_set('display_errors', 0);
            ExceptionHandler::register();
        } elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) {
            // CLI - display errors only if they're not already logged to STDERR
            ini_set('display_errors', 1);
        }
        if ($displayErrors) {
            ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
        } else {
            ErrorHandler::register()->throwAt(0, true);
        }

        DebugClassLoader::enable();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\ClassNotFoundException;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\DebugClassLoader;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;

/**
 * ErrorHandler for classes that do not exist.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public function handleError(array $error, FatalErrorException $exception)
    {
        $messageLen = strlen($error['message']);
        $notFoundSuffix = '\' not found';
        $notFoundSuffixLen = strlen($notFoundSuffix);
        if ($notFoundSuffixLen > $messageLen) {
            return;
        }

        if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
            return;
        }

        foreach (array('class', 'interface', 'trait') as $typeName) {
            $prefix = ucfirst($typeName).' \'';
            $prefixLen = strlen($prefix);
            if (0 !== strpos($error['message'], $prefix)) {
                continue;
            }

            $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
            if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
                $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
                $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
                $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
                $tail = ' for another namespace?';
            } else {
                $className = $fullyQualifiedClassName;
                $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
                $tail = '?';
            }

            if ($candidates = $this->getClassCandidates($className)) {
                $tail = array_pop($candidates).'"?';
                if ($candidates) {
                    $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
                } else {
                    $tail = ' for "'.$tail;
                }
            }
            $message .= "\nDid you forget a \"use\" statement".$tail;

            return new ClassNotFoundException($message, $exception);
        }
    }

    /**
     * Tries to guess the full namespace for a given class name.
     *
     * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
     * autoloader (that should cover all common cases).
     *
     * @param string $class A class name (without its namespace)
     *
     * @return array An array of possible fully qualified class names
     */
    private function getClassCandidates($class)
    {
        if (!is_array($functions = spl_autoload_functions())) {
            return array();
        }

        // find Symfony and Composer autoloaders
        $classes = array();

        foreach ($functions as $function) {
            if (!is_array($function)) {
                continue;
            }
            // get class loaders wrapped by DebugClassLoader
            if ($function[0] instanceof DebugClassLoader) {
                $function = $function[0]->getClassLoader();

                if (!is_array($function)) {
                    continue;
                }
            }

            if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
                foreach ($function[0]->getPrefixes() as $prefix => $paths) {
                    foreach ($paths as $path) {
                        $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
                    }
                }
            }
            if ($function[0] instanceof ComposerClassLoader) {
                foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
                    foreach ($paths as $path) {
                        $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
                    }
                }
            }
        }

        return array_unique($classes);
    }

    /**
     * @param string $path
     * @param string $class
     * @param string $prefix
     *
     * @return array
     */
    private function findClassInPath($path, $class, $prefix)
    {
        if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
            return array();
        }

        $classes = array();
        $filename = $class.'.php';
        foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
            if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
                $classes[] = $class;
            }
        }

        return $classes;
    }

    /**
     * @param string $path
     * @param string $file
     * @param string $prefix
     *
     * @return string|null
     */
    private function convertFileToClass($path, $file, $prefix)
    {
        $candidates = array(
            // namespaced class
            $namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
            // namespaced class (with target dir)
            $prefix.$namespacedClass,
            // namespaced class (with target dir and separator)
            $prefix.'\\'.$namespacedClass,
            // PEAR class
            str_replace('\\', '_', $namespacedClass),
            // PEAR class (with target dir)
            str_replace('\\', '_', $prefix.$namespacedClass),
            // PEAR class (with target dir and separator)
            str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
        );

        if ($prefix) {
            $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
        }

        // We cannot use the autoloader here as most of them use require; but if the class
        // is not found, the new autoloader call will require the file again leading to a
        // "cannot redeclare class" error.
        foreach ($candidates as $candidate) {
            if ($this->classExists($candidate)) {
                return $candidate;
            }
        }

        require_once $file;

        foreach ($candidates as $candidate) {
            if ($this->classExists($candidate)) {
                return $candidate;
            }
        }
    }

    /**
     * @param string $class
     *
     * @return bool
     */
    private function classExists($class)
    {
        return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedMethodException;

/**
 * ErrorHandler for undefined methods.
 *
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public function handleError(array $error, FatalErrorException $exception)
    {
        preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
        if (!$matches) {
            return;
        }

        $className = $matches[1];
        $methodName = $matches[2];

        $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);

        if (!class_exists($className) || null === $methods = get_class_methods($className)) {
            // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class)
            return new UndefinedMethodException($message, $exception);
        }

        $candidates = array();
        foreach ($methods as $definedMethodName) {
            $lev = levenshtein($methodName, $definedMethodName);
            if ($lev <= strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
                $candidates[] = $definedMethodName;
            }
        }

        if ($candidates) {
            sort($candidates);
            $last = array_pop($candidates).'"?';
            if ($candidates) {
                $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
            } else {
                $candidates = '"'.$last;
            }

            $message .= "\nDid you mean to call ".$candidates;
        }

        return new UndefinedMethodException($message, $exception);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\UndefinedFunctionException;
use Symfony\Component\Debug\Exception\FatalErrorException;

/**
 * ErrorHandler for undefined functions.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public function handleError(array $error, FatalErrorException $exception)
    {
        $messageLen = strlen($error['message']);
        $notFoundSuffix = '()';
        $notFoundSuffixLen = strlen($notFoundSuffix);
        if ($notFoundSuffixLen > $messageLen) {
            return;
        }

        if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
            return;
        }

        $prefix = 'Call to undefined function ';
        $prefixLen = strlen($prefix);
        if (0 !== strpos($error['message'], $prefix)) {
            return;
        }

        $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
        if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
            $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
            $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
            $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
        } else {
            $functionName = $fullyQualifiedFunctionName;
            $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
        }

        $candidates = array();
        foreach (get_defined_functions() as $type => $definedFunctionNames) {
            foreach ($definedFunctionNames as $definedFunctionName) {
                if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
                    $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
                } else {
                    $definedFunctionNameBasename = $definedFunctionName;
                }

                if ($definedFunctionNameBasename === $functionName) {
                    $candidates[] = '\\'.$definedFunctionName;
                }
            }
        }

        if ($candidates) {
            sort($candidates);
            $last = array_pop($candidates).'"?';
            if ($candidates) {
                $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
            } else {
                $candidates = '"'.$last;
            }
            $message .= "\nDid you mean to call ".$candidates;
        }

        return new UndefinedFunctionException($message, $exception);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\FatalErrorException;

/**
 * Attempts to convert fatal errors to exceptions.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface FatalErrorHandlerInterface
{
    /**
     * Attempts to convert an error into an exception.
     *
     * @param array               $error     An array as returned by error_get_last()
     * @param FatalErrorException $exception A FatalErrorException instance
     *
     * @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
     */
    public function handleError(array $error, FatalErrorException $exception);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

use Psr\Log\AbstractLogger;

/**
 * A buffering logger that stacks logs for later.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class BufferingLogger extends AbstractLogger
{
    private $logs = array();

    public function log($level, $message, array $context = array())
    {
        $this->logs[] = array($level, $message, $context);
    }

    public function cleanLogs()
    {
        $logs = $this->logs;
        $this->logs = array();

        return $logs;
    }
}
<?php
/**
 * Part of JsonMapper
 *
 * PHP version 5
 *
 * @category Netresearch
 * @package  JsonMapper
 * @author   Christian Weiske <cweiske@cweiske.de>
 * @license  OSL-3.0 http://opensource.org/licenses/osl-3.0
 * @link     http://cweiske.de/
 */

/**
 * Simple exception
 *
 * @category Netresearch
 * @package  JsonMapper
 * @author   Christian Weiske <cweiske@cweiske.de>
 * @license  OSL-3.0 http://opensource.org/licenses/osl-3.0
 * @link     http://cweiske.de/
 */
class JsonMapper_Exception extends Exception
{
}
?>
<?php
/**
 * Part of JsonMapper
 *
 * PHP version 5
 *
 * @category Netresearch
 * @package  JsonMapper
 * @author   Christian Weiske <cweiske@cweiske.de>
 * @license  OSL-3.0 http://opensource.org/licenses/osl-3.0
 * @link     http://cweiske.de/
 */

/**
 * Automatically map JSON structures into objects.
 *
 * @category Netresearch
 * @package  JsonMapper
 * @author   Christian Weiske <cweiske@cweiske.de>
 * @license  OSL-3.0 http://opensource.org/licenses/osl-3.0
 * @link     http://cweiske.de/
 */
class JsonMapper
{
    /**
     * PSR-3 compatible logger object
     *
     * @link http://www.php-fig.org/psr/psr-3/
     * @var  object
     * @see  setLogger()
     */
    protected $logger;

    /**
     * Throw an exception when JSON data contain a property
     * that is not defined in the PHP class
     *
     * @var boolean
     */
    public $bExceptionOnUndefinedProperty = false;

    /**
     * Throw an exception if the JSON data miss a property
     * that is marked with @required in the PHP class
     *
     * @var boolean
     */
    public $bExceptionOnMissingData = false;

    /**
     * If the types of map() parameters shall be checked.
     *
     * You have to disable it if you're using the json_decode "assoc" parameter.
     *
     *     json_decode($str, false)
     *
     * @var boolean
     */
    public $bEnforceMapType = true;

    /**
     * Throw an exception when an object is expected but the JSON contains
     * a non-object type.
     *
     * @var boolean
     */
    public $bStrictObjectTypeChecking = false;

    /**
     * Throw an exception, if null value is found
     * but the type of attribute does not allow nulls.
     *
     * @var bool
     */
    public $bStrictNullTypes = true;

    /**
     * Allow mapping of private and proteted properties.
     *
     * @var boolean
     */
    public $bIgnoreVisibility = false;

    /**
     * Override class names that JsonMapper uses to create objects.
     * Useful when your setter methods accept abstract classes or interfaces.
     *
     * @var array
     */
    public $classMap = array();

    /**
     * Callback used when an undefined property is found.
     *
     * Works only when $bExceptionOnUndefinedProperty is disabled.
     *
     * Parameters to this function are:
     * 1. Object that is being filled
     * 2. Name of the unknown JSON property
     * 3. JSON value of the property
     *
     * @var callable
     */
    public $undefinedPropertyHandler = null;

    /**
     * Runtime cache for inspected classes. This is particularly effective if
     * mapArray() is called with a large number of objects
     *
     * @var array property inspection result cache
     */
    protected $arInspectedClasses = array();

    /**
     * Map data all data in $json into the given $object instance.
     *
     * @param object $json   JSON object structure from json_decode()
     * @param object $object Object to map $json data into
     *
     * @return object Mapped object is returned.
     * @see    mapArray()
     */
    public function map($json, $object)
    {
        if ($this->bEnforceMapType && !is_object($json)) {
            throw new InvalidArgumentException(
                'JsonMapper::map() requires first argument to be an object'
                . ', ' . gettype($json) . ' given.'
            );
        }
        if (!is_object($object)) {
            throw new InvalidArgumentException(
                'JsonMapper::map() requires second argument to be an object'
                . ', ' . gettype($object) . ' given.'
            );
        }

        $strClassName = get_class($object);
        $rc = new ReflectionClass($object);
        $strNs = $rc->getNamespaceName();
        $providedProperties = array();
        foreach ($json as $key => $jvalue) {
            $key = $this->getSafeName($key);
            $providedProperties[$key] = true;

            // Store the property inspection results so we don't have to do it
            // again for subsequent objects of the same type
            if (!isset($this->arInspectedClasses[$strClassName][$key])) {
                $this->arInspectedClasses[$strClassName][$key]
                    = $this->inspectProperty($rc, $key);
            }

            list($hasProperty, $accessor, $type)
                = $this->arInspectedClasses[$strClassName][$key];

            if (!$hasProperty) {
                if ($this->bExceptionOnUndefinedProperty) {
                    throw new JsonMapper_Exception(
                        'JSON property "' . $key . '" does not exist'
                        . ' in object of type ' . $strClassName
                    );
                } else if ($this->undefinedPropertyHandler !== null) {
                    call_user_func(
                        $this->undefinedPropertyHandler,
                        $object, $key, $jvalue
                    );
                } else {
                    $this->log(
                        'info',
                        'Property {property} does not exist in {class}',
                        array('property' => $key, 'class' => $strClassName)
                    );
                }
                continue;
            }

            if ($accessor === null) {
                if ($this->bExceptionOnUndefinedProperty) {
                    throw new JsonMapper_Exception(
                        'JSON property "' . $key . '" has no public setter method'
                        . ' in object of type ' . $strClassName
                    );
                }
                $this->log(
                    'info',
                    'Property {property} has no public setter method in {class}',
                    array('property' => $key, 'class' => $strClassName)
                );
                continue;
            }

            if ($this->isNullable($type) || !$this->bStrictNullTypes) {
                if ($jvalue === null) {
                    $this->setProperty($object, $accessor, null);
                    continue;
                }
                $type = $this->removeNullable($type);
            } else if ($jvalue === null) {
                throw new JsonMapper_Exception(
                    'JSON property "' . $key . '" in class "'
                    . $strClassName . '" must not be NULL'
                );
            }

            if ($type === null || $type === 'mixed') {
                //no given type - simply set the json data
                $this->setProperty($object, $accessor, $jvalue);
                continue;
            } else if ($this->isObjectOfSameType($type, $jvalue)) {
                $this->setProperty($object, $accessor, $jvalue);
                continue;
            } else if ($this->isSimpleType($type)) {
                settype($jvalue, $type);
                $this->setProperty($object, $accessor, $jvalue);
                continue;
            }

            //FIXME: check if type exists, give detailed error message if not
            if ($type === '') {
                throw new JsonMapper_Exception(
                    'Empty type at property "'
                    . $strClassName . '::$' . $key . '"'
                );
            }

            $array = null;
            $subtype = null;
            if ($this->isArrayOfType($type)) {
                //array
                $array = array();
                $subtype = substr($type, 0, -2);
            } else if (substr($type, -1) == ']') {
                list($proptype, $subtype) = explode('[', substr($type, 0, -1));
                if (!$this->isSimpleType($proptype)) {
                    $proptype = $this->getFullNamespace($proptype, $strNs);
                }
                if ($proptype == 'array') {
                    $array = array();
                } else {
                    $array = $this->createInstance($proptype, false, $jvalue);
                }
            } else {
                $type = $this->getFullNamespace($type, $strNs);
                if (is_a($type, 'ArrayObject', true)) {
                    $array = $this->createInstance($type, false, $jvalue);
                }
            }

            if ($array !== null) {
                if (!is_array($jvalue) && $this->isFlatType(gettype($jvalue))) {
                    throw new JsonMapper_Exception(
                        'JSON property "' . $key . '" must be an array, '
                        . gettype($jvalue) . ' given'
                    );
                }

                $cleanSubtype = $this->removeNullable($subtype);
                if (!$this->isSimpleType($cleanSubtype)
                    && $cleanSubtype !== null
                ) {
                    $subtype = $this->getFullNamespace($cleanSubtype, $strNs);
                }
                $child = $this->mapArray($jvalue, $array, $subtype);
            } else if ($this->isFlatType(gettype($jvalue))) {
                //use constructor parameter if we have a class
                // but only a flat type (i.e. string, int)
                if ($this->bStrictObjectTypeChecking) {
                    throw new JsonMapper_Exception(
                        'JSON property "' . $key . '" must be an object, '
                        . gettype($jvalue) . ' given'
                    );
                }
                $type = $this->getFullNamespace($type, $strNs);
                $child = $this->createInstance($type, true, $jvalue);
            } else {
                $type = $this->getFullNamespace($type, $strNs);
                $child = $this->createInstance($type, false, $jvalue);
                $this->map($jvalue, $child);
            }
            $this->setProperty($object, $accessor, $child);
        }

        if ($this->bExceptionOnMissingData) {
            $this->checkMissingData($providedProperties, $rc);
        }

        return $object;
    }

    /**
     * Convert a type name to a fully namespaced type name.
     *
     * @param string $type  Type name (simple type or class name)
     * @param string $strNs Base namespace that gets prepended to the type name
     *
     * @return string Fully-qualified type name with namespace
     */
    protected function getFullNamespace($type, $strNs)
    {
        if ($type !== '' && $type{0} != '\\') {
            //create a full qualified namespace
            if ($strNs != '') {
                $type = '\\' . $strNs . '\\' . $type;
            }
        }
        return $type;
    }

    /**
     * Check required properties exist in json
     *
     * @param array  $providedProperties array with json properties
     * @param object $rc                 Reflection class to check
     *
     * @throws JsonMapper_Exception
     *
     * @return void
     */
    protected function checkMissingData($providedProperties, ReflectionClass $rc)
    {
        foreach ($rc->getProperties() as $property) {
            $rprop = $rc->getProperty($property->name);
            $docblock = $rprop->getDocComment();
            $annotations = $this->parseAnnotations($docblock);
            if (isset($annotations['required'])
                && !isset($providedProperties[$property->name])
            ) {
                throw new JsonMapper_Exception(
                    'Required property "' . $property->name . '" of class '
                    . $rc->getName()
                    . ' is missing in JSON data'
                );
            }
        }
    }

    /**
     * Map an array
     *
     * @param array  $json  JSON array structure from json_decode()
     * @param mixed  $array Array or ArrayObject that gets filled with
     *                      data from $json
     * @param string $class Class name for children objects.
     *                      All children will get mapped onto this type.
     *                      Supports class names and simple types
     *                      like "string" and nullability "string|null".
     *                      Pass "null" to not convert any values
     *
     * @return mixed Mapped $array is returned
     */
    public function mapArray($json, $array, $class = null)
    {
        foreach ($json as $key => $jvalue) {
            if ($class === null) {
                $array[$key] = $jvalue;
            } else if ($this->isArrayOfType($class)) {
                $array[$key] = $this->mapArray(
                    $jvalue,
                    array(),
                    substr($class, 0, -2)
                );
            } else if ($this->isFlatType(gettype($jvalue))) {
                //use constructor parameter if we have a class
                // but only a flat type (i.e. string, int)
                if ($jvalue === null) {
                    $array[$key] = null;
                } else {
                    if ($this->isSimpleType($class)) {
                        settype($jvalue, $class);
                        $array[$key] = $jvalue;
                    } else {
                        $array[$key] = $this->createInstance(
                            $class, true, $jvalue
                        );
                    }
                }
            } else if (is_a($class, 'ArrayObject', true)) {
                $array[$key] = $this->mapArray(
                    $jvalue,
                    $this->createInstance($class)
                );
            } else {
                $array[$key] = $this->map(
                    $jvalue, $this->createInstance($class, false, $jvalue)
                );
            }
        }
        return $array;
    }

    /**
     * Try to find out if a property exists in a given class.
     * Checks property first, falls back to setter method.
     *
     * @param object $rc   Reflection class to check
     * @param string $name Property name
     *
     * @return array First value: if the property exists
     *               Second value: the accessor to use (
     *                 ReflectionMethod or ReflectionProperty, or null)
     *               Third value: type of the property
     */
    protected function inspectProperty(ReflectionClass $rc, $name)
    {
        //try setter method first
        $setter = 'set' . $this->getCamelCaseName($name);

        if ($rc->hasMethod($setter)) {
            $rmeth = $rc->getMethod($setter);
            if ($rmeth->isPublic() || $this->bIgnoreVisibility) {
                $rparams = $rmeth->getParameters();
                if (count($rparams) > 0) {
                    $pclass = $rparams[0]->getClass();
                    $nullability = '';
                    if ($rparams[0]->allowsNull()) {
                        $nullability = '|null';
                    }
                    if ($pclass !== null) {
                        return array(
                            true, $rmeth,
                            '\\' . $pclass->getName() . $nullability
                        );
                    }
                }

                $docblock    = $rmeth->getDocComment();
                $annotations = $this->parseAnnotations($docblock);

                if (!isset($annotations['param'][0])) {
                    return array(true, $rmeth, null);
                }
                list($type) = explode(' ', trim($annotations['param'][0]));
                return array(true, $rmeth, $type);
            }
        }

        //now try to set the property directly
        if ($rc->hasProperty($name)) {
            $rprop = $rc->getProperty($name);
        } else {
            //case-insensitive property matching
            $rprop = null;
            foreach ($rc->getProperties() as $p) {
                if ((strcasecmp($p->name, $name) === 0)) {
                    $rprop = $p;
                    break;
                }
            }
        }
        if ($rprop !== null) {
            if ($rprop->isPublic() || $this->bIgnoreVisibility) {
                $docblock    = $rprop->getDocComment();
                $annotations = $this->parseAnnotations($docblock);

                if (!isset($annotations['var'][0])) {
                    return array(true, $rprop, null);
                }

                //support "@var type description"
                list($type) = explode(' ', $annotations['var'][0]);

                return array(true, $rprop, $type);
            } else {
                //no setter, private property
                return array(true, null, null);
            }
        }

        //no setter, no property
        return array(false, null, null);
    }

    /**
     * Removes - and _ and makes the next letter uppercase
     *
     * @param string $name Property name
     *
     * @return string CamelCasedVariableName
     */
    protected function getCamelCaseName($name)
    {
        return str_replace(
            ' ', '', ucwords(str_replace(array('_', '-'), ' ', $name))
        );
    }

    /**
     * Since hyphens cannot be used in variables we have to uppercase them.
     *
     * Technically you may use them, but they are awkward to access.
     *
     * @param string $name Property name
     *
     * @return string Name without hyphen
     */
    protected function getSafeName($name)
    {
        if (strpos($name, '-') !== false) {
            $name = $this->getCamelCaseName($name);
        }

        return $name;
    }

    /**
     * Set a property on a given object to a given value.
     *
     * Checks if the setter or the property are public are made before
     * calling this method.
     *
     * @param object $object   Object to set property on
     * @param object $accessor ReflectionMethod or ReflectionProperty
     * @param mixed  $value    Value of property
     *
     * @return void
     */
    protected function setProperty(
        $object, $accessor, $value
    ) {
        if (!$accessor->isPublic() && $this->bIgnoreVisibility) {
            $accessor->setAccessible(true);
        }
        if ($accessor instanceof ReflectionProperty) {
            $accessor->setValue($object, $value);
        } else {
            //setter method
            $accessor->invoke($object, $value);
        }
    }

    /**
     * Create a new object of the given type.
     *
     * This method exists to be overwritten in child classes,
     * so you can do dependency injection or so.
     *
     * @param string  $class        Class name to instantiate
     * @param boolean $useParameter Pass $parameter to the constructor or not
     * @param mixed   $jvalue       Constructor parameter (the json value)
     *
     * @return object Freshly created object
     */
    public function createInstance(
        $class, $useParameter = false, $jvalue = null
    ) {
        if (isset($this->classMap[$class])) {
            if (is_callable($mapper = $this->classMap[$class])) {
                $class = $mapper($class, $jvalue);
            } else {
                $class = $this->classMap[$class];
            }
        }
        if ($useParameter) {
            return new $class($jvalue);
        } else {
            return (new ReflectionClass($class))->newInstanceWithoutConstructor();
        }
    }

    /**
     * Checks if the given type is a "simple type"
     *
     * @param string $type type name from gettype()
     *
     * @return boolean True if it is a simple PHP type
     *
     * @see isFlatType()
     */
    protected function isSimpleType($type)
    {
        return $type == 'string'
            || $type == 'boolean' || $type == 'bool'
            || $type == 'integer' || $type == 'int'
            || $type == 'double' || $type == 'float'
            || $type == 'array' || $type == 'object';
    }

    /**
     * Checks if the object is of this type or has this type as one of its parents
     *
     * @param string $type  class name of type being required
     * @param mixed  $value Some PHP value to be tested
     *
     * @return boolean True if $object has type of $type
     */
    protected function isObjectOfSameType($type, $value)
    {
        if (false === is_object($value)) {
            return false;
        }

        return is_a($value, $type);
    }

    /**
     * Checks if the given type is a type that is not nested
     * (simple type except array and object)
     *
     * @param string $type type name from gettype()
     *
     * @return boolean True if it is a non-nested PHP type
     *
     * @see isSimpleType()
     */
    protected function isFlatType($type)
    {
        return $type == 'NULL'
            || $type == 'string'
            || $type == 'boolean' || $type == 'bool'
            || $type == 'integer' || $type == 'int'
            || $type == 'double' || $type == 'float';
    }

    /**
     * Returns true if type is an array of elements
     * (bracket notation)
     *
     * @param string $strType type to be matched
     *
     * @return bool
     */
    protected function isArrayOfType($strType)
    {
        return substr($strType, -2) === '[]';
    }

    /**
     * Checks if the given type is nullable
     *
     * @param string $type type name from the phpdoc param
     *
     * @return boolean True if it is nullable
     */
    protected function isNullable($type)
    {
        return stripos('|' . $type . '|', '|null|') !== false;
    }

    /**
     * Remove the 'null' section of a type
     *
     * @param string $type type name from the phpdoc param
     *
     * @return string The new type value
     */
    protected function removeNullable($type)
    {
        if ($type === null) {
            return null;
        }
        return substr(
            str_ireplace('|null|', '|', '|' . $type . '|'),
            1, -1
        );
    }

    /**
     * Copied from PHPUnit 3.7.29, Util/Test.php
     *
     * @param string $docblock Full method docblock
     *
     * @return array
     */
    protected static function parseAnnotations($docblock)
    {
        $annotations = array();
        // Strip away the docblock header and footer
        // to ease parsing of one line annotations
        $docblock = substr($docblock, 3, -2);

        $re = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m';
        if (preg_match_all($re, $docblock, $matches)) {
            $numMatches = count($matches[0]);

            for ($i = 0; $i < $numMatches; ++$i) {
                $annotations[$matches['name'][$i]][] = $matches['value'][$i];
            }
        }

        return $annotations;
    }

    /**
     * Log a message to the $logger object
     *
     * @param string $level   Logging level
     * @param string $message Text to log
     * @param array  $context Additional information
     *
     * @return null
     */
    protected function log($level, $message, array $context = array())
    {
        if ($this->logger) {
            $this->logger->log($level, $message, $context);
        }
    }

    /**
     * Sets a logger instance on the object
     *
     * @param LoggerInterface $logger PSR-3 compatible logger object
     *
     * @return null
     */
    public function setLogger($logger)
    {
        $this->logger = $logger;
    }
}
?>
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class DiagnosticKind {
    const Error = 0;
    const Warning = 1;
}
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

use Microsoft\PhpParser\Node;

class DiagnosticsProvider {

    /**
     * @var string[] maps the token kind to the corresponding name
     */
    private static $tokenKindToText;

    /**
     * @param int $kind (must be a valid token kind)
     * @return string
     */
    public static function getTextForTokenKind($kind) {
        return self::$tokenKindToText[$kind];
    }

    /**
     * This is called when this class is loaded, at the bottom of this file.
     * @return void
     */
    public static function initTokenKindToText() {
        self::$tokenKindToText = \array_flip(\array_merge(
            TokenStringMaps::OPERATORS_AND_PUNCTUATORS,
            TokenStringMaps::KEYWORDS,
            TokenStringMaps::RESERVED_WORDS
        ));
    }

    /**
     * Returns the diagnostic for $node, or null.
     * @param \Microsoft\PhpParser\Node|\Microsoft\PhpParser\Token $node
     * @return Diagnostic|null
     */
    public static function checkDiagnostics($node) {
        if ($node instanceof Token) {
            if (\get_class($node) === Token::class) {
                return null;
            }
            return self::checkDiagnosticForUnexpectedToken($node);
        }

        if ($node instanceof Node) {
            return $node->getDiagnosticForNode();
        }
        return null;
    }

    /**
     * @param Token $token
     * @return Diagnostic|null
     */
    private static function checkDiagnosticForUnexpectedToken($token) {
        if ($token instanceof SkippedToken) {
            // TODO - consider also attaching parse context information to skipped tokens
            // this would allow us to provide more helpful error messages that inform users what to do
            // about the problem rather than simply pointing out the mistake.
            return new Diagnostic(
                DiagnosticKind::Error,
                "Unexpected '" .
                (self::$tokenKindToText[$token->kind]
                    ?? Token::getTokenKindNameFromValue($token->kind)) .
                "'",
                $token->start,
                $token->getEndPosition() - $token->start
            );
        } elseif ($token instanceof MissingToken) {
            return new Diagnostic(
                DiagnosticKind::Error,
                "'" .
                (self::$tokenKindToText[$token->kind]
                    ?? Token::getTokenKindNameFromValue($token->kind)) .
                "' expected.",
                $token->start,
                $token->getEndPosition() - $token->start
            );
        }
    }

    /**
     * Traverses AST to generate diagnostics.
     * @param \Microsoft\PhpParser\Node $n
     * @return Diagnostic[]
     */
    public static function getDiagnostics(Node $n) : array {
        $diagnostics = [];

        /**
         * @param \Microsoft\PhpParser\Node|\Microsoft\PhpParser\Token $node
         */
        $n->walkDescendantNodesAndTokens(function($node) use (&$diagnostics) {
            if (($diagnostic = self::checkDiagnostics($node)) !== null) {
                $diagnostics[] = $diagnostic;
            }
        });

        return $diagnostics;
    }
}

DiagnosticsProvider::initTokenKindToText();
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class ParseContext {
    const SourceElements = 0;
    const BlockStatements = 1;
    const ClassMembers = 2;
    const IfClause2Elements = 3;
    const SwitchStatementElements = 4;
    const CaseStatementElements = 5;
    const WhileStatementElements = 6;
    const ForStatementElements = 7;
    const ForeachStatementElements = 8;
    const DeclareStatementElements = 9;
    const InterfaceMembers = 10;
    const TraitMembers = 11;
    const Count = 12;
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

/**
 * Tokenizes content using PHP's built-in `token_get_all`, and converts to "lightweight" Token representation.
 *
 * Initially we tried hand-spinning the lexer (see `experiments/Lexer.php`), but we had difficulties optimizing
 * performance (especially when working with Unicode characters.)
 *
 * Class PhpTokenizer
 * @package Microsoft\PhpParser
 */
class PhpTokenizer implements TokenStreamProviderInterface {
    public $pos;
    public $endOfFilePos;

    private $tokensArray;

    public function __construct($content) {
        $this->tokensArray = $this->getTokensArrayFromContent($content);
        $this->endOfFilePos = \count($this->tokensArray) - 1;
        $this->pos = 0;
    }

    public function scanNextToken() : Token {
        return $this->pos >= $this->endOfFilePos
            ? $this->tokensArray[$this->endOfFilePos]
            : $this->tokensArray[$this->pos++];
    }

    public function getCurrentPosition() : int {
        return $this->pos;
    }

    public function setCurrentPosition(int $pos) {
        $this->pos = $pos;
    }

    public function getEndOfFilePosition() : int {
        return $this->endOfFilePos;
    }

    public function getTokensArray() : array {
        return $this->tokensArray;
    }

    public static function getTokensArrayFromContent(
        $content, $parseContext = null, $initialPos = 0, $treatCommentsAsTrivia = true
    ) : array {
        if ($parseContext !== null) {
            $prefix = self::PARSE_CONTEXT_TO_PREFIX[$parseContext];
            $content = $prefix . $content;
            $passedPrefix = false;
        }

        $tokens = @\token_get_all($content);

        $arr = array();
        $fullStart = $start = $pos = $initialPos;

        foreach ($tokens as $token) {
            if (\is_array($token)) {
                $tokenKind = $token[0];
                $strlen = \strlen($token[1]);
            } else {
                $tokenKind = $token;
                $strlen = \strlen($token);
            }

            $pos += $strlen;

            if ($parseContext !== null && !$passedPrefix) {
                $passedPrefix = \strlen($prefix) < $pos;
                if ($passedPrefix) {
                    $fullStart = $start = $pos = $initialPos;
                }
                continue;
            }

            switch ($tokenKind) {
                case T_OPEN_TAG:
                    $arr[] = new Token(TokenKind::ScriptSectionStartTag, $fullStart, $start, $pos-$fullStart);
                    $start = $fullStart = $pos;
                    continue;

                case T_WHITESPACE:
                    $start += $strlen;
                    continue;

                case T_STRING:
                    $name = \strtolower($token[1]);
                    if (isset(TokenStringMaps::RESERVED_WORDS[$name])) {
                        $newTokenKind = TokenStringMaps::RESERVED_WORDS[$name];
                        $arr[] = new Token($newTokenKind, $fullStart, $start, $pos - $fullStart);
                        $start = $fullStart = $pos;
                        continue;
                    }

                default:
                    if (($tokenKind === T_COMMENT || $tokenKind === T_DOC_COMMENT) && $treatCommentsAsTrivia) {
                        $start += $strlen;
                        continue;
                    }

                    $newTokenKind = self::TOKEN_MAP[$tokenKind] ?? TokenKind::Unknown;
                    $arr[] = new Token($newTokenKind, $fullStart, $start, $pos - $fullStart);
                    $start = $fullStart = $pos;
                    continue;
            }
        }

        $arr[] = new Token(TokenKind::EndOfFileToken, $fullStart, $start, $pos - $fullStart);
        return $arr;
    }

    const TOKEN_MAP = [
        T_CLASS_C => TokenKind::Name,
        T_DIR => TokenKind::Name,
        T_FILE => TokenKind::Name,
        T_FUNC_C => TokenKind::Name,
        T_HALT_COMPILER => TokenKind::Name,
        T_METHOD_C => TokenKind::Name,
        T_NS_C => TokenKind::Name,
        T_TRAIT_C => TokenKind::Name,
        T_LINE => TokenKind::Name,

        T_STRING => TokenKind::Name,
        T_VARIABLE => TokenKind::VariableName,

        T_ABSTRACT => TokenKind::AbstractKeyword,
        T_LOGICAL_AND => TokenKind::AndKeyword,
        T_ARRAY => TokenKind::ArrayKeyword,
        T_AS => TokenKind::AsKeyword,
        T_BREAK => TokenKind::BreakKeyword,
        T_CALLABLE => TokenKind::CallableKeyword,
        T_CASE => TokenKind::CaseKeyword,
        T_CATCH => TokenKind::CatchKeyword,
        T_CLASS => TokenKind::ClassKeyword,
        T_CLONE => TokenKind::CloneKeyword,
        T_CONST => TokenKind::ConstKeyword,
        T_CONTINUE => TokenKind::ContinueKeyword,
        T_DECLARE => TokenKind::DeclareKeyword,
        T_DEFAULT => TokenKind::DefaultKeyword,
        T_DO => TokenKind::DoKeyword,
        T_ECHO => TokenKind::EchoKeyword,
        T_ELSE => TokenKind::ElseKeyword,
        T_ELSEIF => TokenKind::ElseIfKeyword,
        T_EMPTY => TokenKind::EmptyKeyword,
        T_ENDDECLARE => TokenKind::EndDeclareKeyword,
        T_ENDFOR => TokenKind::EndForKeyword,
        T_ENDFOREACH => TokenKind::EndForEachKeyword,
        T_ENDIF => TokenKind::EndIfKeyword,
        T_ENDSWITCH => TokenKind::EndSwitchKeyword,
        T_ENDWHILE => TokenKind::EndWhileKeyword,
        T_EVAL => TokenKind::EvalKeyword,
        T_EXIT => TokenKind::ExitKeyword,
        T_EXTENDS => TokenKind::ExtendsKeyword,
        T_FINAL => TokenKind::FinalKeyword,
        T_FINALLY => TokenKind::FinallyKeyword,
        T_FOR => TokenKind::ForKeyword,
        T_FOREACH => TokenKind::ForeachKeyword,
        T_FUNCTION => TokenKind::FunctionKeyword,
        T_GLOBAL => TokenKind::GlobalKeyword,
        T_GOTO => TokenKind::GotoKeyword,
        T_IF => TokenKind::IfKeyword,
        T_IMPLEMENTS => TokenKind::ImplementsKeyword,
        T_INCLUDE => TokenKind::IncludeKeyword,
        T_INCLUDE_ONCE => TokenKind::IncludeOnceKeyword,
        T_INSTANCEOF => TokenKind::InstanceOfKeyword,
        T_INSTEADOF => TokenKind::InsteadOfKeyword,
        T_INTERFACE => TokenKind::InterfaceKeyword,
        T_ISSET => TokenKind::IsSetKeyword,
        T_LIST => TokenKind::ListKeyword,
        T_NAMESPACE => TokenKind::NamespaceKeyword,
        T_NEW => TokenKind::NewKeyword,
        T_LOGICAL_OR => TokenKind::OrKeyword,
        T_PRINT => TokenKind::PrintKeyword,
        T_PRIVATE => TokenKind::PrivateKeyword,
        T_PROTECTED => TokenKind::ProtectedKeyword,
        T_PUBLIC => TokenKind::PublicKeyword,
        T_REQUIRE => TokenKind::RequireKeyword,
        T_REQUIRE_ONCE => TokenKind::RequireOnceKeyword,
        T_RETURN => TokenKind::ReturnKeyword,
        T_STATIC => TokenKind::StaticKeyword,
        T_SWITCH => TokenKind::SwitchKeyword,
        T_THROW => TokenKind::ThrowKeyword,
        T_TRAIT => TokenKind::TraitKeyword,
        T_TRY => TokenKind::TryKeyword,
        T_UNSET => TokenKind::UnsetKeyword,
        T_USE => TokenKind::UseKeyword,
        T_VAR => TokenKind::VarKeyword,
        T_WHILE => TokenKind::WhileKeyword,
        T_LOGICAL_XOR => TokenKind::XorKeyword,
        T_YIELD => TokenKind::YieldKeyword,
        T_YIELD_FROM => TokenKind::YieldFromKeyword,

        "[" => TokenKind::OpenBracketToken,
        "]" => TokenKind::CloseBracketToken,
        "(" => TokenKind::OpenParenToken,
        ")" => TokenKind::CloseParenToken,
        "{" => TokenKind::OpenBraceToken,
        "}" => TokenKind::CloseBraceToken,
        "." => TokenKind::DotToken,
        T_OBJECT_OPERATOR => TokenKind::ArrowToken,
        T_INC => TokenKind::PlusPlusToken,
        T_DEC => TokenKind::MinusMinusToken,
        T_POW => TokenKind::AsteriskAsteriskToken,
        "*" => TokenKind::AsteriskToken,
        "+" => TokenKind::PlusToken,
        "-" => TokenKind::MinusToken,
        "~" => TokenKind::TildeToken,
        "!" => TokenKind::ExclamationToken,
        "$" => TokenKind::DollarToken,
        "/" => TokenKind::SlashToken,
        "%" => TokenKind::PercentToken,
        T_SL => TokenKind::LessThanLessThanToken,
        T_SR => TokenKind::GreaterThanGreaterThanToken,
        "<" => TokenKind::LessThanToken,
        ">" => TokenKind::GreaterThanToken,
        T_IS_SMALLER_OR_EQUAL => TokenKind::LessThanEqualsToken,
        T_IS_GREATER_OR_EQUAL => TokenKind::GreaterThanEqualsToken,
        T_IS_EQUAL => TokenKind::EqualsEqualsToken,
        T_IS_IDENTICAL => TokenKind::EqualsEqualsEqualsToken,
        T_IS_NOT_EQUAL => TokenKind::ExclamationEqualsToken,
        T_IS_NOT_IDENTICAL => TokenKind::ExclamationEqualsEqualsToken,
        "^" => TokenKind::CaretToken,
        "|" => TokenKind::BarToken,
        "&" => TokenKind::AmpersandToken,
        T_BOOLEAN_AND => TokenKind::AmpersandAmpersandToken,
        T_BOOLEAN_OR => TokenKind::BarBarToken,
        ":" => TokenKind::ColonToken,
        ";" => TokenKind::SemicolonToken,
        "=" => TokenKind::EqualsToken,
        T_POW_EQUAL => TokenKind::AsteriskAsteriskEqualsToken,
        T_MUL_EQUAL => TokenKind::AsteriskEqualsToken,
        T_DIV_EQUAL => TokenKind::SlashEqualsToken,
        T_MOD_EQUAL => TokenKind::PercentEqualsToken,
        T_PLUS_EQUAL => TokenKind::PlusEqualsToken,
        T_MINUS_EQUAL => TokenKind::MinusEqualsToken,
        T_CONCAT_EQUAL => TokenKind::DotEqualsToken,
        T_SL_EQUAL => TokenKind::LessThanLessThanEqualsToken,
        T_SR_EQUAL => TokenKind::GreaterThanGreaterThanEqualsToken,
        T_AND_EQUAL => TokenKind::AmpersandEqualsToken,
        T_XOR_EQUAL => TokenKind::CaretEqualsToken,
        T_OR_EQUAL => TokenKind::BarEqualsToken,
        "," => TokenKind::CommaToken,
        T_COALESCE => TokenKind::QuestionQuestionToken,
        T_SPACESHIP => TokenKind::LessThanEqualsGreaterThanToken,
        T_ELLIPSIS => TokenKind::DotDotDotToken,
        T_NS_SEPARATOR => TokenKind::BackslashToken,
        T_PAAMAYIM_NEKUDOTAYIM => TokenKind::ColonColonToken,
        T_DOUBLE_ARROW => TokenKind::DoubleArrowToken, // TODO missing from spec

        "@" => TokenKind::AtSymbolToken,
        "`" => TokenKind::BacktickToken,
        "?" => TokenKind::QuestionToken,

        T_LNUMBER => TokenKind::IntegerLiteralToken,

        T_DNUMBER => TokenKind::FloatingLiteralToken,

        T_OPEN_TAG => TokenKind::ScriptSectionStartTag,
        T_OPEN_TAG_WITH_ECHO => TokenKind::ScriptSectionStartTag,
        T_CLOSE_TAG => TokenKind::ScriptSectionEndTag,

        T_INLINE_HTML => TokenKind::InlineHtml,

        "\"" => TokenKind::DoubleQuoteToken,
        "'" => TokenKind::SingleQuoteToken,
        T_ENCAPSED_AND_WHITESPACE => TokenKind::EncapsedAndWhitespace,
        T_DOLLAR_OPEN_CURLY_BRACES => TokenKind::DollarOpenBraceToken,
        T_CURLY_OPEN => TokenKind::OpenBraceDollarToken,
        T_CONSTANT_ENCAPSED_STRING => TokenKind::StringLiteralToken,

        T_ARRAY_CAST        => TokenKind::ArrayCastToken,
        T_BOOL_CAST         => TokenKind::BoolCastToken,
        T_DOUBLE_CAST       => TokenKind::DoubleCastToken,
        T_INT_CAST          => TokenKind::IntCastToken,
        T_OBJECT_CAST       => TokenKind::ObjectCastToken,
        T_STRING_CAST       => TokenKind::StringCastToken,
        T_UNSET_CAST        => TokenKind::UnsetCastToken,

        T_START_HEREDOC     => TokenKind::HeredocStart,
        T_END_HEREDOC       => TokenKind::HeredocEnd,
        T_STRING_VARNAME    => TokenKind::StringVarname,
        T_COMMENT           => TokenKind::CommentToken,
        T_DOC_COMMENT       => TokenKind::DocCommentToken,
        T_NUM_STRING        => TokenKind::IntegerLiteralToken
    ];

    const PARSE_CONTEXT_TO_PREFIX = [
        ParseContext::SourceElements => "<?php "
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class CharacterCodes {
    const _0 = 0x30;
    const _1 = 0x31;
    const _2 = 0x32;
    const _3 = 0x33;
    const _4 = 0x34;
    const _5 = 0x35;
    const _6 = 0x36;
    const _7 = 0x37;
    const _8 = 0x38;
    const _9 = 0x39;

    const a = 0x61;
    const b = 0x62;
    const c = 0x63;
    const d = 0x64;
    const e = 0x65;
    const f = 0x66;
    const g = 0x67;
    const h = 0x68;
    const i = 0x69;
    const j = 0x6A;
    const k = 0x6B;
    const l = 0x6C;
    const m = 0x6D;
    const n = 0x6E;
    const o = 0x6F;
    const p = 0x70;
    const q = 0x71;
    const r = 0x72;
    const s = 0x73;
    const t = 0x74;
    const u = 0x75;
    const v = 0x76;
    const w = 0x77;
    const x = 0x78;
    const y = 0x79;
    const z = 0x7A;

    const A = 0x41;
    const B = 0x42;
    const C = 0x43;
    const D = 0x44;
    const E = 0x45;
    const F = 0x46;
    const G = 0x47;
    const H = 0x48;
    const I = 0x49;
    const J = 0x4A;
    const K = 0x4B;
    const L = 0x4C;
    const M = 0x4D;
    const N = 0x4E;
    const O = 0x4F;
    const P = 0x50;
    const Q = 0x51;
    const R = 0x52;
    const S = 0x53;
    const T = 0x54;
    const U = 0x55;
    const V = 0x56;
    const W = 0x57;
    const X = 0x58;
    const Y = 0x59;
    const Z = 0x5a;

    const _underscore = 0x5F;            // _
    const _dollar = 0x24;                // $
    const _ampersand = 0x26;             // &
    const _asterisk = 0x2A;              // *
    const _at = 0x40;                    // @
    const _backslash = 0x5C;             // \
    const _backtick = 0x60;              // `
    const _bar = 0x7C;                   // |
    const _caret = 0x5E;                 // ^
    const _closeBrace = 0x7D;            // }
    const _closeBracket = 0x5D;          // ]
    const _closeParen = 0x29;            // )
    const _colon = 0x3A;                 // :
    const _comma = 0x2C;                 // ;
    const _dot = 0x2E;                   // .
    const _doubleQuote = 0x22;           // "
    const _equals = 0x3D;                // =
    const _exclamation = 0x21;           // !
    const _greaterThan = 0x3E;           // >
    const _hash = 0x23;                  // #
    const _lessThan = 0x3C;              // <
    const _minus = 0x2D;                 // -
    const _openBrace = 0x7B;             // {
    const _openBracket = 0x5B;           // [
    const _openParen = 0x28;             // (
    const _percent = 0x25;               // %
    const _plus = 0x2B;                  // +
    const _question = 0x3F;              // ?
    const _semicolon = 0x3B;             // ;
    const _singleQuote = 0x27;           // '
    const _slash = 0x2F;                 // /
    const _tilde = 0x7E;                 // ~

    const _backspace = 0x08;             // \b
    const _formFeed = 0x0C;              // \f
    const _byteOrderMark = 0xFEFF;
    const _space = 0x20;
    const _newline = 0x0A;               // \n
    const _return = 0x0D;                // \r
    const _tab = 0x09;                   // \t
    const _verticalTab = 0x0B;           // \v
}
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

use Microsoft\PhpParser\Node\NamespaceUseClause;
use Microsoft\PhpParser\Node\NamespaceUseGroupClause;
use Microsoft\PhpParser\Node\QualifiedName;
use Microsoft\PhpParser\Node\Statement\NamespaceDefinition;
use Microsoft\PhpParser\Node\Statement\NamespaceUseDeclaration;

trait NamespacedNameTrait {
    public abstract function getNamespaceDefinition();
    public abstract function getFileContents() : string;
    public abstract function getNameParts() : array;

    /**
     * Gets resolved name from current namespace. Note that this is not necessarily the *actual* name
     * that is resolved during compilation or at runtime. For that, see QualifiedName::getResolvedName().
     *
     * @return ResolvedName
     */
    public function getNamespacedName() : ResolvedName {
        $namespaceDefinition = $this->getNamespaceDefinition();
        $content = $this->getFileContents();
        if ($namespaceDefinition === null) {
            // global namespace -> strip namespace\ prefix
            return ResolvedName::buildName($this->getNameParts(), $content);
        }

        if ($namespaceDefinition->name !== null) {
            $resolvedName = ResolvedName::buildName($namespaceDefinition->name->nameParts, $content);
        } else {
            $resolvedName = ResolvedName::buildName([], $content);
        }
        if (
            !($this instanceof QualifiedName && (
            ($this->parent instanceof NamespaceDefinition) ||
            ($this->parent instanceof NamespaceUseDeclaration) ||
            ($this->parent instanceof NamespaceUseClause) ||
            ($this->parent instanceof NamespaceUseGroupClause)))
        ) {
            $resolvedName->addNameParts($this->getNameParts(), $content);
        }
        return $resolvedName;
    }
}<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

/**
 * Interface for recognizing functions easily.
 * Each Node that implements this interface can be considered a function.
 */
interface FunctionLike {}<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

spl_autoload_register(function ($class) {
    $path = str_replace("\\", "/", __DIR__ . "/" . \substr($class, 20) . ".php");
    if (file_exists($path)) {
        require_once $path;
    }
});
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class TokenStreamProviderFactory {
    public static function GetTokenStreamProvider($content) {
        //    return new Lexer($content);
        return new PhpTokenizer($content);
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class Diagnostic {
    /** @var int */
    public $kind;

    /** @var string */
    public $message;

    /** @var int */
    public $start;

    /** @var int */
    public $length;

    public function __construct(int $kind, string $message, int $start, int $length) {
        $this->kind = $kind;
        $this->message = $message;
        $this->start = $start;
        $this->length = $length;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

use Microsoft\PhpParser\TokenKind;

class TokenStringMaps {
    const KEYWORDS = array(
        "abstract" => TokenKind::AbstractKeyword,
        "and" => TokenKind::AndKeyword,
        "array" => TokenKind::ArrayKeyword,
        "as" => TokenKind::AsKeyword,
        "break" => TokenKind::BreakKeyword,
        "callable" => TokenKind::CallableKeyword,
        "case" => TokenKind::CaseKeyword,
        "catch" => TokenKind::CatchKeyword,
        "class" => TokenKind::ClassKeyword,
        "clone" => TokenKind::CloneKeyword,
        "const" => TokenKind::ConstKeyword,
        "continue" => TokenKind::ContinueKeyword,
        "declare" => TokenKind::DeclareKeyword,
        "default" => TokenKind::DefaultKeyword,
        "die" => TokenKind::DieKeyword,
        "do" => TokenKind::DoKeyword,
        "echo" => TokenKind::EchoKeyword,
        "else" => TokenKind::ElseKeyword,
        "elseif" => TokenKind::ElseIfKeyword,
        "empty" => TokenKind::EmptyKeyword,
        "enddeclare" => TokenKind::EndDeclareKeyword,
        "endfor" => TokenKind::EndForKeyword,
        "endforeach" => TokenKind::EndForEachKeyword,
        "endif" => TokenKind::EndIfKeyword,
        "endswitch" => TokenKind::EndSwitchKeyword,
        "endwhile" => TokenKind::EndWhileKeyword,
        "eval" => TokenKind::EvalKeyword,
        "exit" => TokenKind::ExitKeyword,
        "extends" => TokenKind::ExtendsKeyword,
        "final" => TokenKind::FinalKeyword,
        "finally" => TokenKind::FinallyKeyword,
        "for" => TokenKind::ForKeyword,
        "foreach" => TokenKind::ForeachKeyword,
        "function" => TokenKind::FunctionKeyword,
        "global" => TokenKind::GlobalKeyword,
        "goto" => TokenKind::GotoKeyword,
        "if" => TokenKind::IfKeyword,
        "implements" => TokenKind::ImplementsKeyword,
        "include" => TokenKind::IncludeKeyword,
        "include_once" => TokenKind::IncludeOnceKeyword,
        "instanceof" => TokenKind::InstanceOfKeyword,
        "insteadof" => TokenKind::InsteadOfKeyword,
        "interface" => TokenKind::InterfaceKeyword,
        "isset" => TokenKind::IsSetKeyword,
        "list" => TokenKind::ListKeyword,
        "namespace" => TokenKind::NamespaceKeyword,
        "new" => TokenKind::NewKeyword,
        "or" => TokenKind::OrKeyword,
        "print" => TokenKind::PrintKeyword,
        "private" => TokenKind::PrivateKeyword,
        "protected" => TokenKind::ProtectedKeyword,
        "public" => TokenKind::PublicKeyword,
        "require" => TokenKind::RequireKeyword,
        "require_once" => TokenKind::RequireOnceKeyword,
        "return" => TokenKind::ReturnKeyword,
        "static" => TokenKind::StaticKeyword,
        "switch" => TokenKind::SwitchKeyword,
        "throw" => TokenKind::ThrowKeyword,
        "trait" => TokenKind::TraitKeyword,
        "try" => TokenKind::TryKeyword,
        "unset" => TokenKind::UnsetKeyword,
        "use" => TokenKind::UseKeyword,
        "var" => TokenKind::VarKeyword,
        "while" => TokenKind::WhileKeyword,
        "xor" => TokenKind::XorKeyword,
        "yield" => TokenKind::YieldKeyword,
        "yield from" => TokenKind::YieldFromKeyword,


        // TODO soft reserved words?
    );

    const RESERVED_WORDS = [
        // http://php.net/manual/en/reserved.constants.php
        // TRUE, FALSE, NULL are special predefined constants
        // TODO - also consider adding other constants
        "true" => TokenKind::TrueReservedWord,
        "false" => TokenKind::FalseReservedWord,
        "null" => TokenKind::NullReservedWord,

        // RESERVED WORDS:
        // http://php.net/manual/en/reserved.other-reserved-words.php
        "int" => TokenKind::IntReservedWord,
        "float" => TokenKind::FloatReservedWord,
        "bool" => TokenKind::BoolReservedWord,
        "string" => TokenKind::StringReservedWord,
        "binary" => TokenKind::BinaryReservedWord,
        "boolean" => TokenKind::BooleanReservedWord,
        "double" => TokenKind::DoubleReservedWord,
        "integer" => TokenKind::IntegerReservedWord,
        "object" => TokenKind::ObjectReservedWord,
        "real" => TokenKind::RealReservedWord,
        "void" => TokenKind::VoidReservedWord
    ];

    const OPERATORS_AND_PUNCTUATORS = array(
        "[" => TokenKind::OpenBracketToken,
        "]" => TokenKind::CloseBracketToken,
        "(" => TokenKind::OpenParenToken,
        ")" => TokenKind::CloseParenToken,
        "{" => TokenKind::OpenBraceToken,
        "}" => TokenKind::CloseBraceToken,
        "." => TokenKind::DotToken,
        "->" => TokenKind::ArrowToken,
        "=>" => TokenKind::DoubleArrowToken,
        "++" => TokenKind::PlusPlusToken,
        "--" => TokenKind::MinusMinusToken,
        "**" => TokenKind::AsteriskAsteriskToken,
        "*" => TokenKind::AsteriskToken,
        "+" => TokenKind::PlusToken,
        "-" => TokenKind::MinusToken,
        "~" => TokenKind::TildeToken,
        "!" => TokenKind::ExclamationToken,
        "$" => TokenKind::DollarToken,
        "/" => TokenKind::SlashToken,
        "%" => TokenKind::PercentToken,
        "<<" => TokenKind::LessThanLessThanToken,
        ">>" => TokenKind::GreaterThanGreaterThanToken,
        "<" => TokenKind::LessThanToken,
        ">" => TokenKind::GreaterThanToken,
        "<=" => TokenKind::LessThanEqualsToken,
        ">=" => TokenKind::GreaterThanEqualsToken,
        "==" => TokenKind::EqualsEqualsToken,
        "===" => TokenKind::EqualsEqualsEqualsToken,
        "!=" => TokenKind::ExclamationEqualsToken,
        "!==" => TokenKind::ExclamationEqualsEqualsToken,
        "^" => TokenKind::CaretToken,
        "|" => TokenKind::BarToken,
        "&" => TokenKind::AmpersandToken,
        "&&" => TokenKind::AmpersandAmpersandToken,
        "||" => TokenKind::BarBarToken,
        "?" => TokenKind::QuestionToken,
        ":" => TokenKind::ColonToken,
        "::" => TokenKind::ColonColonToken,
        ";" => TokenKind::SemicolonToken,
        "=" => TokenKind::EqualsToken,
        "**=" => TokenKind::AsteriskAsteriskEqualsToken,
        "*=" => TokenKind::AsteriskEqualsToken,
        "/=" => TokenKind::SlashEqualsToken,
        "%=" => TokenKind::PercentEqualsToken,
        "+=" => TokenKind::PlusEqualsToken,
        "-=" => TokenKind::MinusEqualsToken,
        ".=" => TokenKind::DotEqualsToken,
        "<<=" => TokenKind::LessThanLessThanEqualsToken,
        ">>=" => TokenKind::GreaterThanGreaterThanEqualsToken,
        "&=" => TokenKind::AmpersandEqualsToken,
        "^=" => TokenKind::CaretEqualsToken,
        "|=" => TokenKind::BarEqualsToken,
        "," => TokenKind::CommaToken,
        "??" => TokenKind::QuestionQuestionToken,
        "<=>" => TokenKind::LessThanEqualsGreaterThanToken,
        "<>" => TokenKind::LessThanGreaterThanToken,
        "..." => TokenKind::DotDotDotToken,
        "\\" => TokenKind::BackslashToken,
        "<?=" => TokenKind::ScriptSectionStartTag, // TODO, technically not an operator
        "<?php " => TokenKind::ScriptSectionStartTag, // TODO, technically not an operator
        "<?php\t" => TokenKind::ScriptSectionStartTag, // TODO add tests
        "<?php\n" => TokenKind::ScriptSectionStartTag,
        "<?php\r" => TokenKind::ScriptSectionStartTag,
        "<?php\r\n" => TokenKind::ScriptSectionStartTag,
        "?>" => TokenKind::ScriptSectionEndTag, // TODO, technically not an operator
        "?>\n" => TokenKind::ScriptSectionEndTag, // TODO, technically not an operator
        "?>\r\n" => TokenKind::ScriptSectionEndTag, // TODO, technically not an operator
        "?>\r" => TokenKind::ScriptSectionEndTag, // TODO, technically not an operator
        "@" => TokenKind::AtSymbolToken, // TODO not in spec
        "`" => TokenKind::BacktickToken
    );

// TODO add new tokens
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

use Microsoft\PhpParser\Node\NamespaceUseClause;
use Microsoft\PhpParser\Node\NamespaceUseGroupClause;
use Microsoft\PhpParser\Node\SourceFileNode;
use Microsoft\PhpParser\Node\Statement\NamespaceDefinition;
use Microsoft\PhpParser\Node\Statement\NamespaceUseDeclaration;

abstract class Node implements \JsonSerializable {
    /** @var array[] Map from node class to array of child keys */
    private static $childNames = [];

    /** @var Node|null */
    public $parent;

    public function getNodeKindName() : string {
        // Use strrpos (rather than explode) to avoid creating a temporary array.
        return substr(static::class, strrpos(static::class, "\\") + 1);
    }

    /**
     * Gets start position of Node, not including leading comments and whitespace.
     * @return int
     * @throws \Exception
     */
    public function getStart() : int {
        $child = $this->getChildNodesAndTokens()->current();
        if ($child instanceof Node) {
            return $child->getStart();
        } elseif ($child instanceof Token) {
            return $child->start;
        }
        throw new \Exception("Unknown type in AST");
    }

    /**
     * Gets start position of Node, including leading comments and whitespace
     * @return int
     * @throws \Exception
     */
    public function getFullStart() : int {
        foreach($this::CHILD_NAMES as $name) {
            if (($child = $this->$name) !== null) {
                if (\is_array($child)) {
                    if(!isset($child[0])) {
                        continue;
                    }
                    $child = $child[0];
                }
                if ($child instanceof Node) {
                    return $child->getFullStart();
                } elseif ($child instanceof Token) {
                    return $child->fullStart;
                }
                throw new \Exception("Unknown type in AST: " . \gettype($child));
            }
        };

        throw new \Exception("Unknown type in AST: " . \gettype($child));
    }

    /**
     * Gets parent of current node (returns null if has no parent)
     * @return null|Node
     */
    public function getParent() {
        return $this->parent;
    }

    /**
     * Gets first ancestor that is an instance of one of the provided classes.
     * Returns null if there is no match.
     *
     * @param string ...$classNames
     * @return Node|null
     */
    public function getFirstAncestor(...$classNames) {
        $ancestor = $this;
        while (($ancestor = $ancestor->parent) !== null) {
            foreach ($classNames as $className) {
                if ($ancestor instanceof $className) {
                    return $ancestor;
                }
            }
        }
        return null;
    }

    /**
     * Gets first child that is an instance of one of the provided classes.
     * Returns null if there is no match.
     *
     * @param array ...$classNames
     * @return Node|null
     */
    public function getFirstChildNode(...$classNames) {
        foreach ($this::CHILD_NAMES as $name) {
            $val = $this->$name;
            foreach ($classNames as $className) {
                if (\is_array($val)) {
                    foreach ($val as $child) {
                        if ($child instanceof $className) {
                            return $child;
                        }
                    }
                    continue;
                } elseif ($val instanceof $className) {
                    return $val;
                }
            }
        }
        return null;
    }

    /**
     * Gets first descendant node that is an instance of one of the provided classes.
     * Returns null if there is no match.
     *
     * @param array ...$classNames
     * @return Node|null
     */
    public function getFirstDescendantNode(...$classNames) {
        foreach ($this->getDescendantNodes() as $descendant) {
            foreach ($classNames as $className) {
                if ($descendant instanceof $className) {
                    return $descendant;
                }
            }
        }
        return null;
    }

    /**
     * Gets root of the syntax tree (returns self if has no parents)
     * @return SourceFileNode (expect root to be SourceFileNode unless the tree was manipulated)
     */
    public function getRoot() : Node {
        $node = $this;
        while ($node->parent !== null) {
            $node = $node->parent;
        }
        return $node;
    }

    /**
     * Gets generator containing all descendant Nodes and Tokens.
     *
     * @param callable|null $shouldDescendIntoChildrenFn
     * @return \Generator|Node[]|Token[]
     */
    public function getDescendantNodesAndTokens(callable $shouldDescendIntoChildrenFn = null) {
        // TODO - write unit tests to prove invariants
        // (concatenating all descendant Tokens should produce document, concatenating all Nodes should produce document)
        foreach ($this->getChildNodesAndTokens() as $child) {
            // Check possible types of $child, most frequent first
            if ($child instanceof Node) {
                yield $child;
                if ($shouldDescendIntoChildrenFn === null || $shouldDescendIntoChildrenFn($child)) {
                   yield from $child->getDescendantNodesAndTokens($shouldDescendIntoChildrenFn);
                }
            } elseif ($child instanceof Token) {
                yield $child;
            }
        }
    }

    /**
     * Iterate over all descendant Nodes and Tokens, calling $callback.
     * This can often be faster than getDescendantNodesAndTokens
     * if you just need to call something and don't need a generator.
     *
     * @param callable $callback a callback that accepts Node|Token
     * @param callable|null $shouldDescendIntoChildrenFn
     * @return void
     */
    public function walkDescendantNodesAndTokens(callable $callback, callable $shouldDescendIntoChildrenFn = null) {
        // TODO - write unit tests to prove invariants
        // (concatenating all descendant Tokens should produce document, concatenating all Nodes should produce document)
        foreach (static::CHILD_NAMES as $name) {
            $child = $this->$name;
            // Check possible types of $child, most frequent first
            if ($child instanceof Token) {
                $callback($child);
            } elseif ($child instanceof Node) {
                $callback($child);
                if ($shouldDescendIntoChildrenFn === null || $shouldDescendIntoChildrenFn($child)) {
                   $child->walkDescendantNodesAndTokens($callback, $shouldDescendIntoChildrenFn);
                }
            } elseif (\is_array($child)) {
                foreach ($child as $childElement) {
                    if ($childElement instanceof Token) {
                        $callback($childElement);
                    } elseif ($childElement instanceof Node) {
                        $callback($childElement);
                        if ($shouldDescendIntoChildrenFn === null || $shouldDescendIntoChildrenFn($childElement)) {
                           $childElement->walkDescendantNodesAndTokens($callback, $shouldDescendIntoChildrenFn);
                        }
                    }
                }
            }
        }
    }

    /**
     * Gets a generator containing all descendant Nodes.
     * @param callable|null $shouldDescendIntoChildrenFn
     * @return \Generator|Node[]
     */
    public function getDescendantNodes(callable $shouldDescendIntoChildrenFn = null) {
        foreach ($this->getChildNodes() as $child) {
            yield $child;
            if ($shouldDescendIntoChildrenFn === null || $shouldDescendIntoChildrenFn($child)) {
                yield from $child->getDescendantNodes($shouldDescendIntoChildrenFn);
            }
        }
    }

    /**
     * Gets generator containing all descendant Tokens.
     * @param callable|null $shouldDescendIntoChildrenFn
     * @return \Generator|Token[]
     */
    public function getDescendantTokens(callable $shouldDescendIntoChildrenFn = null) {
        foreach ($this->getChildNodesAndTokens() as $child) {
            if ($child instanceof Node) {
                if ($shouldDescendIntoChildrenFn == null || $shouldDescendIntoChildrenFn($child)) {
                    yield from $child->getDescendantTokens($shouldDescendIntoChildrenFn);
                }
            } elseif ($child instanceof Token) {
                yield $child;
            }
        }
    }

    /**
     * Gets generator containing all child Nodes and Tokens (direct descendants).
     * Does not return null elements.
     *
     * @return \Generator|Token[]|Node[]
     */
    public function getChildNodesAndTokens() : \Generator {
        foreach ($this::CHILD_NAMES as $name) {
            $val = $this->$name;

            if (\is_array($val)) {
                foreach ($val as $child) {
                    if ($child !== null) {
                        yield $name => $child;
                    }
                }
                continue;
            }
            if ($val !== null) {
                yield $name => $val;
            }
        }
    }

    /**
     * Gets generator containing all child Nodes (direct descendants)
     * @return \Generator|Node[]
     */
    public function getChildNodes() : \Generator {
        foreach ($this::CHILD_NAMES as $name) {
            $val = $this->$name;
            if (\is_array($val)) {
                foreach ($val as $child) {
                    if ($child instanceof Node) {
                        yield $child;
                    }
                }
                continue;
            } elseif ($val instanceof Node) {
                yield $val;
            }
        }
    }

    /**
     * Gets generator containing all child Tokens (direct descendants)
     *
     * @return \Generator|Token[]
     */
    public function getChildTokens() {
        foreach ($this::CHILD_NAMES as $name) {
            $val = $this->$name;
            if (\is_array($val)) {
                foreach ($val as $child) {
                    if ($child instanceof Token) {
                        yield $child;
                    }
                }
                continue;
            } elseif ($val instanceof Token) {
                yield $val;
            }
        }
    }

    /**
     * Gets array of declared child names (cached).
     *
     * This is used as an optimization when iterating over nodes: For direct iteration
     * PHP will create a properties hashtable on the object, thus doubling memory usage.
     * We avoid this by iterating over just the names instead.
     *
     * @return string[]
     */
    public function getChildNames() {
        return $this::CHILD_NAMES;
    }

    /**
     * Gets width of a Node (not including comment / whitespace trivia)
     *
     * @return int
     */
    public function getWidth() : int {
        $first = $this->getStart();
        $last = $this->getEndPosition();

        return $last - $first;
    }

    /**
     * Gets width of a Node (including comment / whitespace trivia)
     *
     * @return int
     */
    public function getFullWidth() : int {
        $first = $this->getFullStart();
        $last = $this->getEndPosition();

        return $last - $first;
    }

    /**
     * Gets string representing Node text (not including leading comment + whitespace trivia)
     * @return string
     */
    public function getText() : string {
        $start = $this->getStart();
        $end = $this->getEndPosition();

        $fileContents = $this->getFileContents();
        return \substr($fileContents, $start, $end - $start);
    }

    /**
     * Gets full text of Node (including leading comment + whitespace trivia)
     * @return string
     */
    public function getFullText() : string {
        $start = $this->getFullStart();
        $end = $this->getEndPosition();

        $fileContents = $this->getFileContents();
        return \substr($fileContents, $start, $end - $start);

    }

    /**
     * Gets string representing Node's leading comment and whitespace text.
     * @return string
     */
    public function getLeadingCommentAndWhitespaceText() : string {
        // TODO re-tokenize comments and whitespace
        $fileContents = $this->getFileContents();
        foreach ($this->getDescendantTokens() as $token) {
            return $token->getLeadingCommentsAndWhitespaceText($fileContents);
        }
    }

    protected function getChildrenKvPairs() {
        $result = array();
        foreach ($this::CHILD_NAMES as $name) {
            $result[$name] = $this->$name;
        }
        return $result;
    }

    public function jsonSerialize() {
        $kindName = $this->getNodeKindName();
        return ["$kindName" => $this->getChildrenKvPairs()];
    }

    /**
     * Get the end index of a Node.
     * @return int
     * @throws \Exception
     */
    public function getEndPosition() {
        // TODO test invariant - start of next node is end of previous node
        for ($i = \count($childKeys = $this::CHILD_NAMES) - 1; $i >= 0; $i--) {
            $lastChildKey = $childKeys[$i];
            $lastChild = $this->$lastChildKey;

            if (\is_array($lastChild)) {
                $lastChild = \end($lastChild);
            }

            if ($lastChild instanceof Token) {
                return $lastChild->fullStart + $lastChild->length;
            } elseif ($lastChild instanceof Node) {
                return $lastChild->getEndPosition();
            }
        }

        throw new \Exception("Unhandled node type");
    }

    public function getFileContents() : string {
        // TODO consider renaming to getSourceText
        return $this->getRoot()->fileContents;
    }

    public function getUri() : string {
        return $this->getRoot()->uri;
    }

    public function getLastChild() {
        $a = iterator_to_array($this->getChildNodesAndTokens());
        return \end($a);
    }

    /**
     * Searches descendants to find a Node at the given position.
     *
     * @param $pos
     * @return Node|null
     */
    public function getDescendantNodeAtPosition(int $pos) {
        foreach ($this->getChildNodes() as $child) {
            if ($child->containsPosition($pos)) {
                $node = $child->getDescendantNodeAtPosition($pos);
                if (!is_null($node)) {
                    return $node;
                }
            }
        }

        return $this;
    }

    /**
     * Returns true if the given Node or Token contains the given position.
     * @param int $pos
     * @return bool
     */
    private function containsPosition(int $pos): bool {
        return $this->getStart() <= $pos && $pos <= $this->getEndPosition();
    }

    /**
     * Gets leading PHP Doc Comment text corresponding to the current Node.
     * Returns last doc comment in leading comment / whitespace trivia,
     * and returns null if there is no preceding doc comment.
     *
     * @return string|null
     */
    public function getDocCommentText() {
        $leadingTriviaText = $this->getLeadingCommentAndWhitespaceText();
        $leadingTriviaTokens = PhpTokenizer::getTokensArrayFromContent(
            $leadingTriviaText, ParseContext::SourceElements, $this->getFullStart(), false
        );
        for ($i = \count($leadingTriviaTokens) - 1; $i >= 0; $i--) {
            $token = $leadingTriviaTokens[$i];
            if ($token->kind === TokenKind::DocCommentToken) {
                return $token->getText($this->getFileContents());
            }
        }
        return null;
    }

    public function __toString() {
        return $this->getText();
    }

    /**
     * @return array|ResolvedName[][]
     * @throws \Exception
     */
    public function getImportTablesForCurrentScope() {
        $namespaceDefinition = $this->getNamespaceDefinition();

        // Use declarations can exist in either the global scope, or inside namespace declarations.
        // http://php.net/manual/en/language.namespaces.importing.php#language.namespaces.importing.scope
        //
        // The only code allowed before a namespace declaration is a declare statement, and sub-namespaces are
        // additionally unaffected by by import rules of higher-level namespaces. Therefore, we can make the assumption
        // that we need not travel up the spine any further once we've found the current namespace.
        // http://php.net/manual/en/language.namespaces.definition.php
        if ($namespaceDefinition instanceof NamespaceDefinition) {
            $topLevelNamespaceStatements = $namespaceDefinition->compoundStatementOrSemicolon instanceof Token
                ? $namespaceDefinition->parent->statementList // we need to start from the namespace definition.
                : $namespaceDefinition->compoundStatementOrSemicolon->statements;
            $namespaceFullStart = $namespaceDefinition->getFullStart();
        } else {
            $topLevelNamespaceStatements = $this->getRoot()->statementList;
            $namespaceFullStart = 0;
        }

        $nodeFullStart = $this->getFullStart();

        // TODO optimize performance
        // Currently we rebuild the import tables on every call (and therefore every name resolution operation)
        // It is likely that a consumer will attempt many consecutive name resolution requests within the same file.
        // Therefore, we can consider optimizing on the basis of the "most recently used" import table set.
        // The idea: Keep a single set of import tables cached based on a unique root node id, and invalidate
        // cache whenever we attempt to resolve a qualified name with a different root node.
        //
        // In order to make this work, it will probably make sense to change the way we parse namespace definitions.
        // https://github.com/Microsoft/tolerant-php-parser/issues/81
        //
        // Currently the namespace definition only includes a compound statement or semicolon token as one if it's children.
        // Instead, we should move to a model where we parse future statements as a child rather than as a separate
        // statement. This would enable us to retrieve all the information we would need to find the fully qualified
        // name by simply traveling up the spine to find the first ancestor of type NamespaceDefinition.
        $namespaceImportTable = $functionImportTable = $constImportTable = [];
        $contents = $this->getFileContents();

        foreach ($topLevelNamespaceStatements as $useDeclaration) {
            if ($useDeclaration->getFullStart() <= $namespaceFullStart) {
                continue;
            }
            if ($useDeclaration->getFullStart() > $nodeFullStart) {
                break;
            } elseif (!($useDeclaration instanceof NamespaceUseDeclaration)) {
                continue;
            }

            // TODO fix getValues
            foreach ((isset($useDeclaration->useClauses) ? $useDeclaration->useClauses->getValues() : []) as $useClause) {
                $namespaceNamePartsPrefix = $useClause->namespaceName !== null ? $useClause->namespaceName->nameParts : [];

                if ($useClause->groupClauses !== null && $useClause instanceof NamespaceUseClause) {
                    // use A\B\C\{D\E};                 namespace import: ["E" => [A,B,C,D,E]]
                    // use A\B\C\{D\E as F};            namespace import: ["F" => [A,B,C,D,E]]
                    // use function A\B\C\{A, B}        function import: ["A" => [A,B,C,A], "B" => [A,B,C]]
                    // use function A\B\C\{const A}     const import: ["A" => [A,B,C,A]]
                    foreach ($useClause->groupClauses->children as $groupClause) {
                        if (!($groupClause instanceof NamespaceUseGroupClause)) {
                            continue;
                        }
                        $namespaceNameParts = \array_merge($namespaceNamePartsPrefix, $groupClause->namespaceName->nameParts);
                        $functionOrConst = $groupClause->functionOrConst ?? $useDeclaration->functionOrConst;
                        $alias = $groupClause->namespaceAliasingClause === null
                            ? $groupClause->namespaceName->getLastNamePart()->getText($contents)
                            : $groupClause->namespaceAliasingClause->name->getText($contents);

                        $this->addToImportTable(
                            $alias, $functionOrConst, $namespaceNameParts, $contents,
                            $namespaceImportTable, $functionImportTable, $constImportTable
                        );
                    }
                } else {
                    // use A\B\C;               namespace import: ["C" => [A,B,C]]
                    // use A\B\C as D;          namespace import: ["D" => [A,B,C]]
                    // use function A\B\C as D  function import: ["D" => [A,B,C]]
                    // use A\B, C\D;            namespace import: ["B" => [A,B], "D" => [C,D]]
                    $alias = $useClause->namespaceAliasingClause === null
                        ? $useClause->namespaceName->getLastNamePart()->getText($contents)
                        : $useClause->namespaceAliasingClause->name->getText($contents);
                    $functionOrConst = $useDeclaration->functionOrConst;
                    $namespaceNameParts = $namespaceNamePartsPrefix;

                    $this->addToImportTable(
                        $alias, $functionOrConst, $namespaceNameParts, $contents,
                        $namespaceImportTable, $functionImportTable, $constImportTable
                    );
                }
            }
        }

        return [$namespaceImportTable, $functionImportTable, $constImportTable];
    }

    /**
     * Gets corresponding NamespaceDefinition for Node. Returns null if in global namespace.
     *
     * @return NamespaceDefinition|null
     */
    public function getNamespaceDefinition() {
        $namespaceDefinition = $this instanceof NamespaceDefinition
            ? $this
            : $this->getFirstAncestor(NamespaceDefinition::class, SourceFileNode::class);

        if ($namespaceDefinition instanceof NamespaceDefinition && !($namespaceDefinition->parent instanceof SourceFileNode)) {
            $namespaceDefinition = $namespaceDefinition->getFirstAncestor(SourceFileNode::class);
        }

        if ($namespaceDefinition === null) {
            // TODO provide a way to throw errors without crashing consumer
            throw new \Exception("Invalid tree - SourceFileNode must always exist at root of tree.");
        }

        $fullStart = $this->getFullStart();
        $lastNamespaceDefinition = null;
        if ($namespaceDefinition instanceof SourceFileNode) {
            foreach ($namespaceDefinition->getChildNodes() as $childNode) {
                if ($childNode instanceof NamespaceDefinition && $childNode->getFullStart() < $fullStart) {
                    $lastNamespaceDefinition = $childNode;
                }
            }
        }

        if ($lastNamespaceDefinition !== null && $lastNamespaceDefinition->compoundStatementOrSemicolon instanceof Token) {
            $namespaceDefinition = $lastNamespaceDefinition;
        } elseif ($namespaceDefinition instanceof SourceFileNode) {
            $namespaceDefinition = null;
        }

        return $namespaceDefinition;
    }

    public function getPreviousSibling() {
        // TODO make more efficient
        $parent = $this->parent;
        if ($parent === null) {
            return null;
        }

        $prevSibling = null;

        foreach ($parent::CHILD_NAMES as $name) {
            $val = $parent->$name;
            if (\is_array($val)) {
                foreach ($val as $sibling) {
                    if ($sibling === $this) {
                        return $prevSibling;
                    } elseif ($sibling instanceof Node) {
                        $prevSibling = $sibling;
                    }
                }
                continue;
            } elseif ($val instanceof Node) {
                if ($val === $this) {
                    return $prevSibling;
                }
                $prevSibling = $val;
            }
        }
        return null;
    }

    /**
     * Add the alias and resolved name to the corresponding namespace, function, or const import table.
     * If the alias already exists, it will get replaced by the most recent using.
     *
     * TODO - worth throwing an error here in stead?
     */
    private function addToImportTable($alias, $functionOrConst, $namespaceNameParts, $contents, & $namespaceImportTable, & $functionImportTable, & $constImportTable):array
    {
        if ($alias !== null) {
            if ($functionOrConst === null) {
                // namespaces are case-insensitive
//                $alias = \strtolower($alias);
                $namespaceImportTable[$alias] = ResolvedName::buildName($namespaceNameParts, $contents);
                return array($namespaceImportTable, $functionImportTable, $constImportTable);
            } elseif ($functionOrConst->kind === TokenKind::FunctionKeyword) {
                // functions are case-insensitive
//                $alias = \strtolower($alias);
                $functionImportTable[$alias] = ResolvedName::buildName($namespaceNameParts, $contents);
                return array($namespaceImportTable, $functionImportTable, $constImportTable);
            } elseif ($functionOrConst->kind === TokenKind::ConstKeyword) {
                // constants are case-sensitive
                $constImportTable[$alias] = ResolvedName::buildName($namespaceNameParts, $contents);
                return array($namespaceImportTable, $functionImportTable, $constImportTable);
            }
            return array($namespaceImportTable, $functionImportTable, $constImportTable);
        }
        return array($namespaceImportTable, $functionImportTable, $constImportTable);
    }

    /**
     * This is overridden in subclasses
     * @return Diagnostic|null - Callers should use DiagnosticsProvider::getDiagnostics instead
     * @internal
     */
    public function getDiagnosticForNode() {
        return null;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class SkippedToken extends Token {
    public function __construct(Token $token) {
        parent::__construct($token->kind, $token->fullStart, $token->start, $token->length);
    }

    public function jsonSerialize() {
        return array_merge(
            ["error" => $this->getTokenKindNameFromValue(TokenKind::SkippedToken)],
            parent::jsonSerialize()
        );
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class Range {
    public $start;
    public $end;

    public function __construct(LineCharacterPosition $start, LineCharacterPosition $end) {
        $this->start = $start;
        $this->end = $end;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class ResolvedName {
    private $parts = [];

    /**
     * @param Token[] $tokens
     */
    public static function buildName(array $tokens, $content) : ResolvedName {
        $name = new ResolvedName();
        foreach ($tokens as $token) {
            if ($token->kind === TokenKind::Name) {
                $name->parts[] = $token->getText($content);
            }
        }
        return $name;
    }

    public function addNameParts(array $parts, $content) {
        foreach ($parts as $part) {
            if ($part->kind === TokenKind::Name && !($part instanceof MissingToken)) {
                $this->parts[] = $part->getText($content);
            }
        }
    }

    public function getNameParts() {
        return $this->parts;
    }

    public function getFullyQualifiedNameText() : string {
        return join("\\", $this->parts);
    }

    public function __toString() {
        return $this->getFullyQualifiedNameText();
    }
}<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

use Microsoft\PhpParser\Node\AnonymousFunctionUseClause;
use Microsoft\PhpParser\Node\ArrayElement;
use Microsoft\PhpParser\Node\CaseStatementNode;
use Microsoft\PhpParser\Node\CatchClause;
use Microsoft\PhpParser\Node\ClassBaseClause;
use Microsoft\PhpParser\Node\ClassInterfaceClause;
use Microsoft\PhpParser\Node\ClassMembersNode;
use Microsoft\PhpParser\Node\ConstElement;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\Expression\{
    AnonymousFunctionCreationExpression,
    ArgumentExpression,
    ArrayCreationExpression,
    AssignmentExpression,
    BinaryExpression,
    BracedExpression,
    CallExpression,
    CastExpression,
    CloneExpression,
    EmptyIntrinsicExpression,
    ErrorControlExpression,
    EvalIntrinsicExpression,
    ExitIntrinsicExpression,
    IssetIntrinsicExpression,
    MemberAccessExpression,
    ParenthesizedExpression,
    PrefixUpdateExpression,
    PrintIntrinsicExpression,
    EchoExpression,
    ListIntrinsicExpression,
    ObjectCreationExpression,
    ScriptInclusionExpression,
    PostfixUpdateExpression,
    ScopedPropertyAccessExpression,
    SubscriptExpression,
    TernaryExpression,
    UnaryExpression,
    UnaryOpExpression,
    UnsetIntrinsicExpression,
    Variable,
    YieldExpression
};
use Microsoft\PhpParser\Node\StaticVariableDeclaration;
use Microsoft\PhpParser\Node\ClassConstDeclaration;
use Microsoft\PhpParser\Node\DeclareDirective;
use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\ElseClauseNode;
use Microsoft\PhpParser\Node\ElseIfClauseNode;
use Microsoft\PhpParser\Node\FinallyClause;
use Microsoft\PhpParser\Node\ForeachKey;
use Microsoft\PhpParser\Node\ForeachValue;
use Microsoft\PhpParser\Node\InterfaceBaseClause;
use Microsoft\PhpParser\Node\InterfaceMembers;
use Microsoft\PhpParser\Node\MissingMemberDeclaration;
use Microsoft\PhpParser\Node\NamespaceAliasingClause;
use Microsoft\PhpParser\Node\NamespaceUseGroupClause;
use Microsoft\PhpParser\Node\NumericLiteral;
use Microsoft\PhpParser\Node\PropertyDeclaration;
use Microsoft\PhpParser\Node\ReservedWord;
use Microsoft\PhpParser\Node\StringLiteral;
use Microsoft\PhpParser\Node\MethodDeclaration;
use Microsoft\PhpParser\Node\Parameter;
use Microsoft\PhpParser\Node\QualifiedName;
use Microsoft\PhpParser\Node\RelativeSpecifier;
use Microsoft\PhpParser\Node\SourceFileNode;
use Microsoft\PhpParser\Node\Statement\{
    ClassDeclaration,
    ConstDeclaration,
    CompoundStatementNode,
    FunctionStaticDeclaration,
    GlobalDeclaration,
    BreakOrContinueStatement,
    DeclareStatement,
    DoStatement,
    EmptyStatement,
    ExpressionStatement,
    ForeachStatement,
    ForStatement,
    FunctionDeclaration,
    GotoStatement,
    IfStatementNode,
    InlineHtml,
    InterfaceDeclaration,
    NamespaceDefinition,
    NamespaceUseDeclaration,
    NamedLabelStatement,
    ReturnStatement,
    SwitchStatementNode,
    ThrowStatement,
    TraitDeclaration,
    TryStatement,
    WhileStatement
};
use Microsoft\PhpParser\Node\TraitMembers;
use Microsoft\PhpParser\Node\TraitSelectOrAliasClause;
use Microsoft\PhpParser\Node\TraitUseClause;
use Microsoft\PhpParser\Node\UseVariableName;
use Microsoft\PhpParser\Node\NamespaceUseClause;

class Parser {
    /** @var TokenStreamProviderInterface */
    private $lexer;

    private $currentParseContext;
    public $sourceFile;

    private $nameOrKeywordOrReservedWordTokens;
    private $nameOrReservedWordTokens;
    private $nameOrStaticOrReservedWordTokens;
    private $reservedWordTokens;
    private $keywordTokens;
    // TODO consider validating parameter and return types on post-parse instead so we can be more permissive
    private $parameterTypeDeclarationTokens;
    private $returnTypeDeclarationTokens;

    public function __construct() {
        $this->reservedWordTokens = \array_values(TokenStringMaps::RESERVED_WORDS);
        $this->keywordTokens = \array_values(TokenStringMaps::KEYWORDS);
        $this->nameOrKeywordOrReservedWordTokens = \array_merge([TokenKind::Name], $this->keywordTokens, $this->reservedWordTokens);
        $this->nameOrReservedWordTokens = \array_merge([TokenKind::Name], $this->reservedWordTokens);
        $this->nameOrStaticOrReservedWordTokens = \array_merge([TokenKind::Name, TokenKind::StaticKeyword], $this->reservedWordTokens);
        $this->parameterTypeDeclarationTokens =
            [TokenKind::ArrayKeyword, TokenKind::CallableKeyword, TokenKind::BoolReservedWord,
            TokenKind::FloatReservedWord, TokenKind::IntReservedWord, TokenKind::StringReservedWord,
            TokenKind::ObjectReservedWord]; // TODO update spec
        $this->returnTypeDeclarationTokens = \array_merge([TokenKind::VoidReservedWord], $this->parameterTypeDeclarationTokens);
    }

    /**
     * Generates AST from source file contents. Returns an instance of SourceFileNode, which is always the top-most
     * Node-type of the tree.
     *
     * @param string $fileContents
     * @return SourceFileNode
     */
    public function parseSourceFile(string $fileContents, string $uri = null) : SourceFileNode {
        $this->lexer = TokenStreamProviderFactory::GetTokenStreamProvider($fileContents);

        $this->reset();

        $sourceFile = new SourceFileNode();
        $this->sourceFile = $sourceFile;
        $sourceFile->fileContents = $fileContents;
        $sourceFile->uri = $uri;
        $sourceFile->statementList = array();
        if ($this->getCurrentToken()->kind !== TokenKind::EndOfFileToken) {
            $sourceFile->statementList[] = $this->parseInlineHtml($sourceFile);
        }
        $sourceFile->statementList =
            \array_merge($sourceFile->statementList, $this->parseList($sourceFile, ParseContext::SourceElements));

        $this->sourceFile->endOfFileToken = $this->eat1(TokenKind::EndOfFileToken);
        $this->advanceToken();

        $sourceFile->parent = null;

        return $sourceFile;
    }

    private function reset() {
        $this->advanceToken();

        // Stores the current parse context, which includes the current and enclosing lists.
        $this->currentParseContext = 0;
    }

    /**
     * Parse a list of elements for a given ParseContext until a list terminator associated
     * with that ParseContext is reached. Additionally abort parsing when an element is reached
     * that is invalid in the current context, but valid in an enclosing context. If an element
     * is invalid in both current and enclosing contexts, generate a SkippedToken, and continue.
     * @param $parentNode
     * @param int $listParseContext
     * @return array
     */
    private function parseList($parentNode, int $listParseContext) {
        $savedParseContext = $this->currentParseContext;
        $this->currentParseContext |= 1 << $listParseContext;
        $parseListElementFn = $this->getParseListElementFn($listParseContext);

        $nodeArray = array();
        while (!$this->isListTerminator($listParseContext)) {
            if ($this->isValidListElement($listParseContext, $this->getCurrentToken())) {
                $element = $parseListElementFn($parentNode);
                if ($element instanceof Node) {
                    $element->parent = $parentNode;
                }
                $nodeArray[] = $element;
                continue;
            }

            // Error handling logic:
            // The current parse context does not know how to handle the current token,
            // so check if the enclosing contexts know what to do. If so, we assume that
            // the list has completed parsing, and return to the enclosing context.
            //
            // Example:
            //     class A {
            //         function foo() {
            //            return;
            //      // } <- MissingToken (generated when we try to "eat" the closing brace)
            //
            //         public function bar() {
            //         }
            //     }
            //
            // In the case above, the Method ParseContext doesn't know how to handle "public", but
            // the Class ParseContext will know what to do with it. So we abort the Method ParseContext,
            // and return to the Class ParseContext. This enables us to generate a tree with a single
            // class that contains two method nodes, even though there was an error present in the first method.
            if ($this->isCurrentTokenValidInEnclosingContexts()) {
                break;
            }

            // None of the enclosing contexts know how to handle the token. Generate a
            // SkippedToken, and continue parsing in the current context.
            // Example:
            //     class A {
            //         function foo() {
            //            return;
            //            & // <- SkippedToken
            //         }
            //     }
            $token = new SkippedToken($this->getCurrentToken());
            $nodeArray[] = $token;
            $this->advanceToken();
        }

        $this->currentParseContext = $savedParseContext;

        return $nodeArray;
    }

    private function isListTerminator(int $parseContext) {
        $tokenKind = $this->getCurrentToken()->kind;
        if ($tokenKind === TokenKind::EndOfFileToken) {
            // Being at the end of the file ends all lists.
            return true;
        }

        switch ($parseContext) {
            case ParseContext::SourceElements:
                return false;

            case ParseContext::InterfaceMembers:
            case ParseContext::ClassMembers:
            case ParseContext::BlockStatements:
            case ParseContext::TraitMembers:
                return $tokenKind === TokenKind::CloseBraceToken;
            case ParseContext::SwitchStatementElements:
                return $tokenKind === TokenKind::CloseBraceToken || $tokenKind === TokenKind::EndSwitchKeyword;
            case ParseContext::IfClause2Elements:
                return
                    $tokenKind === TokenKind::ElseIfKeyword ||
                    $tokenKind === TokenKind::ElseKeyword ||
                    $tokenKind === TokenKind::EndIfKeyword;

            case ParseContext::WhileStatementElements:
                return $tokenKind === TokenKind::EndWhileKeyword;

            case ParseContext::CaseStatementElements:
                return
                    $tokenKind === TokenKind::CaseKeyword ||
                    $tokenKind === TokenKind::DefaultKeyword;

            case ParseContext::ForStatementElements:
                return
                    $tokenKind === TokenKind::EndForKeyword;

            case ParseContext::ForeachStatementElements:
                return $tokenKind === TokenKind::EndForEachKeyword;

            case ParseContext::DeclareStatementElements:
                return $tokenKind === TokenKind::EndDeclareKeyword;
        }
        // TODO warn about unhandled parse context
        return false;
    }

    private function isValidListElement($context, Token $token) {

        // TODO
        switch ($context) {
            case ParseContext::SourceElements:
            case ParseContext::BlockStatements:
            case ParseContext::IfClause2Elements:
            case ParseContext::CaseStatementElements:
            case ParseContext::WhileStatementElements:
            case ParseContext::ForStatementElements:
            case ParseContext::ForeachStatementElements:
            case ParseContext::DeclareStatementElements:
                return $this->isStatementStart($token);

            case ParseContext::ClassMembers:
                return $this->isClassMemberDeclarationStart($token);

            case ParseContext::TraitMembers:
                return $this->isTraitMemberDeclarationStart($token);

            case ParseContext::InterfaceMembers:
                return $this->isInterfaceMemberDeclarationStart($token);

            case ParseContext::SwitchStatementElements:
                return
                    $token->kind === TokenKind::CaseKeyword ||
                    $token->kind === TokenKind::DefaultKeyword;
        }
        return false;
    }

    private function getParseListElementFn($context) {
        switch ($context) {
            case ParseContext::SourceElements:
            case ParseContext::BlockStatements:
            case ParseContext::IfClause2Elements:
            case ParseContext::CaseStatementElements:
            case ParseContext::WhileStatementElements:
            case ParseContext::ForStatementElements:
            case ParseContext::ForeachStatementElements:
            case ParseContext::DeclareStatementElements:
                return $this->parseStatementFn();
            case ParseContext::ClassMembers:
                return $this->parseClassElementFn();

            case ParseContext::TraitMembers:
                return $this->parseTraitElementFn();

            case ParseContext::InterfaceMembers:
                return $this->parseInterfaceElementFn();

            case ParseContext::SwitchStatementElements:
                return $this->parseCaseOrDefaultStatement();
            default:
                throw new \Exception("Unrecognized parse context");
        }
    }

    /**
     * Aborts parsing list when one of the parent contexts understands something
     * @return bool
     */
    private function isCurrentTokenValidInEnclosingContexts() {
        for ($contextKind = 0; $contextKind < ParseContext::Count; $contextKind++) {
            if ($this->isInParseContext($contextKind)) {
                if ($this->isValidListElement($contextKind, $this->getCurrentToken()) || $this->isListTerminator($contextKind)) {
                    return true;
                }
            }
        }
        return false;
    }

    private function isInParseContext($contextToCheck) {
        return ($this->currentParseContext & (1 << $contextToCheck));
    }

    /**
     * Retrieve the current token, and check that it's of the expected TokenKind.
     * If so, advance and return the token. Otherwise return a MissingToken for
     * the expected token.
     * @param int|int[] ...$kinds
     * @return Token
     */
    private function eat(...$kinds) {
        $token = $this->token;
        if (\is_array($kinds[0])) {
            $kinds = $kinds[0];
        }
        foreach ($kinds as $kind) {
            if ($token->kind === $kind) {
                $this->token = $this->lexer->scanNextToken();
                return $token;
            }
        }
        // TODO include optional grouping for token kinds
        return new MissingToken($kinds[0], $token->fullStart);
    }

    /**
     * Retrieve the current token, and check that it's of the kind $kind.
     * If so, advance and return the token. Otherwise return a MissingToken for
     * the expected token.
     *
     * This is faster than calling eat() if there is a single token.
     *
     * @param int $kind
     * @return Token
     */
    private function eat1($kind) {
        $token = $this->token;
        if ($token->kind === $kind) {
            $this->token = $this->lexer->scanNextToken();
            return $token;
        }
        // TODO include optional grouping for token kinds
        return new MissingToken($kind, $token->fullStart);
    }

    /**
     * @param int|int[] ...$kinds (Can provide a single value with a list of kinds, or multiple kinds)
     * @return Token|null
     */
    private function eatOptional(...$kinds) {
        $token = $this->token;
        if (\is_array($kinds[0])) {
            $kinds = $kinds[0];
        }
        if (\in_array($token->kind, $kinds)) {
            $this->token = $this->lexer->scanNextToken();
            return $token;
        }
        return null;
    }

    /**
     * @param int $kind a single kind
     * @return Token|null
     */
    private function eatOptional1($kind) {
        $token = $this->token;
        if ($token->kind === $kind) {
            $this->token = $this->lexer->scanNextToken();
            return $token;
        }
        return null;
    }

    private $token;

    private function getCurrentToken() : Token {
        return $this->token;
    }

    private function advanceToken() {
        $this->token = $this->lexer->scanNextToken();
    }

    private function parseStatement($parentNode) {
        return ($this->parseStatementFn())($parentNode);
    }

    private function parseStatementFn() {
        return function ($parentNode) {
            $token = $this->getCurrentToken();
            switch ($token->kind) {
                // compound-statement
                case TokenKind::OpenBraceToken:
                    return $this->parseCompoundStatement($parentNode);

                // labeled-statement
                case TokenKind::Name:
                    if ($this->lookahead(TokenKind::ColonToken)) {
                        return $this->parseNamedLabelStatement($parentNode);
                    }
                    break;

                // selection-statement
                case TokenKind::IfKeyword:
                    return $this->parseIfStatement($parentNode);
                case TokenKind::SwitchKeyword:
                    return $this->parseSwitchStatement($parentNode);

                // iteration-statement
                case TokenKind::WhileKeyword: // while-statement
                    return $this->parseWhileStatement($parentNode);
                case TokenKind::DoKeyword: // do-statement
                    return $this->parseDoStatement($parentNode);
                case TokenKind::ForKeyword: // for-statement
                    return $this->parseForStatement($parentNode);
                case TokenKind::ForeachKeyword: // foreach-statement
                    return $this->parseForeachStatement($parentNode);

                // jump-statement
                case TokenKind::GotoKeyword: // goto-statement
                    return $this->parseGotoStatement($parentNode);
                case TokenKind::ContinueKeyword: // continue-statement
                case TokenKind::BreakKeyword: // break-statement
                    return $this->parseBreakOrContinueStatement($parentNode);
                case TokenKind::ReturnKeyword: // return-statement
                    return $this->parseReturnStatement($parentNode);
                case TokenKind::ThrowKeyword: // throw-statement
                    return $this->parseThrowStatement($parentNode);

                // try-statement
                case TokenKind::TryKeyword:
                    return $this->parseTryStatement($parentNode);

                // declare-statement
                case TokenKind::DeclareKeyword:
                    return $this->parseDeclareStatement($parentNode);

                // function-declaration
                case TokenKind::FunctionKeyword:
                    // Check that this is not an anonymous-function-creation-expression
                    if ($this->lookahead($this->nameOrKeywordOrReservedWordTokens) || $this->lookahead(TokenKind::AmpersandToken, $this->nameOrKeywordOrReservedWordTokens)) {
                        return $this->parseFunctionDeclaration($parentNode);
                    }
                    break;

                // class-declaration
                case TokenKind::FinalKeyword:
                case TokenKind::AbstractKeyword:
                    if (!$this->lookahead(TokenKind::ClassKeyword)) {
                        $this->advanceToken();
                        return new SkippedToken($token);
                    }
                case TokenKind::ClassKeyword:
                    return $this->parseClassDeclaration($parentNode);

                // interface-declaration
                case TokenKind::InterfaceKeyword:
                    return $this->parseInterfaceDeclaration($parentNode);

                // namespace-definition
                case TokenKind::NamespaceKeyword:
                    if (!$this->lookahead(TokenKind::BackslashToken)) {
                        // TODO add error handling for the case where a namespace definition does not occur in the outer-most scope
                        return $this->parseNamespaceDefinition($parentNode);
                    }
                    break;

                // namespace-use-declaration
                case TokenKind::UseKeyword:
                    return $this->parseNamespaceUseDeclaration($parentNode);

                case TokenKind::SemicolonToken:
                    return $this->parseEmptyStatement($parentNode);

                // trait-declaration
                case TokenKind::TraitKeyword:
                    return $this->parseTraitDeclaration($parentNode);

                // global-declaration
                case TokenKind::GlobalKeyword:
                    return $this->parseGlobalDeclaration($parentNode);

                // const-declaration
                case TokenKind::ConstKeyword:
                    return $this->parseConstDeclaration($parentNode);

                // function-static-declaration
                case TokenKind::StaticKeyword:
                    // Check that this is not an anonymous-function-creation-expression
                    if (!$this->lookahead([TokenKind::FunctionKeyword, TokenKind::OpenParenToken, TokenKind::ColonColonToken])) {
                        return $this->parseFunctionStaticDeclaration($parentNode);
                    }
                    break;

                case TokenKind::ScriptSectionEndTag:
                    return $this->parseInlineHtml($parentNode);
            }

            $expressionStatement = new ExpressionStatement();
            $expressionStatement->parent = $parentNode;
            $expressionStatement->expression = $this->parseExpression($expressionStatement, true);
            $expressionStatement->semicolon = $this->eatSemicolonOrAbortStatement();
            return $expressionStatement;
        };
    }

    private function parseClassElementFn() {
        return function ($parentNode) {
            $modifiers = $this->parseModifiers();

            $token = $this->getCurrentToken();
            switch ($token->kind) {
                case TokenKind::ConstKeyword:
                    return $this->parseClassConstDeclaration($parentNode, $modifiers);

                case TokenKind::FunctionKeyword:
                    return $this->parseMethodDeclaration($parentNode, $modifiers);

                case TokenKind::VariableName:
                    return $this->parsePropertyDeclaration($parentNode, $modifiers);

                case TokenKind::UseKeyword:
                    return $this->parseTraitUseClause($parentNode);

                default:
                    $missingClassMemberDeclaration = new MissingMemberDeclaration();
                    $missingClassMemberDeclaration->parent = $parentNode;
                    $missingClassMemberDeclaration->modifiers = $modifiers;
                    return $missingClassMemberDeclaration;
            }
        };
    }

    private function parseClassDeclaration($parentNode) : Node {
        $classNode = new ClassDeclaration(); // TODO verify not nested
        $classNode->parent = $parentNode;
        $classNode->abstractOrFinalModifier = $this->eatOptional(TokenKind::AbstractKeyword, TokenKind::FinalKeyword);
        $classNode->classKeyword = $this->eat1(TokenKind::ClassKeyword);
        $classNode->name = $this->eat($this->nameOrReservedWordTokens); // TODO should be any
        $classNode->name->kind = TokenKind::Name;
        $classNode->classBaseClause = $this->parseClassBaseClause($classNode);
        $classNode->classInterfaceClause = $this->parseClassInterfaceClause($classNode);
        $classNode->classMembers = $this->parseClassMembers($classNode);
        return $classNode;
    }

    private function parseClassMembers($parentNode) : Node {
        $classMembers = new ClassMembersNode();
        $classMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
        $classMembers->classMemberDeclarations = $this->parseList($classMembers, ParseContext::ClassMembers);
        $classMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
        $classMembers->parent = $parentNode;
        return $classMembers;
    }

    private function parseFunctionDeclaration($parentNode) {
        $functionNode = new FunctionDeclaration();
        $this->parseFunctionType($functionNode);
        $functionNode->parent = $parentNode;
        return $functionNode;
    }

    private function parseMethodDeclaration($parentNode, $modifiers) {
        $methodDeclaration = new MethodDeclaration();
        $methodDeclaration->modifiers = $modifiers;
        $this->parseFunctionType($methodDeclaration, true);
        $methodDeclaration->parent = $parentNode;
        return $methodDeclaration;
    }

    private function parseParameterFn() {
        return function ($parentNode) {
            $parameter = new Parameter();
            $parameter->parent = $parentNode;
            $parameter->questionToken = $this->eatOptional1(TokenKind::QuestionToken);
            $parameter->typeDeclaration = $this->tryParseParameterTypeDeclaration($parameter);
            $parameter->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
            // TODO add post-parse rule that prevents assignment
            // TODO add post-parse rule that requires only last parameter be variadic
            $parameter->dotDotDotToken = $this->eatOptional1(TokenKind::DotDotDotToken);
            $parameter->variableName = $this->eat1(TokenKind::VariableName);
            $parameter->equalsToken = $this->eatOptional1(TokenKind::EqualsToken);
            if ($parameter->equalsToken !== null) {
                // TODO add post-parse rule that checks for invalid assignments
                $parameter->default = $this->parseExpression($parameter);
            }
            return $parameter;
        };
    }

    private function parseReturnTypeDeclaration($parentNode) {
        $returnTypeDeclaration =
            $this->eatOptional($this->returnTypeDeclarationTokens)
            ?? $this->parseQualifiedName($parentNode)
            ?? new MissingToken(TokenKind::ReturnType, $this->getCurrentToken()->fullStart);

        return $returnTypeDeclaration;
    }

    private function tryParseParameterTypeDeclaration($parentNode) {
        $parameterTypeDeclaration =
            $this->eatOptional($this->parameterTypeDeclarationTokens) ?? $this->parseQualifiedName($parentNode);
        return $parameterTypeDeclaration;
    }

    private function parseCompoundStatement($parentNode) {
        $compoundStatement = new CompoundStatementNode();
        $compoundStatement->openBrace = $this->eat1(TokenKind::OpenBraceToken);
        $compoundStatement->statements =  $this->parseList($compoundStatement, ParseContext::BlockStatements);
        $compoundStatement->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
        $compoundStatement->parent = $parentNode;
        return $compoundStatement;
    }

    private function array_push_list(& $array, $list) {
        foreach ($list as $item) {
            $array[] = $item;
        }
    }

    private function isClassMemberDeclarationStart(Token $token) {
        switch ($token->kind) {
            // const-modifier
            case TokenKind::ConstKeyword:

            // visibility-modifier
            case TokenKind::PublicKeyword:
            case TokenKind::ProtectedKeyword:
            case TokenKind::PrivateKeyword:

            // static-modifier
            case TokenKind::StaticKeyword:

            // class-modifier
            case TokenKind::AbstractKeyword:
            case TokenKind::FinalKeyword:

            case TokenKind::VarKeyword:

            case TokenKind::FunctionKeyword:

            case TokenKind::UseKeyword:
                return true;

        }

        return false;
    }

    private function isStatementStart(Token $token) {
        // https://github.com/php/php-langspec/blob/master/spec/19-grammar.md#statements
        switch ($token->kind) {
            // Compound Statements
            case TokenKind::OpenBraceToken:

            // Labeled Statements
            case TokenKind::Name:
//            case TokenKind::CaseKeyword: // TODO update spec
//            case TokenKind::DefaultKeyword:

            // Expression Statements
            case TokenKind::SemicolonToken:
            case TokenKind::IfKeyword:
            case TokenKind::SwitchKeyword:

            // Iteration Statements
            case TokenKind::WhileKeyword:
            case TokenKind::DoKeyword:
            case TokenKind::ForKeyword:
            case TokenKind::ForeachKeyword:

            // Jump Statements
            case TokenKind::GotoKeyword:
            case TokenKind::ContinueKeyword:
            case TokenKind::BreakKeyword:
            case TokenKind::ReturnKeyword:
            case TokenKind::ThrowKeyword:

            // The try Statement
            case TokenKind::TryKeyword:

            // The declare Statement
            case TokenKind::DeclareKeyword:

            // const-declaration
            case TokenKind::ConstKeyword:

            // function-definition
            case TokenKind::FunctionKeyword:

            // class-declaration
            case TokenKind::ClassKeyword:
            case TokenKind::AbstractKeyword:
            case TokenKind::FinalKeyword:

            // interface-declaration
            case TokenKind::InterfaceKeyword:

            // trait-declaration
            case TokenKind::TraitKeyword:

            // namespace-definition
            case TokenKind::NamespaceKeyword:

            // namespace-use-declaration
            case TokenKind::UseKeyword:

            // global-declaration
            case TokenKind::GlobalKeyword:

            // function-static-declaration
            case TokenKind::StaticKeyword:

            case TokenKind::ScriptSectionEndTag:
                return true;

            default:
                return $this->isExpressionStart($token);
        }
    }

    private function isExpressionStart($token) {
        return ($this->isExpressionStartFn())($token);
    }

    private function isExpressionStartFn() {
        return function ($token) {
            switch ($token->kind) {
                // Script Inclusion Expression
                case TokenKind::RequireKeyword:
                case TokenKind::RequireOnceKeyword:
                case TokenKind::IncludeKeyword:
                case TokenKind::IncludeOnceKeyword:

                // yield-expression
                case TokenKind::YieldKeyword:
                case TokenKind::YieldFromKeyword:

                // object-creation-expression
                case TokenKind::NewKeyword:
                case TokenKind::CloneKeyword:
                    return true;

                // unary-op-expression
                case TokenKind::PlusToken:
                case TokenKind::MinusToken:
                case TokenKind::ExclamationToken:
                case TokenKind::TildeToken:

                // error-control-expression
                case TokenKind::AtSymbolToken:

                // prefix-increment-expression
                case TokenKind::PlusPlusToken:
                // prefix-decrement-expression
                case TokenKind::MinusMinusToken:
                    return true;

                // variable-name
                case TokenKind::VariableName:
                case TokenKind::DollarToken:
                    return true;

                // qualified-name
                case TokenKind::Name:
                case TokenKind::BackslashToken:
                    return true;
                case TokenKind::NamespaceKeyword:
                    // TODO currently only supports qualified-names, but eventually parse namespace declarations
                    return $this->checkToken(TokenKind::BackslashToken);

                // literal
                case TokenKind::DecimalLiteralToken: // TODO merge dec, oct, hex, bin, float -> NumericLiteral
                case TokenKind::OctalLiteralToken:
                case TokenKind::HexadecimalLiteralToken:
                case TokenKind::BinaryLiteralToken:
                case TokenKind::FloatingLiteralToken:
                case TokenKind::InvalidOctalLiteralToken:
                case TokenKind::InvalidHexadecimalLiteral:
                case TokenKind::InvalidBinaryLiteral:
                case TokenKind::IntegerLiteralToken:

                case TokenKind::StringLiteralToken:

                case TokenKind::SingleQuoteToken:
                case TokenKind::DoubleQuoteToken:
                case TokenKind::HeredocStart:
                case TokenKind::BacktickToken:

                // array-creation-expression
                case TokenKind::ArrayKeyword:
                case TokenKind::OpenBracketToken:

                // intrinsic-construct
                case TokenKind::EchoKeyword:
                case TokenKind::ListKeyword:
                case TokenKind::UnsetKeyword:

                // intrinsic-operator
                case TokenKind::EmptyKeyword:
                case TokenKind::EvalKeyword:
                case TokenKind::ExitKeyword:
                case TokenKind::DieKeyword:
                case TokenKind::IsSetKeyword:
                case TokenKind::PrintKeyword:

                // ( expression )
                case TokenKind::OpenParenToken:
                case TokenKind::ArrayCastToken:
                case TokenKind::BoolCastToken:
                case TokenKind::DoubleCastToken:
                case TokenKind::IntCastToken:
                case TokenKind::ObjectCastToken:
                case TokenKind::StringCastToken:
                case TokenKind::UnsetCastToken:

                // anonymous-function-creation-expression
                case TokenKind::StaticKeyword:
                case TokenKind::FunctionKeyword:
                    return true;
            }
            return \in_array($token->kind, $this->reservedWordTokens);
        };
    }

    /**
     * @param Node $parentNode
     * @return Token|MissingToken|Node
     */
    private function parsePrimaryExpression($parentNode) {
        $token = $this->getCurrentToken();
        switch ($token->kind) {
            // variable-name
            case TokenKind::VariableName: // TODO special case $this
            case TokenKind::DollarToken:
                return $this->parseSimpleVariable($parentNode);

            // qualified-name
            case TokenKind::Name: // TODO Qualified name
            case TokenKind::BackslashToken:
            case TokenKind::NamespaceKeyword:
                return $this->parseQualifiedName($parentNode);

            case TokenKind::DecimalLiteralToken: // TODO merge dec, oct, hex, bin, float -> NumericLiteral
            case TokenKind::OctalLiteralToken:
            case TokenKind::HexadecimalLiteralToken:
            case TokenKind::BinaryLiteralToken:
            case TokenKind::FloatingLiteralToken:
            case TokenKind::InvalidOctalLiteralToken:
            case TokenKind::InvalidHexadecimalLiteral:
            case TokenKind::InvalidBinaryLiteral:
            case TokenKind::IntegerLiteralToken:
                return $this->parseNumericLiteralExpression($parentNode);

            case TokenKind::StringLiteralToken:
                return $this->parseStringLiteralExpression($parentNode);

            case TokenKind::DoubleQuoteToken:
            case TokenKind::SingleQuoteToken:
            case TokenKind::HeredocStart:
            case TokenKind::BacktickToken:
                return $this->parseStringLiteralExpression2($parentNode);

            // TODO constant-expression

            // array-creation-expression
            case TokenKind::ArrayKeyword:
            case TokenKind::OpenBracketToken:
                return $this->parseArrayCreationExpression($parentNode);

            // intrinsic-construct
            case TokenKind::EchoKeyword:
                return $this->parseEchoExpression($parentNode);
            case TokenKind::ListKeyword:
                return $this->parseListIntrinsicExpression($parentNode);
            case TokenKind::UnsetKeyword:
                return $this->parseUnsetIntrinsicExpression($parentNode);

            // intrinsic-operator
            case TokenKind::EmptyKeyword:
                return $this->parseEmptyIntrinsicExpression($parentNode);
            case TokenKind::EvalKeyword:
                return $this->parseEvalIntrinsicExpression($parentNode);

            case TokenKind::ExitKeyword:
            case TokenKind::DieKeyword:
                return $this->parseExitIntrinsicExpression($parentNode);

            case TokenKind::IsSetKeyword:
                return $this->parseIssetIntrinsicExpression($parentNode);

            case TokenKind::PrintKeyword:
                return $this->parsePrintIntrinsicExpression($parentNode);

            // ( expression )
            case TokenKind::OpenParenToken:
                return $this->parseParenthesizedExpression($parentNode);

            // anonymous-function-creation-expression
            case TokenKind::StaticKeyword:
                // handle `static::`, `static(`, `new static;`, `instanceof static`
                if (($this->lookahead([TokenKind::ColonColonToken, TokenKind::OpenParenToken])) ||
                    (!$this->lookahead(TokenKind::FunctionKeyword))
                ) {
                    return $this->parseQualifiedName($parentNode);
                }
                // Could be `static function` anonymous function creation expression, so flow through
            case TokenKind::FunctionKeyword:
                return $this->parseAnonymousFunctionCreationExpression($parentNode);

            case TokenKind::TrueReservedWord:
            case TokenKind::FalseReservedWord:
            case TokenKind::NullReservedWord:
                // handle `true::`, `true(`, `true\`
                if ($this->lookahead([TokenKind::BackslashToken, TokenKind::ColonColonToken, TokenKind::OpenParenToken])) {
                    return $this->parseQualifiedName($parentNode);
                }
                return $this->parseReservedWordExpression($parentNode);
        }
        if (\in_array($token->kind, TokenStringMaps::RESERVED_WORDS)) {
            return $this->parseQualifiedName($parentNode);
        }

        return new MissingToken(TokenKind::Expression, $token->fullStart);
    }

    private function parseEmptyStatement($parentNode) {
        $emptyStatement = new EmptyStatement();
        $emptyStatement->parent = $parentNode;
        $emptyStatement->semicolon = $this->eat1(TokenKind::SemicolonToken);
        return $emptyStatement;
    }

    private function parseStringLiteralExpression($parentNode) {
        // TODO validate input token
        $expression = new StringLiteral();
        $expression->parent = $parentNode;
        $expression->children = $this->getCurrentToken(); // TODO - merge string types
        $this->advanceToken();
        return $expression;
    }

    private function parseStringLiteralExpression2($parentNode) {
        // TODO validate input token
        $expression = new StringLiteral();
        $expression->parent = $parentNode;
        $expression->startQuote = $this->eat(TokenKind::SingleQuoteToken, TokenKind::DoubleQuoteToken, TokenKind::HeredocStart, TokenKind::BacktickToken);
        $expression->children = array();

        while (true) {
            switch ($this->getCurrentToken()->kind) {
                case TokenKind::DollarOpenBraceToken:
                case TokenKind::OpenBraceDollarToken:
                    $expression->children[] = $this->eat(TokenKind::DollarOpenBraceToken, TokenKind::OpenBraceDollarToken);
                    // TODO: Reject ${var->prop} and ${(var->prop)} without rejecting ${var+otherVar}
                    // Currently, this fails to reject ${var->prop} (because `var` has TokenKind::Name instead of StringVarname)
                    if ($this->getCurrentToken()->kind === TokenKind::StringVarname) {
                        $expression->children[] = $this->parseComplexDollarTemplateStringExpression($expression);
                    } else {
                        $expression->children[] = $this->parseExpression($expression);
                    }
                    $expression->children[] = $this->eat1(TokenKind::CloseBraceToken);
                    continue;
                case $startQuoteKind = $expression->startQuote->kind:
                case TokenKind::EndOfFileToken:
                case TokenKind::HeredocEnd:
                    $expression->endQuote = $this->eat($startQuoteKind, TokenKind::HeredocEnd);
                    return $expression;
                case TokenKind::VariableName:
                    $expression->children[] = $this->parseTemplateStringExpression($expression);
                    continue;
                default:
                    $expression->children[] = $this->getCurrentToken();
                    $this->advanceToken();
                    continue;
            }
        }
    }

    /**
     * This is used to parse the contents of `"${...}"` expressions.
     *
     * Supported: x, x[0], x[$y]
     * Not supported: $x->p1, x[0][1], etc.
     * @see parseTemplateStringExpression
     *
     * Precondition: getCurrentToken()->kind === TokenKind::StringVarname
     */
    private function parseComplexDollarTemplateStringExpression($parentNode) {
        $var = $this->parseSimpleVariable($parentNode);
        $token = $this->getCurrentToken();
        if ($token->kind === TokenKind::OpenBracketToken) {
            return $this->parseTemplateStringSubscriptExpression($var);
        }
        return $var;
    }

    /**
     * Double-quoted and heredoc strings support a basic set of expression types, described in http://php.net/manual/en/language.types.string.php#language.types.string.parsing
     * Supported: $x, $x->p, $x[0], $x[$y]
     * Not supported: $x->p1->p2, $x[0][1], etc.
     * Since there is a relatively small finite set of allowed forms, I implement it here rather than trying to reuse the general expression parsing code.
     */
    private function parseTemplateStringExpression($parentNode) {
        $token = $this->getCurrentToken();
        if ($token->kind === TokenKind::VariableName) {
            $var = $this->parseSimpleVariable($parentNode);
            $token = $this->getCurrentToken();
            if ($token->kind === TokenKind::OpenBracketToken) {
                return $this->parseTemplateStringSubscriptExpression($var);
            } else if ($token->kind === TokenKind::ArrowToken) {
                return $this->parseTemplateStringMemberAccessExpression($var);
            } else {
                return $var;
            }
        }

        return null;
    }

    private function parseTemplateStringSubscriptExpression($postfixExpression) : SubscriptExpression {
        $subscriptExpression = new SubscriptExpression();
        $subscriptExpression->parent = $postfixExpression->parent;
        $postfixExpression->parent = $subscriptExpression;

        $subscriptExpression->postfixExpression = $postfixExpression;
        $subscriptExpression->openBracketOrBrace = $this->eat1(TokenKind::OpenBracketToken); // Only [] syntax is supported, not {}
        $token = $this->getCurrentToken();
        if ($token->kind === TokenKind::VariableName) {
            $subscriptExpression->accessExpression = $this->parseSimpleVariable($subscriptExpression);
        } elseif ($token->kind === TokenKind::IntegerLiteralToken) {
            $subscriptExpression->accessExpression = $this->parseNumericLiteralExpression($subscriptExpression);
        } elseif ($token->kind === TokenKind::StringLiteralToken) {
            // TODO: investigate if this should add other uncommon types of tokens for strings/numbers mentioned in parsePrimaryExpression()
            $subscriptExpression->accessExpression = $this->parseStringLiteralExpression($subscriptExpression);
        } elseif ($token->kind === TokenKind::Name) {
            $subscriptExpression->accessExpression = $this->parseTemplateStringSubscriptStringLiteral($subscriptExpression);
        } else {
            $subscriptExpression->accessExpression = new MissingToken(TokenKind::Expression, $token->fullStart);
        }

        $subscriptExpression->closeBracketOrBrace = $this->eat1(TokenKind::CloseBracketToken);

        return $subscriptExpression;
    }

    private function parseTemplateStringSubscriptStringLiteral($parentNode) : StringLiteral {
        $expression = new StringLiteral();
        $expression->parent = $parentNode;
        $expression->children = $this->eat1(TokenKind::Name);
        return $expression;
    }

    private function parseTemplateStringMemberAccessExpression($expression) : MemberAccessExpression {
        $memberAccessExpression = new MemberAccessExpression();
        $memberAccessExpression->parent = $expression->parent;
        $expression->parent = $memberAccessExpression;

        $memberAccessExpression->dereferencableExpression = $expression;
        $memberAccessExpression->arrowToken = $this->eat1(TokenKind::ArrowToken);
        $memberAccessExpression->memberName = $this->eat1(TokenKind::Name);

        return $memberAccessExpression;
    }

    private function parseNumericLiteralExpression($parentNode) {
        $numericLiteral = new NumericLiteral();
        $numericLiteral->parent = $parentNode;
        $numericLiteral->children = $this->getCurrentToken();
        $this->advanceToken();
        return $numericLiteral;
    }

    private function parseReservedWordExpression($parentNode) {
        $reservedWord = new ReservedWord();
        $reservedWord->parent = $parentNode;
        $reservedWord->children = $this->getCurrentToken();
        $this->advanceToken();
        return $reservedWord;
    }

    private function isModifier($token) {
        switch ($token->kind) {
            // class-modifier
            case TokenKind::AbstractKeyword:
            case TokenKind::FinalKeyword:

            // visibility-modifier
            case TokenKind::PublicKeyword:
            case TokenKind::ProtectedKeyword:
            case TokenKind::PrivateKeyword:

            // static-modifier
            case TokenKind::StaticKeyword:

            // var
            case TokenKind::VarKeyword:
                return true;
        }
        return false;
    }

    private function parseModifiers() {
        $modifiers = array();
        $token = $this->getCurrentToken();
        while ($this->isModifier($token)) {
            $modifiers[] = $token;
            $this->advanceToken();
            $token = $this->getCurrentToken();
        }
        return $modifiers;
    }

    private function isParameterStartFn() {
        return function ($token) {
            switch ($token->kind) {
                case TokenKind::DotDotDotToken:

                // qualified-name
                case TokenKind::Name: // http://php.net/manual/en/language.namespaces.rules.php
                case TokenKind::BackslashToken:
                case TokenKind::NamespaceKeyword:

                case TokenKind::AmpersandToken:

                case TokenKind::VariableName:
                    return true;

                // nullable-type
                case TokenKind::QuestionToken:
                    return true;
            }

            // scalar-type
            return \in_array($token->kind, $this->parameterTypeDeclarationTokens);
        };
    }

    /**
     * @param string $className (name of subclass of DelimitedList)
     * @param int $delimiter
     * @param callable $isElementStartFn
     * @param callable $parseElementFn
     * @param Node $parentNode
     * @param bool $allowEmptyElements
     * @return DelimitedList|null instance of $className
     */
    private function parseDelimitedList($className, $delimiter, $isElementStartFn, $parseElementFn, $parentNode, $allowEmptyElements = false) {
        // TODO consider allowing empty delimiter to be more tolerant
        $node = new $className();
        $token = $this->getCurrentToken();
        do {
            if ($isElementStartFn($token)) {
                $node->addElement($parseElementFn($node));
            } elseif (!$allowEmptyElements || ($allowEmptyElements && !$this->checkToken($delimiter))) {
                break;
            }

            $delimeterToken = $this->eatOptional($delimiter);
            if ($delimeterToken !== null) {
                $node->addElement($delimeterToken);
            }
            $token = $this->getCurrentToken();
            // TODO ERROR CASE - no delimeter, but a param follows
        } while ($delimeterToken !== null);


        $node->parent = $parentNode;
        if ($node->children === null) {
            return null;
        }
        return $node;
    }

    private function isQualifiedNameStart($token) {
        return ($this->isQualifiedNameStartFn())($token);
    }

    private function isQualifiedNameStartFn() {
        return function ($token) {
            switch ($token->kind) {
                case TokenKind::BackslashToken:
                case TokenKind::NamespaceKeyword:
                case TokenKind::Name:
                    return true;
            }
            return false;
        };
    }

    private function parseQualifiedName($parentNode) {
        return ($this->parseQualifiedNameFn())($parentNode);
    }

    private function parseQualifiedNameFn() {
        return function ($parentNode) {
            $node = new QualifiedName();
            $node->parent = $parentNode;
            $node->relativeSpecifier = $this->parseRelativeSpecifier($node);
            if (!isset($node->relativeSpecifier)) {
                $node->globalSpecifier = $this->eatOptional1(TokenKind::BackslashToken);
            }

            $nameParts =
                $this->parseDelimitedList(
                    DelimitedList\QualifiedNameParts::class,
                    TokenKind::BackslashToken,
                    function ($token) {
                        // a\static() <- VALID
                        // a\static\b <- INVALID
                        // a\function <- INVALID
                        // a\true\b <-VALID
                        // a\b\true <-VALID
                        // a\static::b <-VALID
                        // TODO more tests
                        return $this->lookahead(TokenKind::BackslashToken)
                            ? in_array($token->kind, $this->nameOrReservedWordTokens)
                            : in_array($token->kind, $this->nameOrStaticOrReservedWordTokens);
                    },
                    function ($parentNode) {
                        $name = $this->lookahead(TokenKind::BackslashToken)
                            ? $this->eat($this->nameOrReservedWordTokens)
                            : $this->eat($this->nameOrStaticOrReservedWordTokens); // TODO support keyword name
                        $name->kind = TokenKind::Name; // bool/true/null/static should not be treated as keywords in this case
                        return $name;
                    }, $node);
            if ($nameParts === null && $node->globalSpecifier === null && $node->relativeSpecifier === null) {
                return null;
            }

            $node->nameParts = $nameParts ? $nameParts->children : [];

            return $node;
        };
    }

    private function parseRelativeSpecifier($parentNode) {
        $node = new RelativeSpecifier();
        $node->parent = $parentNode;
        $node->namespaceKeyword = $this->eatOptional1(TokenKind::NamespaceKeyword);
        if ($node->namespaceKeyword !== null) {
            $node->backslash = $this->eat1(TokenKind::BackslashToken);
        }
        if (isset($node->backslash)) {
            return $node;
        }
        return null;
    }

    private function parseFunctionType(Node $functionDeclaration, $canBeAbstract = false, $isAnonymous = false) {
        $functionDeclaration->functionKeyword = $this->eat1(TokenKind::FunctionKeyword);
        $functionDeclaration->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
        $functionDeclaration->name = $isAnonymous
            ? $this->eatOptional($this->nameOrKeywordOrReservedWordTokens)
            : $this->eat($this->nameOrKeywordOrReservedWordTokens);

        if (isset($functionDeclaration->name)) {
            $functionDeclaration->name->kind = TokenKind::Name;
        }

        if ($isAnonymous && isset($functionDeclaration->name)) {
            // Anonymous functions should not have names
            $functionDeclaration->name = new SkippedToken($functionDeclaration->name); // TODO instaed handle this during post-walk
        }

        $functionDeclaration->openParen = $this->eat1(TokenKind::OpenParenToken);
        $functionDeclaration->parameters = $this->parseDelimitedList(
            DelimitedList\ParameterDeclarationList::class,
            TokenKind::CommaToken,
            $this->isParameterStartFn(),
            $this->parseParameterFn(),
            $functionDeclaration);
        $functionDeclaration->closeParen = $this->eat1(TokenKind::CloseParenToken);
        if ($isAnonymous) {
            $functionDeclaration->anonymousFunctionUseClause = $this->parseAnonymousFunctionUseClause($functionDeclaration);
        }

        if ($this->checkToken(TokenKind::ColonToken)) {
            $functionDeclaration->colonToken = $this->eat1(TokenKind::ColonToken);
            $functionDeclaration->questionToken = $this->eatOptional1(TokenKind::QuestionToken);
            $functionDeclaration->returnType = $this->parseReturnTypeDeclaration($functionDeclaration);
        }

        if ($canBeAbstract) {
            $functionDeclaration->compoundStatementOrSemicolon = $this->eatOptional1(TokenKind::SemicolonToken);
        }

        if (!isset($functionDeclaration->compoundStatementOrSemicolon)) {
            $functionDeclaration->compoundStatementOrSemicolon = $this->parseCompoundStatement($functionDeclaration);
        }
    }

    private function parseNamedLabelStatement($parentNode) {
        $namedLabelStatement = new NamedLabelStatement();
        $namedLabelStatement->parent = $parentNode;
        $namedLabelStatement->name = $this->eat1(TokenKind::Name);
        $namedLabelStatement->colon = $this->eat1(TokenKind::ColonToken);
        $namedLabelStatement->statement = $this->parseStatement($namedLabelStatement);
        return $namedLabelStatement;
    }

    private function lookahead(...$expectedKinds) : bool {
        $startPos = $this->lexer->getCurrentPosition();
        $startToken = $this->token;
        $succeeded = true;
        foreach ($expectedKinds as $kind) {
            $token = $this->lexer->scanNextToken();
            $currentPosition = $this->lexer->getCurrentPosition();
            $endOfFilePosition = $this->lexer->getEndOfFilePosition();
            if (\is_array($kind)) {
                $succeeded = false;
                foreach ($kind as $kindOption) {
                    if ($currentPosition <= $endOfFilePosition && $token->kind === $kindOption) {
                        $succeeded = true;
                        break;
                    }
                }
            } else {
                if ($currentPosition > $endOfFilePosition || $token->kind !== $kind) {
                    $succeeded = false;
                    break;
                }
            }
        }
        $this->lexer->setCurrentPosition($startPos);
        $this->token = $startToken;
        return $succeeded;
    }

    private function checkToken($expectedKind) : bool {
        return $this->getCurrentToken()->kind === $expectedKind;
    }

    private function parseIfStatement($parentNode) {
        $ifStatement = new IfStatementNode();
        $ifStatement->parent = $parentNode;
        $ifStatement->ifKeyword = $this->eat1(TokenKind::IfKeyword);
        $ifStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
        $ifStatement->expression = $this->parseExpression($ifStatement);
        $ifStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
        if ($this->checkToken(TokenKind::ColonToken)) {
            $ifStatement->colon = $this->eat1(TokenKind::ColonToken);
            $ifStatement->statements = $this->parseList($ifStatement, ParseContext::IfClause2Elements);
        } else {
            $ifStatement->statements = $this->parseStatement($ifStatement);
        }
        $ifStatement->elseIfClauses = array(); // TODO - should be some standard for empty arrays vs. null?
        while ($this->checkToken(TokenKind::ElseIfKeyword)) {
            $ifStatement->elseIfClauses[] = $this->parseElseIfClause($ifStatement);
        }

        if ($this->checkToken(TokenKind::ElseKeyword)) {
            $ifStatement->elseClause = $this->parseElseClause($ifStatement);
        }

        $ifStatement->endifKeyword = $this->eatOptional1(TokenKind::EndIfKeyword);
        if ($ifStatement->endifKeyword) {
            $ifStatement->semicolon = $this->eatSemicolonOrAbortStatement();
        }

        return $ifStatement;
    }

    private function parseElseIfClause($parentNode) {
        $elseIfClause = new ElseIfClauseNode();
        $elseIfClause->parent = $parentNode;
        $elseIfClause->elseIfKeyword = $this->eat1(TokenKind::ElseIfKeyword);
        $elseIfClause->openParen = $this->eat1(TokenKind::OpenParenToken);
        $elseIfClause->expression = $this->parseExpression($elseIfClause);
        $elseIfClause->closeParen = $this->eat1(TokenKind::CloseParenToken);
        if ($this->checkToken(TokenKind::ColonToken)) {
            $elseIfClause->colon = $this->eat1(TokenKind::ColonToken);
            $elseIfClause->statements = $this->parseList($elseIfClause, ParseContext::IfClause2Elements);
        } else {
            $elseIfClause->statements = $this->parseStatement($elseIfClause);
        }
        return $elseIfClause;
    }

    private function parseElseClause($parentNode) {
        $elseClause = new ElseClauseNode();
        $elseClause->parent = $parentNode;
        $elseClause->elseKeyword = $this->eat1(TokenKind::ElseKeyword);
        if ($this->checkToken(TokenKind::ColonToken)) {
            $elseClause->colon = $this->eat1(TokenKind::ColonToken);
            $elseClause->statements = $this->parseList($elseClause, ParseContext::IfClause2Elements);
        } else {
            $elseClause->statements = $this->parseStatement($elseClause);
        }
        return $elseClause;
    }

    private function parseSwitchStatement($parentNode) {
        $switchStatement = new SwitchStatementNode();
        $switchStatement->parent = $parentNode;
        $switchStatement->switchKeyword = $this->eat1(TokenKind::SwitchKeyword);
        $switchStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
        $switchStatement->expression = $this->parseExpression($switchStatement);
        $switchStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
        $switchStatement->openBrace = $this->eatOptional1(TokenKind::OpenBraceToken);
        $switchStatement->colon = $this->eatOptional1(TokenKind::ColonToken);
        $switchStatement->caseStatements = $this->parseList($switchStatement, ParseContext::SwitchStatementElements);
        if ($switchStatement->colon !== null) {
            $switchStatement->endswitch = $this->eat1(TokenKind::EndSwitchKeyword);
            $switchStatement->semicolon = $this->eatSemicolonOrAbortStatement();
        } else {
            $switchStatement->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
        }

        return $switchStatement;
    }

    private function parseCaseOrDefaultStatement() {
        return function ($parentNode) {
            $caseStatement = new CaseStatementNode();
            $caseStatement->parent = $parentNode;
            // TODO add error checking
            $caseStatement->caseKeyword = $this->eat(TokenKind::CaseKeyword, TokenKind::DefaultKeyword);
            if ($caseStatement->caseKeyword->kind === TokenKind::CaseKeyword) {
                $caseStatement->expression = $this->parseExpression($caseStatement);
            }
            $caseStatement->defaultLabelTerminator = $this->eat(TokenKind::ColonToken, TokenKind::SemicolonToken);
            $caseStatement->statementList = $this->parseList($caseStatement, ParseContext::CaseStatementElements);
            return $caseStatement;
        };
    }

    private function parseWhileStatement($parentNode) {
        $whileStatement = new WhileStatement();
        $whileStatement->parent = $parentNode;
        $whileStatement->whileToken = $this->eat1(TokenKind::WhileKeyword);
        $whileStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
        $whileStatement->expression = $this->parseExpression($whileStatement);
        $whileStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
        $whileStatement->colon = $this->eatOptional1(TokenKind::ColonToken);
        if ($whileStatement->colon !== null) {
            $whileStatement->statements = $this->parseList($whileStatement, ParseContext::WhileStatementElements);
            $whileStatement->endWhile = $this->eat1(TokenKind::EndWhileKeyword);
            $whileStatement->semicolon = $this->eatSemicolonOrAbortStatement();
        } else {
            $whileStatement->statements = $this->parseStatement($whileStatement);
        }
        return $whileStatement;
    }

    /**
     * @param Node $parentNode
     * @param bool $force
     * @return Node|MissingToken|array - The expression, or a missing token, or (if $force) an array containing a missed and skipped token
     */
    private function parseExpression($parentNode, $force = false) {
        $token = $this->getCurrentToken();
        if ($token->kind === TokenKind::EndOfFileToken) {
            return new MissingToken(TokenKind::Expression, $token->fullStart);
        }

        $expression = ($this->parseExpressionFn())($parentNode);
        if ($force && $expression instanceof MissingToken) {
            $expression = [$expression, new SkippedToken($token)];
            $this->advanceToken();
        }

        return $expression;
    }

    private function parseExpressionFn() {
        return function ($parentNode) {
            return $this->parseBinaryExpressionOrHigher(0, $parentNode);
        };
    }

    /**
     * @param Node $parentNode
     * @return Expression
     */
    private function parseUnaryExpressionOrHigher($parentNode) {
        $token = $this->getCurrentToken();
        switch ($token->kind) {
            // unary-op-expression
            case TokenKind::PlusToken:
            case TokenKind::MinusToken:
            case TokenKind::ExclamationToken:
            case TokenKind::TildeToken:
                return $this->parseUnaryOpExpression($parentNode);

            // error-control-expression
            case TokenKind::AtSymbolToken:
                return $this->parseErrorControlExpression($parentNode);

            // prefix-increment-expression
            case TokenKind::PlusPlusToken:
            // prefix-decrement-expression
            case TokenKind::MinusMinusToken:
                return $this->parsePrefixUpdateExpression($parentNode);

            case TokenKind::ArrayCastToken:
            case TokenKind::BoolCastToken:
            case TokenKind::DoubleCastToken:
            case TokenKind::IntCastToken:
            case TokenKind::ObjectCastToken:
            case TokenKind::StringCastToken:
            case TokenKind::UnsetCastToken:
                return $this->parseCastExpression($parentNode);

            case TokenKind::OpenParenToken:
                // TODO remove duplication
                if ($this->lookahead(
                    [TokenKind::ArrayKeyword,
                    TokenKind::BinaryReservedWord,
                    TokenKind::BoolReservedWord,
                    TokenKind::BooleanReservedWord,
                    TokenKind::DoubleReservedWord,
                    TokenKind::IntReservedWord,
                    TokenKind::IntegerReservedWord,
                    TokenKind::FloatReservedWord,
                    TokenKind::ObjectReservedWord,
                    TokenKind::RealReservedWord,
                    TokenKind::StringReservedWord,
                    TokenKind::UnsetKeyword], TokenKind::CloseParenToken)) {
                    return $this->parseCastExpressionGranular($parentNode);
                }
                break;

/*

            case TokenKind::BacktickToken:
                return $this->parseShellCommandExpression($parentNode);

            case TokenKind::OpenParenToken:
                // TODO
//                return $this->parseCastExpressionGranular($parentNode);
                break;*/

            // object-creation-expression (postfix-expression)
            case TokenKind::NewKeyword:
                return $this->parseObjectCreationExpression($parentNode);

            // clone-expression (postfix-expression)
            case TokenKind::CloneKeyword:
                return $this->parseCloneExpression($parentNode);

            case TokenKind::YieldKeyword:
            case TokenKind::YieldFromKeyword:
                return $this->parseYieldExpression($parentNode);

            // include-expression
            // include-once-expression
            // require-expression
            // require-once-expression
            case TokenKind::IncludeKeyword:
            case TokenKind::IncludeOnceKeyword:
            case TokenKind::RequireKeyword:
            case TokenKind::RequireOnceKeyword:
                return $this->parseScriptInclusionExpression($parentNode);
        }

        $expression = $this->parsePrimaryExpression($parentNode);
        return $this->parsePostfixExpressionRest($expression);
    }

    /**
     * @param int $precedence
     * @param Node $parentNode
     * @return Expression
     */
    private function parseBinaryExpressionOrHigher($precedence, $parentNode) {
        $leftOperand = $this->parseUnaryExpressionOrHigher($parentNode);

        list($prevNewPrecedence, $prevAssociativity) = self::UNKNOWN_PRECEDENCE_AND_ASSOCIATIVITY;

        while (true) {
            $token = $this->getCurrentToken();

            list($newPrecedence, $associativity) = $this->getBinaryOperatorPrecedenceAndAssociativity($token);

            // Expressions using operators w/o associativity (equality, relational, instanceof)
            // cannot reference identical expression types within one of their operands.
            //
            // Example:
            //   $a < $b < $c // CASE 1: INVALID
            //   $a < $b === $c < $d // CASE 2: VALID
            //
            // In CASE 1, it is expected that we stop parsing the expression after the $b token.
            if ($prevAssociativity === Associativity::None && $prevNewPrecedence === $newPrecedence) {
                break;
            }

            // Precedence and associativity properties determine whether we recurse, and continue
            // building up the current operand, or whether we pop out.
            //
            // Example:
            //   $a + $b + $c // CASE 1: additive-expression (left-associative)
            //   $a = $b = $c // CASE 2: equality-expression (right-associative)
            //
            // CASE 1:
            // The additive-expression is left-associative, which means we expect the grouping to be:
            //   ($a + $b) + $c
            //
            // Because both + operators have the same precedence, and the + operator is left associative,
            // we expect the second + operator NOT to be consumed because $newPrecedence > $precedence => FALSE
            //
            // CASE 2:
            // The equality-expression is right-associative, which means we expect the grouping to be:
            //   $a = ($b = $c)
            //
            // Because both = operators have the same precedence, and the = operator is right-associative,
            // we expect the second = operator to be consumed because $newPrecedence >= $precedence => TRUE
            $shouldConsumeCurrentOperator =
                $associativity === Associativity::Right ?
                    $newPrecedence >= $precedence:
                    $newPrecedence > $precedence;

            if (!$shouldConsumeCurrentOperator) {
                break;
            }

            // Unlike every other binary expression, exponentiation operators take precedence over unary operators.
            //
            // Example:
            //   -3**2 => -9
            //
            // In these cases, we strip the UnaryExpression operator, and reassign $leftOperand to
            // $unaryExpression->operand.
            //
            // After we finish building the BinaryExpression, we rebuild the UnaryExpression so that it includes
            // the original operator, and the newly constructed exponentiation-expression as the operand.
            $shouldOperatorTakePrecedenceOverUnary = false;
            switch ($token->kind) {
                case TokenKind::AsteriskAsteriskToken:
                    $shouldOperatorTakePrecedenceOverUnary = $leftOperand instanceof UnaryExpression;
                    break;
                case TokenKind::EqualsToken:
                case TokenKind::AsteriskAsteriskEqualsToken:
                case TokenKind::AsteriskEqualsToken:
                case TokenKind::SlashEqualsToken:
                case TokenKind::PercentEqualsToken:
                case TokenKind::PlusEqualsToken:
                case TokenKind::MinusEqualsToken:
                case TokenKind::DotEqualsToken:
                case TokenKind::LessThanLessThanEqualsToken:
                case TokenKind::GreaterThanGreaterThanEqualsToken:
                case TokenKind::AmpersandEqualsToken:
                case TokenKind::CaretEqualsToken:
                case TokenKind::BarEqualsToken:
                case TokenKind::InstanceOfKeyword:
                    // Workarounds for https://github.com/Microsoft/tolerant-php-parser/issues/19#issue-201714377
                    // Parse `!$a = $b` as `!($a = $b)` - PHP constrains the Left Hand Side of an assignment to a variable. A unary operator (`@`, `!`, etc.) is not a variable.
                    // Instanceof has similar constraints for the LHS.
                    // So does `!$a += $b`
                    // TODO: Any other operators?
                    if ($leftOperand instanceof UnaryOpExpression) {
                        $shouldOperatorTakePrecedenceOverUnary = true;
                    }
                    break;
            }

            if ($shouldOperatorTakePrecedenceOverUnary) {
                $unaryExpression = $leftOperand;
                $leftOperand = $unaryExpression->operand;
            }

            $this->advanceToken();

            if ($token->kind === TokenKind::EqualsToken) {
                $byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
            }

            $leftOperand = $token->kind === TokenKind::QuestionToken ?
                $this->parseTernaryExpression($leftOperand, $token) :
                $this->makeBinaryExpression(
                    $leftOperand,
                    $token,
                    $byRefToken ?? null,
                    $this->parseBinaryExpressionOrHigher($newPrecedence, null),
                    $parentNode);

            // Rebuild the unary expression if we deconstructed it earlier.
            if ($shouldOperatorTakePrecedenceOverUnary) {
                $leftOperand->parent = $unaryExpression;
                $unaryExpression->operand = $leftOperand;
                $leftOperand = $unaryExpression;
            }

            // Hold onto these values, so we know whether we've hit duplicate non-associative operators,
            // and need to terminate early.
            $prevNewPrecedence = $newPrecedence;
            $prevAssociativity = $associativity;
        }
        return $leftOperand;
    }

    const OPERATOR_PRECEDENCE_AND_ASSOCIATIVITY =
        [
            // logical-inc-OR-expression-2 (L)
            TokenKind::OrKeyword => [6, Associativity::Left],

            // logical-exc-OR-expression-2 (L)
            TokenKind::XorKeyword=> [7, Associativity::Left],

            // logical-AND-expression-2 (L)
            TokenKind::AndKeyword=> [8, Associativity::Left],

            // simple-assignment-expression (R)
            // TODO byref-assignment-expression
            TokenKind::EqualsToken => [9, Associativity::Right],

            // compound-assignment-expression (R)
            TokenKind::AsteriskAsteriskEqualsToken => [9, Associativity::Right],
            TokenKind::AsteriskEqualsToken => [9, Associativity::Right],
            TokenKind::SlashEqualsToken => [9, Associativity::Right],
            TokenKind::PercentEqualsToken => [9, Associativity::Right],
            TokenKind::PlusEqualsToken => [9, Associativity::Right],
            TokenKind::MinusEqualsToken => [9, Associativity::Right],
            TokenKind::DotEqualsToken => [9, Associativity::Right],
            TokenKind::LessThanLessThanEqualsToken => [9, Associativity::Right],
            TokenKind::GreaterThanGreaterThanEqualsToken => [9, Associativity::Right],
            TokenKind::AmpersandEqualsToken => [9, Associativity::Right],
            TokenKind::CaretEqualsToken => [9, Associativity::Right],
            TokenKind::BarEqualsToken => [9, Associativity::Right],

            // TODO conditional-expression (L)
            TokenKind::QuestionToken => [10, Associativity::Left],
//            TokenKind::ColonToken => [9, Associativity::Left],

            // TODO coalesce-expression (R)
            TokenKind::QuestionQuestionToken => [9, Associativity::Right],

            //logical-inc-OR-expression-1 (L)
            TokenKind::BarBarToken => [12, Associativity::Left],

            // logical-AND-expression-1 (L)
            TokenKind::AmpersandAmpersandToken => [13, Associativity::Left],

            // bitwise-inc-OR-expression (L)
            TokenKind::BarToken => [14, Associativity::Left],

            // bitwise-exc-OR-expression (L)
            TokenKind::CaretToken => [15, Associativity::Left],

            // bitwise-AND-expression (L)
            TokenKind::AmpersandToken => [16, Associativity::Left],

            // equality-expression (X)
            TokenKind::EqualsEqualsToken => [17, Associativity::None],
            TokenKind::ExclamationEqualsToken => [17, Associativity::None],
            TokenKind::LessThanGreaterThanToken => [17, Associativity::None],
            TokenKind::EqualsEqualsEqualsToken => [17, Associativity::None],
            TokenKind::ExclamationEqualsEqualsToken => [17, Associativity::None],

            // relational-expression (X)
            TokenKind::LessThanToken => [18, Associativity::None],
            TokenKind::GreaterThanToken => [18, Associativity::None],
            TokenKind::LessThanEqualsToken => [18, Associativity::None],
            TokenKind::GreaterThanEqualsToken => [18, Associativity::None],
            TokenKind::LessThanEqualsGreaterThanToken => [18, Associativity::None],

            // shift-expression (L)
            TokenKind::LessThanLessThanToken => [19, Associativity::Left],
            TokenKind::GreaterThanGreaterThanToken => [19, Associativity::Left],

            // additive-expression (L)
            TokenKind::PlusToken => [20, Associativity::Left],
            TokenKind::MinusToken => [20, Associativity::Left],
            TokenKind::DotToken =>[20, Associativity::Left],

            // multiplicative-expression (L)
            TokenKind::AsteriskToken => [21, Associativity::Left],
            TokenKind::SlashToken => [21, Associativity::Left],
            TokenKind::PercentToken => [21, Associativity::Left],

            // instanceof-expression (X)
            TokenKind::InstanceOfKeyword => [22, Associativity::None],

            // exponentiation-expression (R)
            TokenKind::AsteriskAsteriskToken => [23, Associativity::Right]
        ];

    const UNKNOWN_PRECEDENCE_AND_ASSOCIATIVITY = [-1, -1];

    private function getBinaryOperatorPrecedenceAndAssociativity($token) {
        if (isset(self::OPERATOR_PRECEDENCE_AND_ASSOCIATIVITY[$token->kind])) {
            return self::OPERATOR_PRECEDENCE_AND_ASSOCIATIVITY[$token->kind];
        }

        return self::UNKNOWN_PRECEDENCE_AND_ASSOCIATIVITY;
    }

    private function makeBinaryExpression($leftOperand, $operatorToken, $byRefToken, $rightOperand, $parentNode) {
        $assignmentExpression = $operatorToken->kind === TokenKind::EqualsToken;
        $binaryExpression = $assignmentExpression ? new AssignmentExpression() : new BinaryExpression();
        $binaryExpression->parent = $parentNode;
        $leftOperand->parent = $binaryExpression;
        $rightOperand->parent = $binaryExpression;
        $binaryExpression->leftOperand = $leftOperand;
        $binaryExpression->operator = $operatorToken;
        if (isset($byRefToken)) {
            $binaryExpression->byRef = $byRefToken;
        }
        $binaryExpression->rightOperand = $rightOperand;
        return $binaryExpression;
    }

    private function parseDoStatement($parentNode) {
        $doStatement = new DoStatement();
        $doStatement->parent = $parentNode;
        $doStatement->do = $this->eat1(TokenKind::DoKeyword);
        $doStatement->statement = $this->parseStatement($doStatement);
        $doStatement->whileToken = $this->eat1(TokenKind::WhileKeyword);
        $doStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
        $doStatement->expression = $this->parseExpression($doStatement);
        $doStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
        $doStatement->semicolon = $this->eatSemicolonOrAbortStatement();
        return $doStatement;
    }

    private function parseForStatement($parentNode) {
        $forStatement = new ForStatement();
        $forStatement->parent = $parentNode;
        $forStatement->for = $this->eat1(TokenKind::ForKeyword);
        $forStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
        $forStatement->forInitializer = $this->parseExpressionList($forStatement); // TODO spec is redundant
        $forStatement->exprGroupSemicolon1 = $this->eat1(TokenKind::SemicolonToken);
        $forStatement->forControl = $this->parseExpressionList($forStatement);
        $forStatement->exprGroupSemicolon2 = $this->eat1(TokenKind::SemicolonToken);
        $forStatement->forEndOfLoop = $this->parseExpressionList($forStatement);
        $forStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
        $forStatement->colon = $this->eatOptional1(TokenKind::ColonToken);
        if ($forStatement->colon !== null) {
            $forStatement->statements = $this->parseList($forStatement, ParseContext::ForStatementElements);
            $forStatement->endFor = $this->eat1(TokenKind::EndForKeyword);
            $forStatement->endForSemicolon = $this->eatSemicolonOrAbortStatement();
        } else {
            $forStatement->statements = $this->parseStatement($forStatement);
        }
        return $forStatement;
    }

    private function parseForeachStatement($parentNode) {
        $foreachStatement = new ForeachStatement();
        $foreachStatement->parent = $parentNode;
        $foreachStatement->foreach = $this->eat1(TokenKind::ForeachKeyword);
        $foreachStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
        $foreachStatement->forEachCollectionName = $this->parseExpression($foreachStatement);
        $foreachStatement->asKeyword = $this->eat1(TokenKind::AsKeyword);
        $foreachStatement->foreachKey = $this->tryParseForeachKey($foreachStatement);
        $foreachStatement->foreachValue = $this->parseForeachValue($foreachStatement);
        $foreachStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
        $foreachStatement->colon = $this->eatOptional1(TokenKind::ColonToken);
        if ($foreachStatement->colon !== null) {
            $foreachStatement->statements = $this->parseList($foreachStatement, ParseContext::ForeachStatementElements);
            $foreachStatement->endForeach = $this->eat1(TokenKind::EndForEachKeyword);
            $foreachStatement->endForeachSemicolon = $this->eatSemicolonOrAbortStatement();
        } else {
            $foreachStatement->statements = $this->parseStatement($foreachStatement);
        }
        return $foreachStatement;
    }

    private function tryParseForeachKey($parentNode) {
        if (!$this->isExpressionStart($this->getCurrentToken())) {
            return null;
        }

        $startPos = $this->lexer->getCurrentPosition();
        $startToken = $this->getCurrentToken();
        $foreachKey = new ForeachKey();
        $foreachKey->parent = $parentNode;
        $foreachKey->expression = $this->parseExpression($foreachKey);

        if (!$this->checkToken(TokenKind::DoubleArrowToken)) {
            $this->lexer->setCurrentPosition($startPos);
            $this->token = $startToken;
            return null;
        }

        $foreachKey->arrow = $this->eat1(TokenKind::DoubleArrowToken);
        return $foreachKey;
    }

    private function parseForeachValue($parentNode) {
        $foreachValue = new ForeachValue();
        $foreachValue->parent = $parentNode;
        $foreachValue->ampersand = $this->eatOptional1(TokenKind::AmpersandToken);
        $foreachValue->expression = $this->parseExpression($foreachValue);
        return $foreachValue;
    }

    private function parseGotoStatement($parentNode) {
        $gotoStatement = new GotoStatement();
        $gotoStatement->parent = $parentNode;
        $gotoStatement->goto = $this->eat1(TokenKind::GotoKeyword);
        $gotoStatement->name = $this->eat1(TokenKind::Name);
        $gotoStatement->semicolon = $this->eatSemicolonOrAbortStatement();
        return $gotoStatement;
    }

    private function parseBreakOrContinueStatement($parentNode) {
        // TODO should be error checking if on top level
        $continueStatement = new BreakOrContinueStatement();
        $continueStatement->parent = $parentNode;
        $continueStatement->breakOrContinueKeyword = $this->eat(TokenKind::ContinueKeyword, TokenKind::BreakKeyword);

        if ($this->isExpressionStart($this->getCurrentToken())) {
            $continueStatement->breakoutLevel = $this->parseExpression($continueStatement);
        }

        $continueStatement->semicolon = $this->eatSemicolonOrAbortStatement();

        return $continueStatement;
    }

    private function parseReturnStatement($parentNode) {
        $returnStatement = new ReturnStatement();
        $returnStatement->parent = $parentNode;
        $returnStatement->returnKeyword = $this->eat1(TokenKind::ReturnKeyword);
        if ($this->isExpressionStart($this->getCurrentToken())) {
            $returnStatement->expression = $this->parseExpression($returnStatement);
        }
        $returnStatement->semicolon = $this->eatSemicolonOrAbortStatement();

        return $returnStatement;
    }

    private function parseThrowStatement($parentNode) {
        $throwStatement = new ThrowStatement();
        $throwStatement->parent = $parentNode;
        $throwStatement->throwKeyword = $this->eat1(TokenKind::ThrowKeyword);
        // TODO error for failures to parse expressions when not optional
        $throwStatement->expression = $this->parseExpression($throwStatement);
        $throwStatement->semicolon = $this->eatSemicolonOrAbortStatement();

        return $throwStatement;
    }

    private function parseTryStatement($parentNode) {
        $tryStatement = new TryStatement();
        $tryStatement->parent = $parentNode;
        $tryStatement->tryKeyword = $this->eat1(TokenKind::TryKeyword);
        $tryStatement->compoundStatement = $this->parseCompoundStatement($tryStatement); // TODO verifiy this is only compound

        $tryStatement->catchClauses = array(); // TODO - should be some standard for empty arrays vs. null?
        while ($this->checkToken(TokenKind::CatchKeyword)) {
            $tryStatement->catchClauses[] = $this->parseCatchClause($tryStatement);
        }

        if ($this->checkToken(TokenKind::FinallyKeyword)) {
            $tryStatement->finallyClause = $this->parseFinallyClause($tryStatement);
        }

        return $tryStatement;
    }

    private function parseCatchClause($parentNode) {
        $catchClause = new CatchClause();
        $catchClause->parent = $parentNode;
        $catchClause->catch = $this->eat1(TokenKind::CatchKeyword);
        $catchClause->openParen = $this->eat1(TokenKind::OpenParenToken);
        $catchClause->qualifiedName = $this->parseQualifiedName($catchClause); // TODO generate missing token or error if null
        $catchClause->variableName = $this->eat1(TokenKind::VariableName);
        $catchClause->closeParen = $this->eat1(TokenKind::CloseParenToken);
        $catchClause->compoundStatement = $this->parseCompoundStatement($catchClause);

        return $catchClause;
    }

    private function parseFinallyClause($parentNode) {
        $finallyClause = new FinallyClause();
        $finallyClause->parent = $parentNode;
        $finallyClause->finallyToken = $this->eat1(TokenKind::FinallyKeyword);
        $finallyClause->compoundStatement = $this->parseCompoundStatement($finallyClause);

        return $finallyClause;
    }

    private function parseDeclareStatement($parentNode) {
        $declareStatement = new DeclareStatement();
        $declareStatement->parent = $parentNode;
        $declareStatement->declareKeyword = $this->eat1(TokenKind::DeclareKeyword);
        $declareStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
        $declareStatement->declareDirective = $this->parseDeclareDirective($declareStatement);
        $declareStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);

        if ($this->checkToken(TokenKind::SemicolonToken)) {
            $declareStatement->semicolon = $this->eatSemicolonOrAbortStatement();
        } elseif ($this->checkToken(TokenKind::ColonToken)) {
            $declareStatement->colon = $this->eat1(TokenKind::ColonToken);
            $declareStatement->statements = $this->parseList($declareStatement, ParseContext::DeclareStatementElements);
            $declareStatement->enddeclareKeyword = $this->eat1(TokenKind::EndDeclareKeyword);
            $declareStatement->semicolon = $this->eatSemicolonOrAbortStatement();
        } else {
            $declareStatement->statements = $this->parseStatement($declareStatement);
        }

        return $declareStatement;
    }

    private function parseDeclareDirective($parentNode) {
        $declareDirective = new DeclareDirective();
        $declareDirective->parent = $parentNode;
        $declareDirective->name = $this->eat1(TokenKind::Name);
        $declareDirective->equals = $this->eat1(TokenKind::EqualsToken);
        $declareDirective->literal =
            $this->eat(
                TokenKind::FloatingLiteralToken,
                TokenKind::IntegerLiteralToken,
                TokenKind::DecimalLiteralToken,
                TokenKind::OctalLiteralToken,
                TokenKind::HexadecimalLiteralToken,
                TokenKind::BinaryLiteralToken,
                TokenKind::InvalidOctalLiteralToken,
                TokenKind::InvalidHexadecimalLiteral,
                TokenKind::InvalidBinaryLiteral,
                TokenKind::StringLiteralToken
            ); // TODO simplify

        return $declareDirective;
    }

    private function parseSimpleVariable($parentNode) {
        return ($this->parseSimpleVariableFn())($parentNode);
    }

    private function parseSimpleVariableFn() {
        return function ($parentNode) {
            $token = $this->getCurrentToken();
            $variable = new Variable();
            $variable->parent = $parentNode;

            if ($token->kind === TokenKind::DollarToken) {
                $variable->dollar = $this->eat1(TokenKind::DollarToken);
                $token = $this->getCurrentToken();

                switch ($token->kind) {
                    case TokenKind::OpenBraceToken:
                        $variable->name = $this->parseBracedExpression($variable);
                        break;
                    case TokenKind::VariableName:
                    case TokenKind::StringVarname:
                    case TokenKind::DollarToken:
                        $variable->name = $this->parseSimpleVariable($variable);
                        break;
                    default:
                        $variable->name = new MissingToken(TokenKind::VariableName, $token->fullStart);
                        break;
                }
            } elseif ($token->kind === TokenKind::VariableName || $token->kind === TokenKind::StringVarname) {
                // TODO consider splitting into dollar and name.
                // StringVarname is the variable name without $, used in a template string e.g. `"${foo}"`
                $variable->name = $this->eat(TokenKind::VariableName, TokenKind::StringVarname);
            } else {
                $variable->name = new MissingToken(TokenKind::VariableName, $token->fullStart);
            }

            return $variable;
        };
    }

    private function parseYieldExpression($parentNode) {
        $yieldExpression = new YieldExpression();
        $yieldExpression->parent = $parentNode;
        $yieldExpression->yieldOrYieldFromKeyword = $this->eat(
            TokenKind::YieldFromKeyword,
            TokenKind::YieldKeyword
            );
        if ($yieldExpression->yieldOrYieldFromKeyword->kind === TokenKind::YieldFromKeyword) {
            // Don't use parseArrayElement. E.g. `yield from &$varName` or `yield from $key => $varName` are both syntax errors
            $arrayElement = new ArrayElement();
            $arrayElement->parent = $yieldExpression;
            $arrayElement->elementValue = $this->parseExpression($arrayElement);
            $yieldExpression->arrayElement = $arrayElement;
        } else {
            // This is always an ArrayElement for backwards compatibilitiy.
            // TODO: Can this be changed to a non-ArrayElement in a future release?
            if ($this->isExpressionStart($this->getCurrentToken())) {
                // Both `yield expr;` and `yield;` are possible.
                $yieldExpression->arrayElement = $this->parseArrayElement($yieldExpression);
            } else {
                $yieldExpression->arrayElement = null;
            }
        }

        return $yieldExpression;
    }

    private function parseScriptInclusionExpression($parentNode) {
        $scriptInclusionExpression = new ScriptInclusionExpression();
        $scriptInclusionExpression->parent = $parentNode;
        $scriptInclusionExpression->requireOrIncludeKeyword =
            $this->eat(
                TokenKind::RequireKeyword, TokenKind::RequireOnceKeyword,
                TokenKind::IncludeKeyword, TokenKind::IncludeOnceKeyword
                );
        $scriptInclusionExpression->expression = $this->parseExpression($scriptInclusionExpression);
        return $scriptInclusionExpression;
    }

    private function parseEchoExpression($parentNode) {
        $echoExpression = new EchoExpression();
        $echoExpression->parent = $parentNode;
        $echoExpression->echoKeyword = $this->eat1(TokenKind::EchoKeyword);
        $echoExpression->expressions =
            $this->parseExpressionList($echoExpression);

        return $echoExpression;
    }

    private function parseListIntrinsicExpression($parentNode) {
        $listExpression = new ListIntrinsicExpression();
        $listExpression->parent = $parentNode;
        $listExpression->listKeyword = $this->eat1(TokenKind::ListKeyword);
        $listExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
        // TODO - parse loosely as ArrayElementList, and validate parse tree later
        $listExpression->listElements =
            $this->parseArrayElementList($listExpression, DelimitedList\ListExpressionList::class);
        $listExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);

        return $listExpression;
    }

    private function isArrayElementStart($token) {
        return ($this->isArrayElementStartFn())($token);
    }

    private function isArrayElementStartFn() {
        return function ($token) {
            return $token->kind === TokenKind::AmpersandToken || $this->isExpressionStart($token);
        };
    }

    private function parseArrayElement($parentNode) {
        return ($this->parseArrayElementFn())($parentNode);
    }

    private function parseArrayElementFn() {
        return function ($parentNode) {
            $arrayElement = new ArrayElement();
            $arrayElement->parent = $parentNode;

            if ($this->checkToken(TokenKind::AmpersandToken)) {
                $arrayElement->byRef = $this->eat1(TokenKind::AmpersandToken);
                $arrayElement->elementValue = $this->parseExpression($arrayElement);
            } else {
                $expression = $this->parseExpression($arrayElement);
                if ($this->checkToken(TokenKind::DoubleArrowToken)) {
                    $arrayElement->elementKey = $expression;
                    $arrayElement->arrowToken = $this->eat1(TokenKind::DoubleArrowToken);
                    $arrayElement->byRef = $this->eatOptional1(TokenKind::AmpersandToken); // TODO not okay for list expressions
                    $arrayElement->elementValue = $this->parseExpression($arrayElement);
                } else {
                    $arrayElement->elementValue = $expression;
                }
            }

            return $arrayElement;
        };
    }

    private function parseExpressionList($parentExpression) {
        return $this->parseDelimitedList(
            DelimitedList\ExpressionList::class,
            TokenKind::CommaToken,
            $this->isExpressionStartFn(),
            $this->parseExpressionFn(),
            $parentExpression
        );
    }

    private function parseUnsetIntrinsicExpression($parentNode) {
        $unsetExpression = new UnsetIntrinsicExpression();
        $unsetExpression->parent = $parentNode;

        $unsetExpression->unsetKeyword = $this->eat1(TokenKind::UnsetKeyword);
        $unsetExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
        $unsetExpression->expressions = $this->parseExpressionList($unsetExpression);
        $unsetExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);

        return $unsetExpression;
    }

    private function parseArrayCreationExpression($parentNode) {
        $arrayExpression = new ArrayCreationExpression();
        $arrayExpression->parent = $parentNode;

        $arrayExpression->arrayKeyword = $this->eatOptional1(TokenKind::ArrayKeyword);

        $arrayExpression->openParenOrBracket = $arrayExpression->arrayKeyword !== null
            ? $this->eat1(TokenKind::OpenParenToken)
            : $this->eat1(TokenKind::OpenBracketToken);

        $arrayExpression->arrayElements = $this->parseArrayElementList($arrayExpression, DelimitedList\ArrayElementList::class);

        $arrayExpression->closeParenOrBracket = $arrayExpression->arrayKeyword !== null
            ? $this->eat1(TokenKind::CloseParenToken)
            : $this->eat1(TokenKind::CloseBracketToken);

        return $arrayExpression;
    }

    private function parseArrayElementList($listExpression, $className) {
        return $this->parseDelimitedList(
            $className,
            TokenKind::CommaToken,
            $this->isArrayElementStartFn(),
            $this->parseArrayElementFn(),
            $listExpression,
            true
        );
    }

    private function parseEmptyIntrinsicExpression($parentNode) {
        $emptyExpression = new EmptyIntrinsicExpression();
        $emptyExpression->parent = $parentNode;

        $emptyExpression->emptyKeyword = $this->eat1(TokenKind::EmptyKeyword);
        $emptyExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
        $emptyExpression->expression = $this->parseExpression($emptyExpression);
        $emptyExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);

        return $emptyExpression;
    }

    private function parseEvalIntrinsicExpression($parentNode) {
        $evalExpression = new EvalIntrinsicExpression();
        $evalExpression->parent = $parentNode;

        $evalExpression->evalKeyword = $this->eat1(TokenKind::EvalKeyword);
        $evalExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
        $evalExpression->expression = $this->parseExpression($evalExpression);
        $evalExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);

        return $evalExpression;
    }

    private function parseParenthesizedExpression($parentNode) {
        $parenthesizedExpression = new ParenthesizedExpression();
        $parenthesizedExpression->parent = $parentNode;

        $parenthesizedExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
        $parenthesizedExpression->expression = $this->parseExpression($parenthesizedExpression);
        $parenthesizedExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);

        return $parenthesizedExpression;
    }

    private function parseExitIntrinsicExpression($parentNode) {
        $exitExpression = new ExitIntrinsicExpression();
        $exitExpression->parent = $parentNode;

        $exitExpression->exitOrDieKeyword = $this->eat(TokenKind::ExitKeyword, TokenKind::DieKeyword);
        $exitExpression->openParen = $this->eatOptional1(TokenKind::OpenParenToken);
        if ($exitExpression->openParen !== null) {
            if ($this->isExpressionStart($this->getCurrentToken())) {
                $exitExpression->expression = $this->parseExpression($exitExpression);
            }
            $exitExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);
        }

        return $exitExpression;
    }

    private function parsePrintIntrinsicExpression($parentNode) {
        $printExpression = new PrintIntrinsicExpression();
        $printExpression->parent = $parentNode;

        $printExpression->printKeyword = $this->eat1(TokenKind::PrintKeyword);
        $printExpression->expression = $this->parseExpression($printExpression);

        return $printExpression;
    }

    private function parseIssetIntrinsicExpression($parentNode) {
        $issetExpression = new IssetIntrinsicExpression();
        $issetExpression->parent = $parentNode;

        $issetExpression->issetKeyword = $this->eat1(TokenKind::IsSetKeyword);
        $issetExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
        $issetExpression->expressions = $this->parseExpressionList($issetExpression);
        $issetExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);

        return $issetExpression;
    }

    private function parseUnaryOpExpression($parentNode) {
        $unaryOpExpression = new UnaryOpExpression();
        $unaryOpExpression->parent = $parentNode;
        $unaryOpExpression->operator =
            $this->eat(TokenKind::PlusToken, TokenKind::MinusToken, TokenKind::ExclamationToken, TokenKind::TildeToken);
        $unaryOpExpression->operand = $this->parseUnaryExpressionOrHigher($unaryOpExpression);

        return $unaryOpExpression;
    }

    private function parseErrorControlExpression($parentNode) {
        $errorControlExpression = new ErrorControlExpression();
        $errorControlExpression->parent = $parentNode;

        $errorControlExpression->operator = $this->eat1(TokenKind::AtSymbolToken);
        $errorControlExpression->operand = $this->parseUnaryExpressionOrHigher($errorControlExpression);

        return $errorControlExpression;
    }

    private function parsePrefixUpdateExpression($parentNode) {
        $prefixUpdateExpression = new PrefixUpdateExpression();
        $prefixUpdateExpression->parent = $parentNode;

        $prefixUpdateExpression->incrementOrDecrementOperator = $this->eat(TokenKind::PlusPlusToken, TokenKind::MinusMinusToken);

        $prefixUpdateExpression->operand = $this->parsePrimaryExpression($prefixUpdateExpression);

        if (!($prefixUpdateExpression->operand instanceof MissingToken)) {
            $prefixUpdateExpression->operand = $this->parsePostfixExpressionRest($prefixUpdateExpression->operand, false);
        }

        // TODO also check operand expression validity
        return $prefixUpdateExpression;
    }

    private function parsePostfixExpressionRest($expression, $allowUpdateExpression = true) {
        $tokenKind = $this->getCurrentToken()->kind;

        // `--$a++` is invalid
        if ($allowUpdateExpression &&
            ($tokenKind === TokenKind::PlusPlusToken ||
            $tokenKind === TokenKind::MinusMinusToken)) {
            return $this->parseParsePostfixUpdateExpression($expression);
        }

        // TODO write tons of tests
        if (!($expression instanceof Variable ||
            $expression instanceof ParenthesizedExpression ||
            $expression instanceof QualifiedName ||
            $expression instanceof CallExpression ||
            $expression instanceof MemberAccessExpression ||
            $expression instanceof SubscriptExpression ||
            $expression instanceof ScopedPropertyAccessExpression ||
            $expression instanceof StringLiteral ||
            $expression instanceof ArrayCreationExpression
        )) {
            return $expression;
        }
        if ($tokenKind === TokenKind::ColonColonToken) {
            $expression = $this->parseScopedPropertyAccessExpression($expression);
            return $this->parsePostfixExpressionRest($expression);
        }

        $tokenKind = $this->getCurrentToken()->kind;

        if ($tokenKind === TokenKind::OpenBraceToken ||
            $tokenKind === TokenKind::OpenBracketToken) {
            $expression = $this->parseSubscriptExpression($expression);
            return $this->parsePostfixExpressionRest($expression);
        }

        if ($expression instanceof ArrayCreationExpression) {
            // Remaining postfix expressions are invalid, so abort
            return $expression;
        }

        if ($tokenKind === TokenKind::ArrowToken) {
            $expression = $this->parseMemberAccessExpression($expression);
            return $this->parsePostfixExpressionRest($expression);
        }

        if ($tokenKind === TokenKind::OpenParenToken && !$this->isParsingObjectCreationExpression) {
            $expression = $this->parseCallExpressionRest($expression);

            if (!$this->checkToken(TokenKind::OpenParenToken)) {
                return $this->parsePostfixExpressionRest($expression);
            }
            if (
                $expression instanceof ParenthesizedExpression ||
                $expression instanceof CallExpression ||
                $expression instanceof SubscriptExpression) {
                // Continue parsing the remaining brackets for expressions
                // such as `(new Foo())()`, `foo()()`, `foo()['index']()`
                return $this->parsePostfixExpressionRest($expression);
            }
            return $expression;
        }

        // Reached the end of the postfix-expression, so return
        return $expression;
    }

    private function parseMemberName($parentNode) {
        $token = $this->getCurrentToken();
        switch ($token->kind) {
            case TokenKind::Name:
                $this->advanceToken(); // TODO all names should be Nodes
                return $token;
            case TokenKind::VariableName:
            case TokenKind::DollarToken:
                return $this->parseSimpleVariable($parentNode); // TODO should be simple-variable
            case TokenKind::OpenBraceToken:
                return $this->parseBracedExpression($parentNode);

            default:
                if (\in_array($token->kind, $this->nameOrKeywordOrReservedWordTokens)) {
                    $this->advanceToken();
                    $token->kind = TokenKind::Name;
                    return $token;
                }
        }
        return new MissingToken(TokenKind::MemberName, $token->fullStart);
    }

    private function isArgumentExpressionStartFn() {
        return function ($token) {
            return
                $token->kind === TokenKind::DotDotDotToken ? true : $this->isExpressionStart($token);
        };
    }

    private function parseArgumentExpressionFn() {
        return function ($parentNode) {
            $argumentExpression = new ArgumentExpression();
            $argumentExpression->parent = $parentNode;
            $argumentExpression->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
            $argumentExpression->dotDotDotToken = $this->eatOptional1(TokenKind::DotDotDotToken);
            $argumentExpression->expression = $this->parseExpression($argumentExpression);
            return $argumentExpression;
        };
    }

    private function parseCallExpressionRest($expression) {
        $callExpression = new CallExpression();
        $callExpression->parent = $expression->parent;
        $expression->parent = $callExpression;
        $callExpression->callableExpression = $expression;
        $callExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
        $callExpression->argumentExpressionList =
            $this->parseArgumentExpressionList($callExpression);
        $callExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);
        return $callExpression;
    }

    private function parseParsePostfixUpdateExpression($prefixExpression) {
        $postfixUpdateExpression = new PostfixUpdateExpression();
        $postfixUpdateExpression->operand = $prefixExpression;
        $postfixUpdateExpression->parent = $prefixExpression->parent;
        $prefixExpression->parent = $postfixUpdateExpression;
        $postfixUpdateExpression->incrementOrDecrementOperator =
            $this->eat(TokenKind::PlusPlusToken, TokenKind::MinusMinusToken);
        return $postfixUpdateExpression;
    }

    private function parseBracedExpression($parentNode) {
        $bracedExpression = new BracedExpression();
        $bracedExpression->parent = $parentNode;

        $bracedExpression->openBrace = $this->eat1(TokenKind::OpenBraceToken);
        $bracedExpression->expression = $this->parseExpression($bracedExpression);
        $bracedExpression->closeBrace = $this->eat1(TokenKind::CloseBraceToken);

        return $bracedExpression;
    }

    private function parseSubscriptExpression($expression) : SubscriptExpression {
        $subscriptExpression = new SubscriptExpression();
        $subscriptExpression->parent = $expression->parent;
        $expression->parent = $subscriptExpression;

        $subscriptExpression->postfixExpression = $expression;
        $subscriptExpression->openBracketOrBrace = $this->eat(TokenKind::OpenBracketToken, TokenKind::OpenBraceToken);
        $subscriptExpression->accessExpression = $this->isExpressionStart($this->getCurrentToken())
            ? $this->parseExpression($subscriptExpression)
            : null; // TODO error if used in a getter

        if ($subscriptExpression->openBracketOrBrace->kind === TokenKind::OpenBraceToken) {
            $subscriptExpression->closeBracketOrBrace = $this->eat1(TokenKind::CloseBraceToken);
        } else {
            $subscriptExpression->closeBracketOrBrace = $this->eat1(TokenKind::CloseBracketToken);
        }

        return $subscriptExpression;
    }

    private function parseMemberAccessExpression($expression):MemberAccessExpression {
        $memberAccessExpression = new MemberAccessExpression();
        $memberAccessExpression->parent = $expression->parent;
        $expression->parent = $memberAccessExpression;

        $memberAccessExpression->dereferencableExpression = $expression;
        $memberAccessExpression->arrowToken = $this->eat1(TokenKind::ArrowToken);
        $memberAccessExpression->memberName = $this->parseMemberName($memberAccessExpression);

        return $memberAccessExpression;
    }

    private function parseScopedPropertyAccessExpression($expression):ScopedPropertyAccessExpression {
        $scopedPropertyAccessExpression = new ScopedPropertyAccessExpression();
        $scopedPropertyAccessExpression->parent = $expression->parent;
        $expression->parent = $scopedPropertyAccessExpression;

        $scopedPropertyAccessExpression->scopeResolutionQualifier = $expression; // TODO ensure always a Node
        $scopedPropertyAccessExpression->doubleColon = $this->eat1(TokenKind::ColonColonToken);
        $scopedPropertyAccessExpression->memberName = $this->parseMemberName($scopedPropertyAccessExpression);

        return $scopedPropertyAccessExpression;
    }

    private $isParsingObjectCreationExpression = false;

    private function parseObjectCreationExpression($parentNode) {
        $objectCreationExpression = new ObjectCreationExpression();
        $objectCreationExpression->parent = $parentNode;
        $objectCreationExpression->newKeword = $this->eat1(TokenKind::NewKeyword);
        // TODO - add tests for this scenario
        $this->isParsingObjectCreationExpression = true;
        $objectCreationExpression->classTypeDesignator =
            $this->eatOptional1(TokenKind::ClassKeyword) ??
            $this->parseExpression($objectCreationExpression);

        $this->isParsingObjectCreationExpression = false;

        $objectCreationExpression->openParen = $this->eatOptional1(TokenKind::OpenParenToken);
        if ($objectCreationExpression->openParen !== null) {
            $objectCreationExpression->argumentExpressionList = $this->parseArgumentExpressionList($objectCreationExpression);
            $objectCreationExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);
        }

        $objectCreationExpression->classBaseClause = $this->parseClassBaseClause($objectCreationExpression);
        $objectCreationExpression->classInterfaceClause = $this->parseClassInterfaceClause($objectCreationExpression);

        if ($this->getCurrentToken()->kind === TokenKind::OpenBraceToken) {
            $objectCreationExpression->classMembers = $this->parseClassMembers($objectCreationExpression);
        }

        return $objectCreationExpression;
    }

    private function parseArgumentExpressionList($parentNode) {
        return $this->parseDelimitedList(
            DelimitedList\ArgumentExpressionList::class,
            TokenKind::CommaToken,
            $this->isArgumentExpressionStartFn(),
            $this->parseArgumentExpressionFn(),
            $parentNode
        );
    }

    private function parseTernaryExpression($leftOperand, $questionToken):TernaryExpression {
        $ternaryExpression = new TernaryExpression();
        $ternaryExpression->parent = $leftOperand->parent;
        $leftOperand->parent = $ternaryExpression;
        $ternaryExpression->condition = $leftOperand;
        $ternaryExpression->questionToken = $questionToken;
        $ternaryExpression->ifExpression = $this->isExpressionStart($this->getCurrentToken()) ? $this->parseExpression($ternaryExpression) : null;
        $ternaryExpression->colonToken = $this->eat1(TokenKind::ColonToken);
        $ternaryExpression->elseExpression = $this->parseBinaryExpressionOrHigher(9, $ternaryExpression);
        $leftOperand = $ternaryExpression;
        return $leftOperand;
    }

    private function parseClassInterfaceClause($parentNode) {
        $classInterfaceClause = new ClassInterfaceClause();
        $classInterfaceClause->parent = $parentNode;
        $classInterfaceClause->implementsKeyword = $this->eatOptional1(TokenKind::ImplementsKeyword);

        if ($classInterfaceClause->implementsKeyword === null) {
            return null;
        }

        $classInterfaceClause->interfaceNameList =
            $this->parseQualifiedNameList($classInterfaceClause);
        return $classInterfaceClause;
    }

    private function parseClassBaseClause($parentNode) {
        $classBaseClause = new ClassBaseClause();
        $classBaseClause->parent = $parentNode;

        $classBaseClause->extendsKeyword = $this->eatOptional1(TokenKind::ExtendsKeyword);
        if ($classBaseClause->extendsKeyword === null) {
            return null;
        }
        $classBaseClause->baseClass = $this->parseQualifiedName($classBaseClause);

        return $classBaseClause;
    }

    private function parseClassConstDeclaration($parentNode, $modifiers) {
        $classConstDeclaration = new ClassConstDeclaration();
        $classConstDeclaration->parent = $parentNode;

        $classConstDeclaration->modifiers = $modifiers;
        $classConstDeclaration->constKeyword = $this->eat1(TokenKind::ConstKeyword);
        $classConstDeclaration->constElements = $this->parseConstElements($classConstDeclaration);
        $classConstDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken);

        return $classConstDeclaration;
    }

    private function parsePropertyDeclaration($parentNode, $modifiers) {
        $propertyDeclaration = new PropertyDeclaration();
        $propertyDeclaration->parent = $parentNode;

        $propertyDeclaration->modifiers = $modifiers;
        $propertyDeclaration->propertyElements = $this->parseExpressionList($propertyDeclaration);
        $propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken);

        return $propertyDeclaration;
    }

    private function parseQualifiedNameList($parentNode) {
        return $this->parseDelimitedList(
            DelimitedList\QualifiedNameList::class,
            TokenKind::CommaToken,
            $this->isQualifiedNameStartFn(),
            $this->parseQualifiedNameFn(),
            $parentNode);
    }

    private function parseInterfaceDeclaration($parentNode) {
        $interfaceDeclaration = new InterfaceDeclaration(); // TODO verify not nested
        $interfaceDeclaration->parent = $parentNode;
        $interfaceDeclaration->interfaceKeyword = $this->eat1(TokenKind::InterfaceKeyword);
        $interfaceDeclaration->name = $this->eat1(TokenKind::Name);
        $interfaceDeclaration->interfaceBaseClause = $this->parseInterfaceBaseClause($interfaceDeclaration);
        $interfaceDeclaration->interfaceMembers = $this->parseInterfaceMembers($interfaceDeclaration);
        return $interfaceDeclaration;
    }

    private function parseInterfaceMembers($parentNode) : Node {
        $interfaceMembers = new InterfaceMembers();
        $interfaceMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);
        $interfaceMembers->interfaceMemberDeclarations = $this->parseList($interfaceMembers, ParseContext::InterfaceMembers);
        $interfaceMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
        $interfaceMembers->parent = $parentNode;
        return $interfaceMembers;
    }

    private function isInterfaceMemberDeclarationStart(Token $token) {
        switch ($token->kind) {
            // visibility-modifier
            case TokenKind::PublicKeyword:
            case TokenKind::ProtectedKeyword:
            case TokenKind::PrivateKeyword:

            // static-modifier
            case TokenKind::StaticKeyword:

            // class-modifier
            case TokenKind::AbstractKeyword:
            case TokenKind::FinalKeyword:

            case TokenKind::ConstKeyword:

            case TokenKind::FunctionKeyword:
                return true;
        }
        return false;
    }

    private function parseInterfaceElementFn() {
        return function ($parentNode) {
            $modifiers = $this->parseModifiers();

            $token = $this->getCurrentToken();
            switch ($token->kind) {
                case TokenKind::ConstKeyword:
                    return $this->parseClassConstDeclaration($parentNode, $modifiers);

                case TokenKind::FunctionKeyword:
                    return $this->parseMethodDeclaration($parentNode, $modifiers);

                default:
                    $missingInterfaceMemberDeclaration = new MissingMemberDeclaration();
                    $missingInterfaceMemberDeclaration->parent = $parentNode;
                    $missingInterfaceMemberDeclaration->modifiers = $modifiers;
                    return $missingInterfaceMemberDeclaration;
            }
        };
    }

    private function parseInterfaceBaseClause($parentNode) {
        $interfaceBaseClause = new InterfaceBaseClause();
        $interfaceBaseClause->parent = $parentNode;

        $interfaceBaseClause->extendsKeyword = $this->eatOptional1(TokenKind::ExtendsKeyword);
        if (isset($interfaceBaseClause->extendsKeyword)) {
            $interfaceBaseClause->interfaceNameList = $this->parseQualifiedNameList($interfaceBaseClause);
        } else {
            return null;
        }

        return $interfaceBaseClause;
    }

    private function parseNamespaceDefinition($parentNode) {
        $namespaceDefinition = new NamespaceDefinition();
        $namespaceDefinition->parent = $parentNode;

        $namespaceDefinition->namespaceKeyword = $this->eat1(TokenKind::NamespaceKeyword);

        if (!$this->checkToken(TokenKind::NamespaceKeyword)) {
            $namespaceDefinition->name = $this->parseQualifiedName($namespaceDefinition); // TODO only optional with compound statement block
        }

        $namespaceDefinition->compoundStatementOrSemicolon =
            $this->checkToken(TokenKind::OpenBraceToken) ?
                $this->parseCompoundStatement($namespaceDefinition) : $this->eatSemicolonOrAbortStatement();

        return $namespaceDefinition;
    }

    private function parseNamespaceUseDeclaration($parentNode) {
        $namespaceUseDeclaration = new NamespaceUseDeclaration();
        $namespaceUseDeclaration->parent = $parentNode;
        $namespaceUseDeclaration->useKeyword = $this->eat1(TokenKind::UseKeyword);
        $namespaceUseDeclaration->functionOrConst = $this->eatOptional(TokenKind::FunctionKeyword, TokenKind::ConstKeyword);
        $namespaceUseDeclaration->useClauses = $this->parseNamespaceUseClauseList($namespaceUseDeclaration);
        $namespaceUseDeclaration->semicolon = $this->eatSemicolonOrAbortStatement();
        return $namespaceUseDeclaration;
    }

    private function parseNamespaceUseClauseList($parentNode) {
        return $this->parseDelimitedList(
            DelimitedList\NamespaceUseClauseList::class,
            TokenKind::CommaToken,
            function ($token) {
                return $this->isQualifiedNameStart($token) || $token->kind === TokenKind::FunctionKeyword || $token->kind === TokenKind::ConstKeyword;
            },
            function ($parentNode) {
                $namespaceUseClause = new NamespaceUseClause();
                $namespaceUseClause->parent = $parentNode;
                $namespaceUseClause->namespaceName = $this->parseQualifiedName($namespaceUseClause);
                if ($this->checkToken(TokenKind::AsKeyword)) {
                    $namespaceUseClause->namespaceAliasingClause = $this->parseNamespaceAliasingClause($namespaceUseClause);
                }
                elseif ($this->checkToken(TokenKind::OpenBraceToken)) {
                    $namespaceUseClause->openBrace = $this->eat1(TokenKind::OpenBraceToken);
                    $namespaceUseClause->groupClauses = $this->parseNamespaceUseGroupClauseList($namespaceUseClause);
                    $namespaceUseClause->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
                }

                return $namespaceUseClause;
            },
            $parentNode
        );
    }

    private function parseNamespaceUseGroupClauseList($parentNode) {
        return $this->parseDelimitedList(
            DelimitedList\NamespaceUseGroupClauseList::class,
            TokenKind::CommaToken,
            function ($token) {
                return $this->isQualifiedNameStart($token) || $token->kind === TokenKind::FunctionKeyword || $token->kind === TokenKind::ConstKeyword;
            },
            function ($parentNode) {
                $namespaceUseGroupClause = new NamespaceUseGroupClause();
                $namespaceUseGroupClause->parent = $parentNode;

                $namespaceUseGroupClause->functionOrConst = $this->eatOptional(TokenKind::FunctionKeyword, TokenKind::ConstKeyword);
                $namespaceUseGroupClause->namespaceName = $this->parseQualifiedName($namespaceUseGroupClause);
                if ($this->checkToken(TokenKind::AsKeyword)) {
                    $namespaceUseGroupClause->namespaceAliasingClause = $this->parseNamespaceAliasingClause($namespaceUseGroupClause);
                }

                return $namespaceUseGroupClause;
            },
            $parentNode
        );
    }

    private function parseNamespaceAliasingClause($parentNode) {
        $namespaceAliasingClause = new NamespaceAliasingClause();
        $namespaceAliasingClause->parent = $parentNode;
        $namespaceAliasingClause->asKeyword = $this->eat1(TokenKind::AsKeyword);
        $namespaceAliasingClause->name = $this->eat1(TokenKind::Name);
        return $namespaceAliasingClause;
    }

    private function parseTraitDeclaration($parentNode) {
        $traitDeclaration = new TraitDeclaration();
        $traitDeclaration->parent = $parentNode;

        $traitDeclaration->traitKeyword = $this->eat1(TokenKind::TraitKeyword);
        $traitDeclaration->name = $this->eat1(TokenKind::Name);

        $traitDeclaration->traitMembers = $this->parseTraitMembers($traitDeclaration);

        return $traitDeclaration;
    }

    private function parseTraitMembers($parentNode) {
        $traitMembers = new TraitMembers();
        $traitMembers->parent = $parentNode;

        $traitMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken);

        $traitMembers->traitMemberDeclarations = $this->parseList($traitMembers, ParseContext::TraitMembers);

        $traitMembers->closeBrace = $this->eat1(TokenKind::CloseBraceToken);

        return $traitMembers;
    }

    private function isTraitMemberDeclarationStart($token) {
        switch ($token->kind) {
            // property-declaration
            case TokenKind::VariableName:

            // modifiers
            case TokenKind::PublicKeyword:
            case TokenKind::ProtectedKeyword:
            case TokenKind::PrivateKeyword:
            case TokenKind::VarKeyword:
            case TokenKind::StaticKeyword:
            case TokenKind::AbstractKeyword:
            case TokenKind::FinalKeyword:

            // method-declaration
            case TokenKind::FunctionKeyword:

            // trait-use-clauses
            case TokenKind::UseKeyword:
                return true;
        }
        return false;
    }

    private function parseTraitElementFn() {
        return function ($parentNode) {
            $modifiers = $this->parseModifiers();

            $token = $this->getCurrentToken();
            switch ($token->kind) {
                case TokenKind::FunctionKeyword:
                    return $this->parseMethodDeclaration($parentNode, $modifiers);

                case TokenKind::VariableName:
                    return $this->parsePropertyDeclaration($parentNode, $modifiers);

                case TokenKind::UseKeyword:
                    return $this->parseTraitUseClause($parentNode);

                default:
                    $missingTraitMemberDeclaration = new MissingMemberDeclaration();
                    $missingTraitMemberDeclaration->parent = $parentNode;
                    $missingTraitMemberDeclaration->modifiers = $modifiers;
                    return $missingTraitMemberDeclaration;
            }
        };
    }

    private function parseTraitUseClause($parentNode) {
        $traitUseClause = new TraitUseClause();
        $traitUseClause->parent = $parentNode;

        $traitUseClause->useKeyword = $this->eat1(TokenKind::UseKeyword);
        $traitUseClause->traitNameList = $this->parseQualifiedNameList($traitUseClause);

        $traitUseClause->semicolonOrOpenBrace = $this->eat(TokenKind::OpenBraceToken, TokenKind::SemicolonToken);
        if ($traitUseClause->semicolonOrOpenBrace->kind === TokenKind::OpenBraceToken) {
            $traitUseClause->traitSelectAndAliasClauses = $this->parseTraitSelectAndAliasClauseList($traitUseClause);
            $traitUseClause->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
        }

        return $traitUseClause;
    }

    private function parseTraitSelectAndAliasClauseList($parentNode) {
        return $this->parseDelimitedList(
            DelimitedList\TraitSelectOrAliasClauseList::class,
            TokenKind::SemicolonToken,
            $this->isQualifiedNameStartFn(),
            $this->parseTraitSelectOrAliasClauseFn(),
            $parentNode
        );
    }

    private function parseTraitSelectOrAliasClauseFn() {
        return function ($parentNode) {
            $traitSelectAndAliasClause = new TraitSelectOrAliasClause();
            $traitSelectAndAliasClause->parent = $parentNode;
            $traitSelectAndAliasClause->name = // TODO update spec
                $this->parseQualifiedNameOrScopedPropertyAccessExpression($traitSelectAndAliasClause);

            $traitSelectAndAliasClause->asOrInsteadOfKeyword = $this->eat(TokenKind::AsKeyword, TokenKind::InsteadOfKeyword);
            $traitSelectAndAliasClause->modifiers = $this->parseModifiers(); // TODO accept all modifiers, verify later

            $traitSelectAndAliasClause->targetName =
                $this->parseQualifiedNameOrScopedPropertyAccessExpression($traitSelectAndAliasClause);

            // TODO errors for insteadof/as
            return $traitSelectAndAliasClause;
        };
    }

    private function parseQualifiedNameOrScopedPropertyAccessExpression($parentNode) {
        $qualifiedNameOrScopedProperty = $this->parseQualifiedName($parentNode);
        if ($this->getCurrentToken()->kind === TokenKind::ColonColonToken) {
            $qualifiedNameOrScopedProperty = $this->parseScopedPropertyAccessExpression($qualifiedNameOrScopedProperty);
        }
        return $qualifiedNameOrScopedProperty;
    }

    private function parseGlobalDeclaration($parentNode) {
        $globalDeclaration = new GlobalDeclaration();
        $globalDeclaration->parent = $parentNode;

        $globalDeclaration->globalKeyword = $this->eat1(TokenKind::GlobalKeyword);
        $globalDeclaration->variableNameList = $this->parseDelimitedList(
            DelimitedList\VariableNameList::class,
            TokenKind::CommaToken,
            $this->isVariableNameStartFn(),
            $this->parseSimpleVariableFn(),
            $globalDeclaration
        );

        $globalDeclaration->semicolon = $this->eatSemicolonOrAbortStatement();

        return $globalDeclaration;
    }

    private function parseFunctionStaticDeclaration($parentNode) {
        $functionStaticDeclaration = new FunctionStaticDeclaration();
        $functionStaticDeclaration->parent = $parentNode;

        $functionStaticDeclaration->staticKeyword = $this->eat1(TokenKind::StaticKeyword);
        $functionStaticDeclaration->staticVariableNameList = $this->parseDelimitedList(
            DelimitedList\StaticVariableNameList::class,
            TokenKind::CommaToken,
            function ($token) {
                return $token->kind === TokenKind::VariableName;
            },
            $this->parseStaticVariableDeclarationFn(),
            $functionStaticDeclaration
        );
        $functionStaticDeclaration->semicolon = $this->eatSemicolonOrAbortStatement();

        return $functionStaticDeclaration;
    }

    private function isVariableNameStartFn() {
        return function ($token) {
            return $token->kind === TokenKind::VariableName || $token->kind === TokenKind::DollarToken;
        };
    }

    private function parseStaticVariableDeclarationFn() {
        return function ($parentNode) {
            $staticVariableDeclaration = new StaticVariableDeclaration();
            $staticVariableDeclaration->parent = $parentNode;
            $staticVariableDeclaration->variableName = $this->eat1(TokenKind::VariableName);
            $staticVariableDeclaration->equalsToken = $this->eatOptional1(TokenKind::EqualsToken);
            if ($staticVariableDeclaration->equalsToken !== null) {
                // TODO add post-parse rule that checks for invalid assignments
                $staticVariableDeclaration->assignment = $this->parseExpression($staticVariableDeclaration);
            }
            return $staticVariableDeclaration;
        };
    }

    private function parseConstDeclaration($parentNode) {
        $constDeclaration = new ConstDeclaration();
        $constDeclaration->parent = $parentNode;

        $constDeclaration->constKeyword = $this->eat1(TokenKind::ConstKeyword);
        $constDeclaration->constElements = $this->parseConstElements($constDeclaration);
        $constDeclaration->semicolon = $this->eatSemicolonOrAbortStatement();

        return $constDeclaration;
    }

    private function parseConstElements($parentNode) {
        return $this->parseDelimitedList(
            DelimitedList\ConstElementList::class,
            TokenKind::CommaToken,
            function ($token) {
                return \in_array($token->kind, $this->nameOrKeywordOrReservedWordTokens);
            },
            $this->parseConstElementFn(),
            $parentNode
        );
    }

    private function parseConstElementFn() {
        return function ($parentNode) {
            $constElement = new ConstElement();
            $constElement->parent = $parentNode;
            $constElement->name = $this->getCurrentToken();
            $this->advanceToken();
            $constElement->name->kind = TokenKind::Name; // to support keyword names
            $constElement->equalsToken = $this->eat1(TokenKind::EqualsToken);
            // TODO add post-parse rule that checks for invalid assignments
            $constElement->assignment = $this->parseExpression($constElement);
            return $constElement;
        };
    }

    private function parseCastExpression($parentNode) {
        $castExpression = new CastExpression();
        $castExpression->parent = $parentNode;
        $castExpression->castType = $this->eat(
            TokenKind::ArrayCastToken,
            TokenKind::BoolCastToken,
            TokenKind::DoubleCastToken,
            TokenKind::IntCastToken,
            TokenKind::ObjectCastToken,
            TokenKind::StringCastToken,
            TokenKind::UnsetCastToken
        );

        $castExpression->operand = $this->parseUnaryExpressionOrHigher($castExpression);

        return $castExpression;
    }

    private function parseCastExpressionGranular($parentNode) {
        $castExpression = new CastExpression();
        $castExpression->parent = $parentNode;

        $castExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
        $castExpression->castType = $this->eat(
            TokenKind::ArrayKeyword,
            TokenKind::BinaryReservedWord,
            TokenKind::BoolReservedWord,
            TokenKind::BooleanReservedWord,
            TokenKind::DoubleReservedWord,
            TokenKind::IntReservedWord,
            TokenKind::IntegerReservedWord,
            TokenKind::FloatReservedWord,
            TokenKind::ObjectReservedWord,
            TokenKind::RealReservedWord,
            TokenKind::StringReservedWord,
            TokenKind::UnsetKeyword
        );
        $castExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);
        $castExpression->operand = $this->parseUnaryExpressionOrHigher($castExpression);

        return $castExpression;
    }

    private function parseAnonymousFunctionCreationExpression($parentNode) {
        $anonymousFunctionCreationExpression = new AnonymousFunctionCreationExpression();
        $anonymousFunctionCreationExpression->parent = $parentNode;

        $anonymousFunctionCreationExpression->staticModifier = $this->eatOptional1(TokenKind::StaticKeyword);
        $this->parseFunctionType($anonymousFunctionCreationExpression, false, true);

        return $anonymousFunctionCreationExpression;
    }

    private function parseAnonymousFunctionUseClause($parentNode) {
        $anonymousFunctionUseClause = new AnonymousFunctionUseClause();
        $anonymousFunctionUseClause->parent = $parentNode;

        $anonymousFunctionUseClause->useKeyword = $this->eatOptional1(TokenKind::UseKeyword);
        if ($anonymousFunctionUseClause->useKeyword === null) {
            return null;
        }
        $anonymousFunctionUseClause->openParen = $this->eat1(TokenKind::OpenParenToken);
        $anonymousFunctionUseClause->useVariableNameList = $this->parseDelimitedList(
            DelimitedList\UseVariableNameList::class,
            TokenKind::CommaToken,
            function ($token) {
                return $token->kind === TokenKind::AmpersandToken || $token->kind === TokenKind::VariableName;
            },
            function ($parentNode) {
                $useVariableName = new UseVariableName();
                $useVariableName->parent = $parentNode;
                $useVariableName->byRef = $this->eatOptional1(TokenKind::AmpersandToken);
                $useVariableName->variableName = $this->eat1(TokenKind::VariableName);
                return $useVariableName;
            },
            $anonymousFunctionUseClause
        );
        $anonymousFunctionUseClause->closeParen = $this->eat1(TokenKind::CloseParenToken);

        return $anonymousFunctionUseClause;
    }

    private function parseCloneExpression($parentNode) {
        $cloneExpression = new CloneExpression();
        $cloneExpression->parent = $parentNode;

        $cloneExpression->cloneKeyword = $this->eat1(TokenKind::CloneKeyword);
        $cloneExpression->expression = $this->parseUnaryExpressionOrHigher($cloneExpression);

        return $cloneExpression;
    }

    private function eatSemicolonOrAbortStatement() {
        if ($this->getCurrentToken()->kind !== TokenKind::ScriptSectionEndTag) {
            return $this->eat1(TokenKind::SemicolonToken);
        }
        return null;
    }

    private function parseInlineHtml($parentNode) {
        $inlineHtml = new InlineHtml();
        $inlineHtml->parent = $parentNode;
        $inlineHtml->scriptSectionEndTag = $this->eatOptional1(TokenKind::ScriptSectionEndTag);
        $inlineHtml->text = $this->eatOptional1(TokenKind::InlineHtml);
        $inlineHtml->scriptSectionStartTag = $this->eatOptional1(TokenKind::ScriptSectionStartTag);

        return $inlineHtml;
    }
}

class Associativity {
    const None = 0;
    const Left = 1;
    const Right = 2;
}<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class TextEdit {
    /** @var int */
    public $start;

    /** @var int */
    public $length;

    /** @var string */
    public $content;

    public function __construct(int $start, int $length, string $content) {
        $this->start = $start;
        $this->length = $length;
        $this->content = $content;
    }

    /**
     * Applies array of edits to the document, and returns the resulting text.
     * Supplied $edits must not overlap, and be ordered by increasing start position.
     *
     * Note that after applying edits, the original AST should be invalidated.
     *
     * @param TextEdit[] $edits
     * @param string $text
     * @return string
     */
    public static function applyEdits(array $edits, string $text) : string {
        $prevEditStart = PHP_INT_MAX;
        for ($i = \count($edits) - 1; $i >= 0; $i--) {
            $edit = $edits[$i];
            \assert(
                $prevEditStart > $edit->start && $prevEditStart > $edit->start + $edit->length,
                "Supplied TextEdit[] must not overlap, and be in increasing start position order."
            );
            if ($edit->start < 0 || $edit->length < 0 || $edit->start + $edit->length > \strlen($text)) {
                throw new \OutOfBoundsException("Applied TextEdit range out of bounds.");
            }
            $prevEditStart = $edit->start;
            $head = \substr($text, 0, $edit->start);
            $tail = \substr($text, $edit->start + $edit->length);
            $text = $head . $edit->content . $tail;
        }
        return $text;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

/**
 * Represents Classes, Interfaces and Traits.
 */
interface ClassLike {}
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class PositionUtilities {
    /**
     * Gets a Range from 0-indexed position into $text.

     * Out of bounds positions are handled gracefully. Positions greater than the length of text length
     * are resolved to the end of the text, and negative positions are resolved to the beginning.
     *
     * @param $pos
     * @param $length
     * @param $text
     * @return Range
     */
    public static function getRangeFromPosition($pos, $length, $text): Range {
        $start = self::getLineCharacterPositionFromPosition($pos, $text);
        $end = self::getLineCharacterPositionFromPosition($pos + $length, $text);

        return new Range($start, $end);
    }

    /**
     * Gets 0-indexed LineCharacterPosition from 0-indexed position into $text.
     *
     * Out of bounds positions are handled gracefully. Positions greater than the length of text length
     * are resolved to text length, and negative positions are resolved to 0.
     * TODO consider throwing exception instead.
     *
     * @param $pos
     * @param $text
     * @return LineCharacterPosition
     */
    public static function getLineCharacterPositionFromPosition($pos, $text) : LineCharacterPosition {
        $textLength = \strlen($text);
        if ($pos >= $textLength) {
            $pos = $textLength;
        } elseif ($pos < 0) {
            $pos = 0;
        }

        // Start strrpos check from the character before the current character,
        // in case the current character is a newline
        $startAt = max(-($textLength - $pos) - 1, -$textLength);
        $lastNewlinePos = \strrpos($text, "\n", $startAt);
        $char = $pos - ($lastNewlinePos === false ? 0 : $lastNewlinePos + 1);
        $line = $pos > 0 ? \substr_count($text, "\n", 0, $pos) : 0;
        return new LineCharacterPosition($line, $char);
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class Token implements \JsonSerializable {
    // TODO optimize memory - ideally this would be a struct of 4 ints
    public $kind;
    public $fullStart;
    public $start;
    public $length;

    public function __construct($kind, $fullStart, $start, $length) {
        $this->kind = $kind;
        $this->fullStart = $fullStart;
        $this->start = $start;
        $this->length = $length;
    }

    public function getLeadingCommentsAndWhitespaceText(string $document) : string {
        return substr($document, $this->fullStart, $this->start - $this->fullStart);
    }

    /**
     * @param string|null $document
     * @return bool|null|string
     */
    public function getText(string $document = null) {
        if ($document === null) {
            return null;
        }
        return substr($document, $this->start, $this->length - ($this->start - $this->fullStart));
    }

    public function getFullText(string $document) : string {
        return substr($document, $this->fullStart, $this->length);
    }

    public function getStartPosition() {
        return $this->start;
    }

    public function getFullStart() {
        return $this->fullStart;
    }

    public function getWidth() {
        return $this->length + $this->fullStart - $this->start;
    }

    public function getFullWidth() {
        return $this->length;
    }

    public function getEndPosition() {
        return $this->fullStart + $this->length;
    }

    /**
     * @return string[] - A hash map of the format [int $tokenKind => string $tokenName]
     */
    private static function getTokenKindNameFromValueMap() {
        static $mapToKindName;
        if ($mapToKindName === null) {
            $constants = (new \ReflectionClass("Microsoft\\PhpParser\\TokenKind"))->getConstants();
            $mapToKindName = \array_flip($constants);
        }
        return $mapToKindName;
    }

    /**
     * @param int $kind
     * @return string (Or int, if the kind name for $kind wasn't found)
     */
    public static function getTokenKindNameFromValue($kind) {
        $mapToKindName = self::getTokenKindNameFromValueMap();
        return $mapToKindName[$kind] ?? $kind;
    }

    public function jsonSerialize() {
        $kindName = $this->getTokenKindNameFromValue($this->kind);

        if (!isset($GLOBALS["SHORT_TOKEN_SERIALIZE"])) {
            $GLOBALS["SHORT_TOKEN_SERIALIZE"] = false;
        }

        if ($GLOBALS["SHORT_TOKEN_SERIALIZE"]) {
            return [
                "kind" => $kindName,
                "textLength" => $this->length - ($this->start - $this->fullStart)
            ];
        } else {
            return [
                "kind" => $kindName,
                "fullStart" => $this->fullStart,
                "start" => $this->start,
                "length" => $this->length
            ];
        }
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class TokenKind {
    const Unknown = 0;
    const EndOfFileToken = 1;

    const Name = 2;
    const VariableName = 3;

    const SkippedToken = 4;
    const MissingToken = 5;
    const QualifiedName = 6;


    const AbstractKeyword = 101;
    const AndKeyword = 102;
    const ArrayKeyword = 103;
    const AsKeyword = 104;
    const BreakKeyword = 105;
    const CallableKeyword = 106;
    const CaseKeyword = 107;
    const CatchKeyword = 108;
    const ClassKeyword = 109;
    const CloneKeyword = 110;
    const ConstKeyword = 111;
    const ContinueKeyword = 112;
    const DeclareKeyword = 113;
    const DefaultKeyword = 114;
    const DieKeyword = 115;
    const DoKeyword = 116;
    const EchoKeyword = 117;
    const ElseKeyword = 118;
    const ElseIfKeyword = 119;
    const EmptyKeyword = 120;
    const EndDeclareKeyword = 121;
    const EndForKeyword = 122;
    const EndForEachKeyword = 123;
    const EndIfKeyword = 124;
    const EndSwitchKeyword = 125;
    const EndWhileKeyword = 126;
    const EvalKeyword = 127;
    const ExitKeyword = 128;
    const ExtendsKeyword = 129;
    const FinalKeyword = 130;
    const FinallyKeyword = 131;
    const ForKeyword = 132;
    const ForeachKeyword = 133;
    const FunctionKeyword = 134;
    const GlobalKeyword = 135;
    const GotoKeyword = 136;
    const IfKeyword = 137;
    const ImplementsKeyword = 138;
    const IncludeKeyword = 139;
    const IncludeOnceKeyword = 140;
    const InstanceOfKeyword = 141;
    const InsteadOfKeyword = 142;
    const InterfaceKeyword = 143;
    const IsSetKeyword = 144;
    const ListKeyword = 145;
    const NamespaceKeyword = 146;
    const NewKeyword = 147;
    const OrKeyword = 148;
    const PrintKeyword = 149;
    const PrivateKeyword = 150;
    const ProtectedKeyword = 151;
    const PublicKeyword = 152;
    const RequireKeyword = 153;
    const RequireOnceKeyword = 154;
    const ReturnKeyword = 155;
    const StaticKeyword = 156;
    const SwitchKeyword = 157;
    const ThrowKeyword = 158;
    const TraitKeyword = 159;
    const TryKeyword = 160;
    const UnsetKeyword = 161;
    const UseKeyword = 162;
    const VarKeyword = 163;
    const WhileKeyword = 164;
    const XorKeyword = 165;
    const YieldKeyword = 166;
    const YieldFromKeyword = 167;

    const OpenBracketToken = 201;
    const CloseBracketToken = 202;
    const OpenParenToken = 203;
    const CloseParenToken = 204;
    const OpenBraceToken = 205;
    const CloseBraceToken = 206;
    const DotToken = 207;
    const ArrowToken = 208;
    const PlusPlusToken = 209;
    const MinusMinusToken = 210;
    const AsteriskAsteriskToken = 211;
    const AsteriskToken = 212;
    const PlusToken = 213;
    const MinusToken = 214;
    const TildeToken = 215;
    const ExclamationToken = 216;
    const DollarToken = 217;
    const SlashToken = 218;
    const PercentToken = 220;
    const LessThanLessThanToken = 221;
    const GreaterThanGreaterThanToken = 222;
    const LessThanToken = 223;
    const GreaterThanToken = 224;
    const LessThanEqualsToken = 225;
    const GreaterThanEqualsToken = 226;
    const EqualsEqualsToken = 227;
    const EqualsEqualsEqualsToken = 228;
    const ExclamationEqualsToken = 229;
    const ExclamationEqualsEqualsToken = 230;
    const CaretToken = 231;
    const BarToken = 232;
    const AmpersandToken = 233;
    const AmpersandAmpersandToken = 234;
    const BarBarToken = 235;
    const ColonToken = 236;
    const SemicolonToken = 237;
    const EqualsToken = 238;
    const AsteriskAsteriskEqualsToken = 239;
    const AsteriskEqualsToken = 240;
    const SlashEqualsToken = 241;
    const PercentEqualsToken = 242;
    const PlusEqualsToken = 243;
    const MinusEqualsToken = 244;
    const DotEqualsToken = 245;
    const LessThanLessThanEqualsToken = 246;
    const GreaterThanGreaterThanEqualsToken = 247;
    const AmpersandEqualsToken = 248;
    const CaretEqualsToken = 249;
    const BarEqualsToken = 250;
    const CommaToken = 251;
    const QuestionQuestionToken = 252;
    const LessThanEqualsGreaterThanToken = 253;
    const DotDotDotToken = 254;
    const BackslashToken = 255;
    const ColonColonToken = 256;
    const DoubleArrowToken = 257; // TODO missing from spec
    const LessThanGreaterThanToken = 258; // TODO missing from spec
    const AtSymbolToken = 259;
    const BacktickToken = 260;
    const QuestionToken = 261;

    const DecimalLiteralToken = 301;
    const OctalLiteralToken = 302;
    const HexadecimalLiteralToken = 303;
    const BinaryLiteralToken = 304;
    const FloatingLiteralToken = 305;
    const InvalidOctalLiteralToken = 306;
    const InvalidHexadecimalLiteral = 307;
    const InvalidBinaryLiteral = 308;
    const StringLiteralToken = 309;

    // RESERVED WORDS
    const IntReservedWord = 317;
    const FloatReservedWord = 318;
    const TrueReservedWord = 319;
    const StringReservedWord = 320;
    const BoolReservedWord = 321;
    const NullReservedWord = 322;

    const ScriptSectionStartTag = 323;
    const ScriptSectionEndTag = 324;


    // TODO how to handle incremental parsing w/ this?
    const ScriptSectionPrependedText = 325;
    const VoidReservedWord = 326;
    const FalseReservedWord = 327;

    const MemberName = 328;
    const Expression = 329;

    const BinaryReservedWord = 330; // TODO better way
    const BooleanReservedWord = 331;
    const DoubleReservedWord = 332;
    const IntegerReservedWord = 333;
    const ObjectReservedWord = 334;
    const RealReservedWord = 335;
    const ReturnType = 336;
    const InlineHtml = 337;

//    const DollarOpenCurly = 339;
    const EncapsedAndWhitespace = 400;
    const SingleQuoteToken = 401;
    const DoubleQuoteToken = 402;
    const DollarOpenBraceToken = 403;
    const OpenBraceDollarToken = 404;
    const CastToken = 405;
    const HeredocStart = 406;
    const HeredocEnd = 407;
    const StringVarname = 408;
    const UnsetCastToken = 409;
    const StringCastToken = 410;
    const ObjectCastToken = 411;
    const IntCastToken = 412;
    const DoubleCastToken = 413;
    const BoolCastToken = 414;
    const ArrayCastToken = 415;
    const IntegerLiteralToken = 416;
    const CommentToken = 417;
    const DocCommentToken = 418;

    // TODO type annotations - PHP7
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

/**
 * FilePositionMap can be used to get the line number for a large number of nodes (starting from 1).
 * It works the most efficiently when the requested node is close to the previously requested node.
 *
 * Other designs that weren't chosen:
 * - Precomputing all of the start/end offsets when initializing was slower - Some offsets weren't needed, and walking the tree was slower.
 * - Caching line numbers for previously requested offsets wasn't really necessary, since offsets are usually close together and weren't requested repeatedly.
 */
class FilePositionMap {
    /** @var string the full file contents */
    private $fileContents;

    /** @var int - Precomputed strlen($file_contents) */
    private $fileContentsLength;

    /** @var int the 0-based byte offset of the most recent request for a line number. */
    private $currentOffset;

    /** @var int the 1-based line number for $this->currentOffset (updated whenever currentOffset is updated) */
    private $lineForCurrentOffset;

    public function __construct(string $file_contents) {
        $this->fileContents = $file_contents;
        $this->fileContentsLength = \strlen($file_contents);
        $this->currentOffset = 0;
        $this->lineForCurrentOffset = 1;
    }

    /**
     * @param Node $node the node to get the start line for.
     * TODO deprecate and merge this and getTokenStartLine into getStartLine
     * if https://github.com/Microsoft/tolerant-php-parser/issues/166 is fixed,
     * (i.e. if there is a consistent way to get the start offset)
     */
    public function getNodeStartLine(Node $node) : int {
        return $this->getLineNumberForOffset($node->getStart());
    }

    /**
     * @param Token $token the token to get the start line for.
     */
    public function getTokenStartLine(Token $token) : int {
        return $this->getLineNumberForOffset($token->start);
    }

    /**
     * @param Node|Token $node
     */
    public function getStartLine($node) : int {
        if ($node instanceof Token) {
            $offset = $node->start;
        } else {
            $offset = $node->getStart();
        }
        return $this->getLineNumberForOffset($offset);
    }

    /**
     * @param Node|Token $node
     * Similar to getStartLine but includes the column
     */
    public function getStartLineCharacterPositionForOffset($node) : LineCharacterPosition {
        if ($node instanceof Token) {
            $offset = $node->start;
        } else {
            $offset = $node->getStart();
        }
        return $this->getLineCharacterPositionForOffset($offset);
    }

    /** @param Node|Token $node */
    public function getEndLine($node) : int {
        return $this->getLineNumberForOffset($node->getEndPosition());
    }

    /**
     * @param Node|Token $node
     * Similar to getStartLine but includes the column
     */
    public function getEndLineCharacterPosition($node) : LineCharacterPosition {
        return $this->getLineCharacterPositionForOffset($node->getEndPosition());
    }

    /**
     * @param int $offset
     * Similar to getStartLine but includes both the line and the column
     */
    public function getLineCharacterPositionForOffset(int $offset) : LineCharacterPosition {
        $line = $this->getLineNumberForOffset($offset);
        $character = $this->getColumnForOffset($offset);
        return new LineCharacterPosition($line, $character);
    }

    /**
     * @param int $offset - A 0-based byte offset
     * @return int - gets the 1-based line number for $offset
     */
    public function getLineNumberForOffset(int $offset) : int {
        if ($offset < 0) {
            $offset = 0;
        } elseif ($offset > $this->fileContentsLength) {
            $offset = $this->fileContentsLength;
        }
        $currentOffset = $this->currentOffset;
        if ($offset > $currentOffset) {
            $this->lineForCurrentOffset += \substr_count($this->fileContents, "\n", $currentOffset, $offset - $currentOffset);
            $this->currentOffset = $offset;
        } elseif ($offset < $currentOffset) {
            $this->lineForCurrentOffset -= \substr_count($this->fileContents, "\n", $offset, $currentOffset - $offset);
            $this->currentOffset = $offset;
        }
        return $this->lineForCurrentOffset;
    }

    /**
     * @param int $offset - A 0-based byte offset
     * @return int - gets the 1-based column number for $offset
     */
    public function getColumnForOffset(int $offset) : int {
        $length = $this->fileContentsLength;
        if ($offset <= 1) {
            return 1;
        } elseif ($offset > $length) {
            $offset = $length;
        }
        // Postcondition: offset >= 1, ($lastNewlinePos < $offset)
        // If there was no previous newline, lastNewlinePos = 0

        // Start strrpos check from the character before the current character,
        // in case the current character is a newline.
        $lastNewlinePos = \strrpos($this->fileContents, "\n", -$length + $offset - 1);
        return 1 + $offset - ($lastNewlinePos === false ? 0 : $lastNewlinePos + 1);
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

interface NamespacedNameInterface {
    public function getNamespacedName() : ResolvedName;
}
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class LineCharacterPosition {
    public $line;
    public $character;

    public function __construct(int $line, int $character) {
        $this->line = $line;
        $this->character = $character;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

interface TokenStreamProviderInterface {
    public function scanNextToken() : Token;

    public function getCurrentPosition() : int;

    public function setCurrentPosition(int $pos);

    public function getEndOfFilePosition() : int;

    public function getTokensArray() : array;
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class TraitSelectOrAliasClause extends Node {
    /** @var QualifiedName|Node\Expression\ScopedPropertyAccessExpression */
    public $name;

    /** @var Token */
    public $asOrInsteadOfKeyword;

    /** @var Token[] */
    public $modifiers;

    /** @var QualifiedName|Node\Expression\ScopedPropertyAccessExpression */
    public $targetName;

    const CHILD_NAMES = [
        'name',
        'asOrInsteadOfKeyword',
        'modifiers',
        'targetName'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Token;

class ReservedWord extends Expression {
    /** @var Token */
    public $children;

    const CHILD_NAMES = [
        'children'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Token;

trait FunctionHeader {
    /** @var Token */
    public $functionKeyword;
    /** @var Token */
    public $byRefToken;
    /** @var null|Token */
    public $name;
    /** @var Token */
    public $openParen;
    /** @var DelimitedList\ParameterDeclarationList */
    public $parameters;
    /** @var Token */
    public $closeParen;
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class TraitUseClause extends Node {
    /** @var Token */
    public $useKeyword;

    /** @var DelimitedList\QualifiedNameList */
    public $traitNameList;

    /** @var Token */
    public $semicolonOrOpenBrace;

    /** @var DelimitedList\TraitSelectOrAliasClauseList */
    public $traitSelectAndAliasClauses;

    /** @var Token */
    public $closeBrace;

    const CHILD_NAMES = [
        'useKeyword',
        'traitNameList',
        'semicolonOrOpenBrace',
        'traitSelectAndAliasClauses',
        'closeBrace'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class SourceFileNode extends Node {
    /** @var string */
    public $fileContents;

    /** @var string */
    public $uri;

    /** @var Node[] */
    public $statementList;

    /** @var Token */
    public $endOfFileToken;

    const CHILD_NAMES = ['statementList', 'endOfFileToken'];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class ForeachValue extends Node {
    /** @var Token|null */
    public $ampersand;
    /** @var Expression */
    public $expression;

    const CHILD_NAMES = [
        'ampersand',
        'expression'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class ElseIfClauseNode extends Node {
    /** @var Token */
    public $elseIfKeyword;
    /** @var Token */
    public $openParen;
    /** @var Expression */
    public $expression;
    /** @var Token */
    public $closeParen;
    /** @var Token|null */
    public $colon;
    /** @var StatementNode|StatementNode[] */
    public $statements;

    const CHILD_NAMES = [
        'elseIfKeyword',
        'openParen',
        'expression',
        'closeParen',
        'colon',
        'statements'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;
use Microsoft\PhpParser\TokenKind;

class PropertyDeclaration extends Node {

    /** @var Token[] */
    public $modifiers;

    /** @var DelimitedList\ExpressionList */
    public $propertyElements;

    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'modifiers',
        'propertyElements',
        'semicolon'
    ];

    public function isStatic() : bool {
        if ($this->modifiers === null) {
            return false;
        }
        foreach ($this->modifiers as $modifier) {
            if ($modifier->kind === TokenKind::StaticKeyword) {
                return true;
            }
        }
        return false;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class MissingMemberDeclaration extends Node {

    /** @var Token[] */
    public $modifiers;

    const CHILD_NAMES = [
        'modifiers'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;

abstract class Expression extends Node {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class RelativeSpecifier extends Node {
    /** @var Token */
    public $namespaceKeyword;

    /** @var Token */
    public $backslash;

    const CHILD_NAMES = [
        'namespaceKeyword',
        'backslash'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\DelimitedList\UseVariableNameList;
use Microsoft\PhpParser\Token;

class AnonymousFunctionUseClause extends Node {
    /** @var Token */
    public $useKeyword;

    /** @var Token */
    public $openParen;

    /** @var UseVariableNameList */
    public $useVariableNameList;

    /** @var Token */
    public $closeParen;

    const CHILD_NAMES = [
        'useKeyword',
        'openParen',
        'useVariableNameList',
        'closeParen'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Diagnostic;
use Microsoft\PhpParser\DiagnosticKind;
use Microsoft\PhpParser\DiagnosticsProvider;
use Microsoft\PhpParser\FunctionLike;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;
use Microsoft\PhpParser\TokenKind;

class MethodDeclaration extends Node implements FunctionLike {
    /** @var Token[] */
    public $modifiers;

    use FunctionHeader, FunctionReturnType, FunctionBody;

    const CHILD_NAMES = [
        'modifiers',

        // FunctionHeader
        'functionKeyword',
        'byRefToken',
        'name',
        'openParen',
        'parameters',
        'closeParen',

        // FunctionReturnType
        'colonToken',
        'questionToken',
        'returnType',

        // FunctionBody
        'compoundStatementOrSemicolon'
    ];

    public function isStatic() : bool {
        if ($this->modifiers === null) {
            return false;
        }
        foreach ($this->modifiers as $modifier) {
            if ($modifier->kind === TokenKind::StaticKeyword) {
                return true;
            }
        }
        return false;
    }

    public function getName() {
        return $this->name->getText($this->getFileContents());
    }

    /**
     * @return Diagnostic|null - Callers should use DiagnosticsProvider::getDiagnostics instead
     * @internal
     * @override
     */
    public function getDiagnosticForNode() {
        foreach ($this->modifiers as $modifier) {
            if ($modifier->kind === TokenKind::VarKeyword) {
                return new Diagnostic(
                    DiagnosticKind::Error,
                    "Unexpected modifier '" . DiagnosticsProvider::getTextForTokenKind($modifier->kind) . "'",
                    $modifier->start,
                    $modifier->length
                );
            }
        }
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class InterfaceMembers extends Node {
    /** @var Token */
    public $openBrace;

    /** @var Node[] */
    public $interfaceMemberDeclarations;

    /** @var Token */
    public $closeBrace;

    const CHILD_NAMES = [
        'openBrace',
        'interfaceMemberDeclarations',
        'closeBrace'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class ClassMembersNode extends Node {
    /** @var Token */
    public $openBrace;

    /** @var Node[] */
    public $classMemberDeclarations;

    /** @var Token */
    public $closeBrace;

    const CHILD_NAMES = [
        'openBrace',
        'classMemberDeclarations',
        'closeBrace'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\NamespacedNameInterface;
use Microsoft\PhpParser\NamespacedNameTrait;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class ConstElement extends Node implements NamespacedNameInterface {
    use NamespacedNameTrait;

    /** @var Token */
    public $name;

    /** @var Token */
    public $equalsToken;

    /** @var Expression */
    public $assignment;

    const CHILD_NAMES = [
        'name',
        'equalsToken',
        'assignment'
    ];

    public function getNameParts() : array {
        return [$this->name];
    }

    public function getName() {
        return $this->name->getText($this->getFileContents());
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class ClassBaseClause extends Node {
    /** @var Token */
    public $extendsKeyword;

    /** @var QualifiedName */
    public $baseClass;

    const CHILD_NAMES = [
        'extendsKeyword',
        'baseClass'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class ConstElementList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class StaticVariableNameList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class ArrayElementList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class TraitSelectOrAliasClauseList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class ListExpressionList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class QualifiedNameList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class ArgumentExpressionList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class UseVariableNameList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class ExpressionList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class NamespaceUseClauseList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class VariableNameList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class ParameterDeclarationList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class QualifiedNameParts extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class NamespaceUseGroupClauseList extends DelimitedList {
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class ArrayElement extends Node {

    /** @var Expression|null */
    public $elementKey;

    /** @var Token|null */
    public $arrowToken;

    /** @var Token|null */
    public $byRef;

    /** @var Expression */
    public $elementValue;

    const CHILD_NAMES = [
        'elementKey',
        'arrowToken',
        'byRef',
        'elementValue'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Token;

class NumericLiteral extends Expression {
    /** @var Token */
    public $children;

    const CHILD_NAMES = [
        'children'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class NamespaceUseGroupClause extends Node {

    /** @var Token */
    public $functionOrConst;
    /** @var QualifiedName */
    public $namespaceName;
    /** @var  NamespaceAliasingClause */
    public $namespaceAliasingClause;

    const CHILD_NAMES = [
        'functionOrConst',
        'namespaceName',
        'namespaceAliasingClause'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class DefaultStatementNode extends Node {
    /** @var Token */
    public $defaultKeyword;
    /** @var Token */
    public $defaultLabelTerminator;
    /** @var StatementNode[] */
    public $statementList;

    const CHILD_NAMES = [
        'defaultKeyword',
        'defaultLabelTerminator',
        'statementList'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class ClassInterfaceClause extends Node {
    /** @var Token */
    public $implementsKeyword;

    /** @var DelimitedList\QualifiedNameList|null */
    public $interfaceNameList;

    const CHILD_NAMES = [
        'implementsKeyword',
        'interfaceNameList'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;
use Microsoft\PhpParser\TokenKind;

abstract class DelimitedList extends Node {
    /** @var Token[]|Node[] */
    public $children;

    const CHILD_NAMES = [
        'children'
    ];

    const DELIMITERS = [TokenKind::CommaToken, TokenKind::BarToken, TokenKind::SemicolonToken];

    public function getElements() : \Generator {
        foreach ($this->children as $child) {
            if ($child instanceof Node) {
                yield $child;
            } elseif ($child instanceof Token && !\in_array($child->kind, self::DELIMITERS)) {
                yield $child;
            }
        }
    }

    public function getValues() {
        foreach ($this->children as $idx=>$value) {
            if ($idx % 2 == 0) {
                yield $value;
            }
        }
    }

    public function addElement($node) {
        if ($node === null) {
            return;
        }
        if ($this->children === null) {
            $this->children = [];
        }
        $this->children[] = $node;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;

abstract class StatementNode extends Node {
}
<?php
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\NamespacedNameInterface;
use Microsoft\PhpParser\NamespacedNameTrait;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\Expression\AnonymousFunctionCreationExpression;
use Microsoft\PhpParser\Node\Expression\CallExpression;
use Microsoft\PhpParser\Node\Expression\ObjectCreationExpression;
use Microsoft\PhpParser\ResolvedName;
use Microsoft\PhpParser\Token;
use Microsoft\PhpParser\TokenKind;

class QualifiedName extends Node implements NamespacedNameInterface {
    use NamespacedNameTrait;

    /** @var Token */
    public $globalSpecifier; // \_opt
    /** @var RelativeSpecifier */
    public $relativeSpecifier; // namespace\
    /** @var array */
    public $nameParts;

    const CHILD_NAMES = [
        'globalSpecifier',
        'relativeSpecifier',
        'nameParts'
    ];

    /**
     * Checks whether a QualifiedName is prefixed with a backslash global specifier.
     * @return bool
     */
    public function isFullyQualifiedName() : bool {
        return isset($this->globalSpecifier);
    }

    /**
     * Checks whether a QualifiedName begins with a "namespace" keyword
     * @return bool
     */
    public function isRelativeName() : bool {
        return isset($this->relativeSpecifier);
    }

    /**
     * Checks whether a Name includes at least one namespace separator (and is neither fully-qualified nor relative)
     * @return bool
     */
    public function isQualifiedName() : bool {
        return
            !$this->isFullyQualifiedName() &&
            !$this->isRelativeName() &&
            \count($this->nameParts) > 1; // at least one namespace separator
    }

    /**
     * Checks whether a name is does not include a namespace separator.
     * @return bool
     */
    public function isUnqualifiedName() : bool {
        return !($this->isFullyQualifiedName() || $this->isRelativeName() || $this->isQualifiedName());
    }

    /**
     * Gets resolved name based on name resolution rules defined in:
     * http://php.net/manual/en/language.namespaces.rules.php
     *
     * Returns null if one of the following conditions is met:
     * - name resolution is not valid for this element (e.g. part of the name in a namespace definition)
     * - name cannot be resolved (unqualified namespaced function/constant names that are not explicitly imported.)
     *
     * @return null|ResolvedName
     * @throws \Exception
     */
    public function getResolvedName($namespaceDefinition = null) {
        // Name resolution not applicable to constructs that define symbol names or aliases.
        if (($this->parent instanceof Node\Statement\NamespaceDefinition && $this->parent->name->getStart() === $this->getStart()) ||
            $this->parent instanceof Node\Statement\NamespaceUseDeclaration ||
            $this->parent instanceof Node\NamespaceUseClause ||
            $this->parent instanceof Node\NamespaceUseGroupClause ||
            $this->parent->parent instanceof Node\TraitUseClause ||
            $this->parent instanceof Node\TraitSelectOrAliasClause ||
            ($this->parent instanceof TraitSelectOrAliasClause &&
            ($this->parent->asOrInsteadOfKeyword == null || $this->parent->asOrInsteadOfKeyword->kind === TokenKind::AsKeyword))
        ) {
            return null;
        }

        if (array_search($lowerText = strtolower($this->getText()), ["self", "static", "parent"]) !== false) {
            return $lowerText;
        }

        // FULLY QUALIFIED NAMES
        // - resolve to the name without leading namespace separator.
        if ($this->isFullyQualifiedName()) {
            return ResolvedName::buildName($this->nameParts, $this->getFileContents());
        }

        // RELATIVE NAMES
        // - resolve to the name with namespace replaced by the current namespace.
        // - if current namespace is global, strip leading namespace\ prefix.
        if ($this->isRelativeName()) {
            return $this->getNamespacedName();
        }

        list($namespaceImportTable, $functionImportTable, $constImportTable) = $this->getImportTablesForCurrentScope();

        // QUALIFIED NAMES
        // - first segment of the name is translated according to the current class/namespace import table.
        // - If no import rule applies, the current namespace is prepended to the name.
        if ($this->isQualifiedName()) {
            return $this->tryResolveFromImportTable($namespaceImportTable) ?? $this->getNamespacedName();
        }

        // UNQUALIFIED NAMES
        // - translated according to the current import table for the respective symbol type.
        //   (class-like => namespace import table, constant => const import table, function => function import table)
        // - if no import rule applies:
        //   - all symbol types: if current namespace is global, resolve to global namespace.
        //   - class-like symbols: resolve from current namespace.
        //   - function or const: resolved at runtime (from current namespace, with fallback to global namespace).
        if ($this->isConstantName()) {
            $resolvedName = $this->tryResolveFromImportTable($constImportTable, /* case-sensitive */ true);
            $namespaceDefinition = $this->getNamespaceDefinition();
            if ($namespaceDefinition !== null && $namespaceDefinition->name === null) {
                $resolvedName = $resolvedName ?? ResolvedName::buildName($this->nameParts, $this->getFileContents());
            }
            return $resolvedName;
        } elseif ($this->parent instanceof CallExpression) {
            $resolvedName = $this->tryResolveFromImportTable($functionImportTable);
            if (($namespaceDefinition = $this->getNamespaceDefinition()) === null || $namespaceDefinition->name === null) {
                $resolvedName = $resolvedName ?? ResolvedName::buildName($this->nameParts, $this->getFileContents());
            }
            return $resolvedName;
        }

        return $this->tryResolveFromImportTable($namespaceImportTable) ?? $this->getNamespacedName();
    }

    public function getLastNamePart() {
        $parts = $this->nameParts;
        for ($i = \count($parts) - 1; $i >= 0; $i--) {
            // TODO - also handle reserved word tokens
            if ($parts[$i]->kind === TokenKind::Name) {
                return $parts[$i];
            }
        }
        return null;
    }

    /**
     * @param ResolvedName[] $importTable
     * @param bool $isCaseSensitive
     * @return string|null
     */
    private function tryResolveFromImportTable($importTable, bool $isCaseSensitive = false) {
        $content = $this->getFileContents();
        $index = $this->nameParts[0]->getText($content);
//        if (!$isCaseSensitive) {
//            $index = strtolower($index);
//        }
        if(isset($importTable[$index])) {
            $resolvedName = $importTable[$index];
            $resolvedName->addNameParts(\array_slice($this->nameParts, 1), $content);
            return $resolvedName;
        }
        return null;
    }

    private function isConstantName() : bool {
        return
            ($this->parent instanceof Node\Statement\ExpressionStatement || $this->parent instanceof Expression) &&
            !(
                $this->parent instanceof Node\Expression\MemberAccessExpression || $this->parent instanceof CallExpression ||
                $this->parent instanceof ObjectCreationExpression ||
                $this->parent instanceof Node\Expression\ScopedPropertyAccessExpression || $this->parent instanceof AnonymousFunctionCreationExpression ||
                ($this->parent instanceof Node\Expression\BinaryExpression && $this->parent->operator->kind === TokenKind::InstanceOfKeyword)
            );
    }

    public function getNameParts() : array {
        return $this->nameParts;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class CatchClause extends Node {
    /** @var Token */
    public $catch;
    /** @var Token */
    public $openParen;
    /** @var QualifiedName */
    public $qualifiedName;
    /** @var Token */
    public $variableName;
    /** @var Token */
    public $closeParen;
    /** @var StatementNode */
    public $compoundStatement;

    const CHILD_NAMES = [
        'catch',
        'openParen',
        'qualifiedName',
        'variableName',
        'closeParen',
        'compoundStatement'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class ElseClauseNode extends Node {
    /** @var Token */
    public $elseKeyword;
    /** @var Token */
    public $colon;
    /** @var StatementNode|StatementNode[] */
    public $statements;

    const CHILD_NAMES = [
        'elseKeyword',
        'colon',
        'statements'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class NamespaceAliasingClause extends Node {
    /** @var Token */
    public $asKeyword;
    /** @var Token */
    public $name;

    const CHILD_NAMES = [
        'asKeyword',
        'name'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class StringLiteral extends Expression {
    /** @var Token */
    public $startQuote;

    /** @var Token[]|Node[]|Token */
    public $children;

    /** @var Token */
    public $endQuote;

    const CHILD_NAMES = [
        'startQuote',
        'children',
        'endQuote',
    ];

    public function getStringContentsText() {
        // TODO add tests
        $stringContents = "";
        if (isset($this->startQuote)) {
            foreach ($this->children as $child) {
                $contents = $this->getFileContents();
                $stringContents .= $child->getFullText($contents);
            }
        } else {
            // TODO ensure string consistency (all strings should have start / end quote)
            $stringContents = trim($this->children->getText($this->getFileContents()), '"\'');
        }
        return $stringContents;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class CaseStatementNode extends Node {
    /** @var Token */
    public $caseKeyword;
    /** @var Expression */
    public $expression;
    /** @var Token */
    public $defaultLabelTerminator;
    /** @var StatementNode[] */
    public $statementList;

    const CHILD_NAMES = [
        'caseKeyword',
        'expression',
        'defaultLabelTerminator',
        'statementList'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class UseVariableName extends Node {
    const CHILD_NAMES = ['byRef', 'variableName'];

    /** @var Token|null */
    public $byRef;

    /** @var Token */
    public $variableName;

    public function getName() {
        if (
            $this->variableName instanceof Token &&
            $name = substr($this->variableName->getText($this->getFileContents()), 1)
        ) {
            return $name;
        }
        return null;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

trait FunctionUseClause {
    /** @var AnonymousFunctionUseClause|null */
    public $anonymousFunctionUseClause;
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class FinallyClause extends Node {
    /** @var Token */
    public $finallyToken;
    /** @var StatementNode */
    public $compoundStatement;

    const CHILD_NAMES = [
        'finallyToken',
        'compoundStatement'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class IssetIntrinsicExpression extends Expression {

    /** @var Token */
    public $issetKeyword;

    /** @var Token */
    public $openParen;

    /** @var DelimitedList\ExpressionList */
    public $expressions;

    /** @var Token */
    public $closeParen;

    const CHILD_NAMES = [
        'issetKeyword',
        'openParen',
        'expressions',
        'closeParen'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class ListIntrinsicExpression extends Expression {

    /** @var Token */
    public $listKeyword;

    /** @var Token */
    public $openParen;

    /** @var DelimitedList\ListExpressionList */
    public $listElements;

    /** @var Token */
    public $closeParen;

    const CHILD_NAMES = [
        'listKeyword',
        'openParen',
        'listElements',
        'closeParen'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\DelimitedList\ExpressionList;
use Microsoft\PhpParser\Token;

class EchoExpression extends Expression {

    /** @var Token */
    public $echoKeyword;

    /** @var ExpressionList */
    public $expressions;

    const CHILD_NAMES = [
        'echoKeyword',
        'expressions'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Token;

class ErrorControlExpression extends UnaryExpression {

    /** @var Token */
    public $operator;

    /** @var UnaryExpression */
    public $operand;

    const CHILD_NAMES = [
        'operator',
        'operand'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class MemberAccessExpression extends Expression {

    /** @var Expression */
    public $dereferencableExpression;

    /** @var Token */
    public $arrowToken;

    /** @var Token */
    public $memberName;

    const CHILD_NAMES = [
        'dereferencableExpression',
        'arrowToken',
        'memberName'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class SubscriptExpression extends Expression {

    /** @var Expression */
    public $postfixExpression;

    /** @var Token */
    public $openBracketOrBrace;

    /** @var Expression */
    public $accessExpression;

    /** @var Token */
    public $closeBracketOrBrace;

    const CHILD_NAMES = [
        'postfixExpression',
        'openBracketOrBrace',
        'accessExpression',
        'closeBracketOrBrace'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Token;

class PrefixUpdateExpression extends UnaryExpression {

    /** @var Token */
    public $incrementOrDecrementOperator;

    /** @var Variable */
    public $operand;

    const CHILD_NAMES = [
        'incrementOrDecrementOperator',
        'operand'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class EmptyIntrinsicExpression extends Expression {

    /** @var Token */
    public $emptyKeyword;

    /** @var Token */
    public $openParen;

    /** @var Expression */
    public $expression;

    /** @var Token */
    public $closeParen;

    const CHILD_NAMES = [
        'emptyKeyword',
        'openParen',
        'expression',
        'closeParen'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class TernaryExpression extends Expression {

    /** @var Expression */
    public $condition;

    /** @var Token */
    public $questionToken;

    /** @var Expression */
    public $ifExpression;

    /** @var Token */
    public $colonToken;

    /** @var Expression */
    public $elseExpression;

    const CHILD_NAMES = [
        'condition',
        'questionToken',
        'ifExpression',
        'colonToken',
        'elseExpression'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class ScriptInclusionExpression extends Expression {
    /** @var Token */
    public $requireOrIncludeKeyword;
    /** @var Expression */
    public $expression;

    const CHILD_NAMES = [
        'requireOrIncludeKeyword',
        'expression'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class Variable extends Expression {
    /** @var Token */
    public $dollar;

    /** @var Token|Variable|BracedExpression */
    public $name;

    const CHILD_NAMES = [
        'dollar',
        'name'
    ];

    public function getName() {
        if (
            $this->name instanceof Token &&
            $name = ltrim($this->name->getText($this->getFileContents()), '$')
        ) {
            return $name;
        }
        return null;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Token;

class UnaryOpExpression extends UnaryExpression {

    /** @var Token */
    public $operator;

    /** @var UnaryExpression */
    public $operand;

    const CHILD_NAMES = [
        'operator',
        'operand'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class AssignmentExpression extends BinaryExpression {

    /** @var Expression */
    public $leftOperand;

    /** @var Token */
    public $operator;

    /** @var Token */
    public $byRef;

    /** @var Expression */
    public $rightOperand;

    const CHILD_NAMES = [
        'leftOperand',
        'operator',
        'byRef',
        'rightOperand'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class ExitIntrinsicExpression extends Expression {

    /** @var Token */
    public $exitOrDieKeyword;

    /** @var Token|null */
    public $openParen;

    /** @var Expression|null */
    public $expression;

    /** @var Token|null */
    public $closeParen;

    const CHILD_NAMES = [
        'exitOrDieKeyword',
        'openParen',
        'expression',
        'closeParen'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class UnsetIntrinsicExpression extends Expression {

    /** @var Token */
    public $unsetKeyword;

    /** @var Token */
    public $openParen;

    /** @var DelimitedList\ExpressionList */
    public $expressions;

    /** @var Token */
    public $closeParen;

    const CHILD_NAMES = [
        'unsetKeyword',
        'openParen',
        'expressions',
        'closeParen'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\FunctionLike;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\FunctionBody;
use Microsoft\PhpParser\Node\FunctionHeader;
use Microsoft\PhpParser\Node\FunctionReturnType;
use Microsoft\PhpParser\Node\FunctionUseClause;
use Microsoft\PhpParser\Token;

class AnonymousFunctionCreationExpression extends Expression implements FunctionLike {
    /** @var Token|null */
    public $staticModifier;

    use FunctionHeader, FunctionUseClause, FunctionReturnType, FunctionBody;

    const CHILD_NAMES = [
        'staticModifier',

        // FunctionHeader
        'functionKeyword',
        'byRefToken',
        'name',
        'openParen',
        'parameters',
        'closeParen',

        // FunctionUseClause
        'anonymousFunctionUseClause',

        // FunctionReturnType
        'colonToken',
        'questionToken',
        'returnType',

        // FunctionBody
        'compoundStatementOrSemicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class BracedExpression extends Expression {
    /** @var Token */
    public $openBrace;

    /** @var Expression */
    public $expression;

    /** @var Token */
    public $closeBrace;

    const CHILD_NAMES = [
        'openBrace',
        'expression',
        'closeBrace'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\ArrayElement;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class YieldExpression extends Expression {
    /** @var Token */
    public $yieldOrYieldFromKeyword;

    /** @var ArrayElement */
    public $arrayElement;

    const CHILD_NAMES = ['yieldOrYieldFromKeyword', 'arrayElement'];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class PostfixUpdateExpression extends Expression {
    /** @var Variable */
    public $operand;

    /** @var Token */
    public $incrementOrDecrementOperator;

    const CHILD_NAMES = [
        'operand',
        'incrementOrDecrementOperator'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;

class UnaryExpression extends Expression {
    /** @var UnaryExpression|Variable */
    public $operand;

    const CHILD_NAMES = [
        'operand'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class ArrayCreationExpression extends Expression {

    /** @var Token|null */
    public $arrayKeyword;

    /** @var Token */
    public $openParenOrBracket;

    /** @var DelimitedList\ArrayElementList */
    public $arrayElements;

    /** @var Token */
    public $closeParenOrBracket;

    const CHILD_NAMES = [
        'arrayKeyword',
        'openParenOrBracket',
        'arrayElements',
        'closeParenOrBracket'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class CallExpression extends Expression {
    /** @var Expression */
    public $callableExpression;

    /** @var Token */
    public $openParen;

    /** @var DelimitedList\ArgumentExpressionList|null */
    public $argumentExpressionList;

    /** @var Token */
    public $closeParen;

    const CHILD_NAMES = [
        'callableExpression',
        'openParen',
        'argumentExpressionList',
        'closeParen'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\ClassBaseClause;
use Microsoft\PhpParser\Node\ClassInterfaceClause;
use Microsoft\PhpParser\Node\ClassMembersNode;
use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\QualifiedName;
use Microsoft\PhpParser\Token;

class ObjectCreationExpression extends Expression {

    /** @var Token */
    public $newKeword;

    /** @var QualifiedName|Variable|Token */
    public $classTypeDesignator;

    /** @var Token|null */
    public $openParen;

    /** @var DelimitedList\ArgumentExpressionList|null  */
    public $argumentExpressionList;

    /** @var Token|null */
    public $closeParen;

    /** @var ClassBaseClause|null */
    public $classBaseClause;

    /** @var ClassInterfaceClause|null */
    public $classInterfaceClause;

    /** @var ClassMembersNode|null */
    public $classMembers;

    const CHILD_NAMES = [
        'newKeword', // TODO
        'classTypeDesignator',
        'openParen',
        'argumentExpressionList',
        'closeParen',
        'classBaseClause',
        'classInterfaceClause',
        'classMembers'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class BinaryExpression extends Expression {

    /** @var Expression */
    public $leftOperand;

    /** @var Token */
    public $operator;

    /** @var Expression */
    public $rightOperand;

    const CHILD_NAMES = [
        'leftOperand',
        'operator',
        'rightOperand'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Token;

class CastExpression extends UnaryExpression {

    /** @var Token */
    public $openParen;

    /** @var Token */
    public $castType;

    /** @var Token */
    public $closeParen;

    /** @var Variable */
    public $operand;

    const CHILD_NAMES = [
        'openParen',
        'castType',
        'closeParen',
        'operand'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\QualifiedName;
use Microsoft\PhpParser\Token;

class ScopedPropertyAccessExpression extends Expression {

    /** @var Expression|QualifiedName|Token */
    public $scopeResolutionQualifier;

    /** @var Token */
    public $doubleColon;

    /** @var Token|Variable */
    public $memberName;

    const CHILD_NAMES = [
        'scopeResolutionQualifier',
        'doubleColon',
        'memberName'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class PrintIntrinsicExpression extends Expression {

    /** @var Token */
    public $printKeyword;

    /** @var Expression */
    public $expression;

    const CHILD_NAMES = [
        'printKeyword',
        'expression'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class ArgumentExpression extends Expression {
    /** @var Token|null */
    public $byRefToken; // TODO removed in newer versions of PHP. Also only accept variable, not expression if byRef

    /** @var Token|null */
    public $dotDotDotToken;

    /** @var Expression */
    public $expression;

    const CHILD_NAMES = [
        'byRefToken',
        'dotDotDotToken',
        'expression'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class CloneExpression extends Expression {

    /** @var Token */
    public $cloneKeyword;

    /** @var Expression */
    public $expression;

    const CHILD_NAMES = [
        'cloneKeyword',
        'expression'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class EvalIntrinsicExpression extends Expression {

    /** @var Token */
    public $evalKeyword;

    /** @var Token */
    public $openParen;

    /** @var Expression */
    public $expression;

    /** @var Token */
    public $closeParen;

    const CHILD_NAMES = [
        'evalKeyword',
        'openParen',
        'expression',
        'closeParen'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class ParenthesizedExpression extends Expression {

    /** @var Token */
    public $openParen;

    /** @var Expression */
    public $expression;

    /** @var Token */
    public $closeParen;

    const CHILD_NAMES = [
        'openParen',
        'expression',
        'closeParen'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class ClassConstDeclaration extends Node {

    /** @var Token[] */
    public $modifiers;

    /** @var Token */
    public $constKeyword;

    /** @var DelimitedList\ConstElementList */
    public $constElements;

    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'modifiers',
        'constKeyword',
        'constElements',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class Parameter extends Node {
    /** @var Token|null */
    public $questionToken;
    /** @var QualifiedName|Token|null */
    public $typeDeclaration;
    /** @var Token|null */
    public $byRefToken;
    /** @var Token|null */
    public $dotDotDotToken;
    /** @var Token */
    public $variableName;
    /** @var Token|null */
    public $equalsToken;
    /** @var null|Expression */
    public $default;

    const CHILD_NAMES = [
        'questionToken',
        'typeDeclaration',
        'byRefToken',
        'dotDotDotToken',
        'variableName',
        'equalsToken',
        'default'
    ];

    public function isVariadic() {
        return $this->byRefToken !== null;
    }

    public function getName() {
        if (
            $this->variableName instanceof Token &&
            $name = substr($this->variableName->getText($this->getFileContents()), 1)
        ) {
            return $name;
        }
        return null;
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node\Statement\CompoundStatementNode;
use Microsoft\PhpParser\Token;

trait FunctionBody {
    /** @var CompoundStatementNode|Token */
    public $compoundStatementOrSemicolon;
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Token;

trait FunctionReturnType {
    /** @var Token */
    public $colonToken;
    /** @var Token|null */
    public $questionToken;
    /** @var Token|QualifiedName */
    public $returnType;
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class InterfaceBaseClause extends Node {
    /** @var Token */
    public $extendsKeyword;

    /** @var DelimitedList\QualifiedNameList */
    public $interfaceNameList;

    const CHILD_NAMES = [
        'extendsKeyword',
        'interfaceNameList'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class ForeachKey extends Node {
    /** @var Expression */
    public $expression;
    /** @var Token */
    public $arrow;

    const CHILD_NAMES = [
        'expression',
        'arrow'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Token;

class NamespaceUseClause extends Node {
    /** @var QualifiedName */
    public $namespaceName;
    /** @var NamespaceAliasingClause */
    public $namespaceAliasingClause;
    /** @var Token|null */
    public $openBrace;
    /** @var DelimitedList\NamespaceUseGroupClauseList|null */
    public $groupClauses;
    /** @var Token|null */
    public $closeBrace;

    const CHILD_NAMES = [
        'namespaceName',
        'namespaceAliasingClause',
        'openBrace',
        'groupClauses',
        'closeBrace'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class TraitMembers extends Node {
    /** @var Token */
    public $openBrace;

    /** @var Node[] */
    public $traitMemberDeclarations;

    /** @var Token */
    public $closeBrace;

    const CHILD_NAMES = [
        'openBrace',
        'traitMemberDeclarations',
        'closeBrace',
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class StaticVariableDeclaration extends Node {

    /** @var Token */
    public $variableName;

    /** @var Token|null */
    public $equalsToken;

    /** @var Expression|null */
    public $assignment;

    const CHILD_NAMES = [
        'variableName',
        'equalsToken',
        'assignment',
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class GlobalDeclaration extends StatementNode {

    /** @var Token */
    public $globalKeyword;

    /** @var DelimitedList\VariableNameList */
    public $variableNameList;

    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'globalKeyword',
        'variableNameList',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\ForeachKey;
use Microsoft\PhpParser\Node\ForeachValue;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class ForeachStatement extends StatementNode {
    /** @var Token */
    public $foreach;
    /** @var Token */
    public $openParen;
    /** @var Expression */
    public $forEachCollectionName;
    /** @var Token */
    public $asKeyword;
    /** @var ForeachKey|null */
    public $foreachKey;
    /** @var ForeachValue */
    public $foreachValue;
    /** @var Token */
    public $closeParen;
    /** @var Token|null */
    public $colon;
    /** @var StatementNode|StatementNode[] */
    public $statements;
    /** @var Token|null */
    public $endForeach;
    /** @var Token|null */
    public $endForeachSemicolon;

    const CHILD_NAMES = [
        'foreach',
        'openParen',
        'forEachCollectionName',
        'asKeyword',
        'foreachKey',
        'foreachValue',
        'closeParen',
        'colon',
        'statements',
        'endForeach',
        'endForeachSemicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class InlineHtml extends StatementNode {
    /** @var Token|null */
    public $scriptSectionEndTag;

    /** @var Token */
    public $text;

    /** @var Token|null */
    public $scriptSectionStartTag;

    const CHILD_NAMES = [
        'scriptSectionEndTag',
        'text',
        'scriptSectionStartTag'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Diagnostic;
use Microsoft\PhpParser\DiagnosticKind;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;
use Microsoft\PhpParser\TokenKind;

class BreakOrContinueStatement extends StatementNode {
    /** @var Token */
    public $breakOrContinueKeyword;
    /** @var Expression|null */
    public $breakoutLevel;
    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'breakOrContinueKeyword',
        'breakoutLevel',
        'semicolon'
    ];

    /**
     * @return Diagnostic|null - Callers should use DiagnosticsProvider::getDiagnostics instead
     * @internal
     * @override
     */
    public function getDiagnosticForNode() {
        if ($this->breakoutLevel === null) {
            return null;
        }

        $breakoutLevel = $this->breakoutLevel;
        while ($breakoutLevel instanceof Node\Expression\ParenthesizedExpression) {
            $breakoutLevel = $breakoutLevel->expression;
        }

        if (
            $breakoutLevel instanceof Node\NumericLiteral
            && $breakoutLevel->children->kind === TokenKind::IntegerLiteralToken
        ) {
            $literalString = $breakoutLevel->getText();
            $firstTwoChars = \substr($literalString, 0, 2);

            if ($firstTwoChars === '0b' || $firstTwoChars === '0B') {
                if (\bindec(\substr($literalString, 2)) > 0) {
                    return null;
                }
            }
            else if (\intval($literalString, 0) > 0) {
                return null;
            }
        }

        if ($breakoutLevel instanceof Token) {
            $start = $breakoutLevel->getStartPosition();
        }
        else {
            $start = $breakoutLevel->getStart();
        }
        $end = $breakoutLevel->getEndPosition();

        return new Diagnostic(
            DiagnosticKind::Error,
            "Positive integer literal expected.",
            $start,
            $end - $start
        );
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\QualifiedName;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class NamespaceDefinition extends StatementNode {
    /** @var Token */
    public $namespaceKeyword;
    /** @var QualifiedName|null */
    public $name;
    /** @var CompoundStatementNode|Token */
    public $compoundStatementOrSemicolon;

    const CHILD_NAMES = [
        'namespaceKeyword',
        'name',
        'compoundStatementOrSemicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class ThrowStatement extends StatementNode {
    /** @var Token */
    public $throwKeyword;
    /** @var Expression */
    public $expression;
    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'throwKeyword',
        'expression',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class ReturnStatement extends StatementNode {
    /** @var Token */
    public $returnKeyword;
    /** @var Expression|null */
    public $expression;
    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'returnKeyword',
        'expression',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Diagnostic;
use Microsoft\PhpParser\DiagnosticKind;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class NamespaceUseDeclaration extends StatementNode {
    /** @var Token */
    public $useKeyword;
    /** @var Token */
    public $functionOrConst;
    /** @var DelimitedList\NamespaceUseClauseList */
    public $useClauses;
    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'useKeyword',
        'functionOrConst',
        'useClauses',
        'semicolon'
    ];

    /**
     * @return Diagnostic|null - Callers should use DiagnosticsProvider::getDiagnostics instead
     * @internal
     * @override
     */
    public function getDiagnosticForNode() {
        if (
            $this->useClauses != null
            && \count($this->useClauses->children) > 1
        ) {
            foreach ($this->useClauses->children as $useClause) {
                if($useClause instanceof Node\NamespaceUseClause && !is_null($useClause->openBrace)) {
                    return new Diagnostic(
                        DiagnosticKind::Error,
                        "; expected.",
                        $useClause->getEndPosition(),
                        1
                    );
                }
            }
        }
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class GotoStatement extends StatementNode {
    /** @var Token */
    public $goto;
    /** @var Token */
    public $name;
    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'goto',
        'name',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class DeclareStatement extends StatementNode {
    /** @var Token */
    public $declareKeyword;
    /** @var Token */
    public $openParen;
    /** @var Node */
    public $declareDirective;
    /** @var Token */
    public $closeParen;
    /** @var Token|null */
    public $colon;
    /** @var StatementNode|StatementNode[] */
    public $statements;
    /** @var Token|null */
    public $enddeclareKeyword;
    /** @var Token|null */
    public $semicolon;

    const CHILD_NAMES = [
        'declareKeyword',
        'openParen',
        'declareDirective',
        'closeParen',
        'colon',
        'statements',
        'enddeclareKeyword',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class ForStatement extends StatementNode {
    /** @var Token */
    public $for;
    /** @var Token */
    public $openParen;
    /** @var Expression|null */
    public $forInitializer;
    /** @var Token */
    public $exprGroupSemicolon1;
    /** @var Expression|null */
    public $forControl;
    /** @var Token */
    public $exprGroupSemicolon2;
    /** @var Expression|null */
    public $forEndOfLoop;
    /** @var Token */
    public $closeParen;
    /** @var Token|null */
    public $colon;
    /** @var StatementNode|StatementNode[] */
    public $statements;
    /** @var Token|null */
    public $endFor;
    /** @var Token|null */
    public $endForSemicolon;

    const CHILD_NAMES = [
        'for',
        'openParen',
        'forInitializer',
        'exprGroupSemicolon1',
        'forControl',
        'exprGroupSemicolon2',
        'forEndOfLoop',
        'closeParen',
        'colon',
        'statements',
        'endFor',
        'endForSemicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class ExpressionStatement extends StatementNode {
    /** @var Expression */
    public $expression;
    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'expression',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class FunctionStaticDeclaration extends StatementNode {

    /** @var Token */
    public $staticKeyword;

    /** @var DelimitedList\StaticVariableNameList */
    public $staticVariableNameList;

    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'staticKeyword',
        'staticVariableNameList',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class EmptyStatement extends StatementNode {
    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\ElseClauseNode;
use Microsoft\PhpParser\Node\ElseIfClauseNode;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class IfStatementNode extends StatementNode {
    /** @var Token */
    public $ifKeyword;
    /** @var Token */
    public $openParen;
    /** @var Expression */
    public $expression;
    /** @var Token */
    public $closeParen;
    /** @var Token|null */
    public $colon;
    /** @var StatementNode|StatementNode[] */
    public $statements;
    /** @var ElseIfClauseNode[] */
    public $elseIfClauses;
    /** @var ElseClauseNode|null */
    public $elseClause;
    /** @var Token|null */
    public $endifKeyword;
    /** @var Token|null */
    public $semicolon;

    const CHILD_NAMES = [
        'ifKeyword',
        'openParen',
        'expression',
        'closeParen',
        'colon',
        'statements',
        'elseIfClauses',
        'elseClause',
        'endifKeyword',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\CatchClause;
use Microsoft\PhpParser\Node\FinallyClause;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class TryStatement extends StatementNode {
    /** @var Token */
    public $tryKeyword;
    /** @var StatementNode */
    public $compoundStatement;
    /** @var CatchClause[]|null */
    public $catchClauses;
    /** @var FinallyClause|null */
    public $finallyClause;

    const CHILD_NAMES = [
        'tryKeyword',
        'compoundStatement',
        'catchClauses',
        'finallyClause'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\DelimitedList;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class ConstDeclaration extends StatementNode {
    /** @var Token */
    public $constKeyword;

    /** @var DelimitedList\ConstElementList */
    public $constElements;

    /** @var Token */
    public $semicolon;

    const CHILD_NAMES = [
        'constKeyword',
        'constElements',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\CaseStatementNode;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class SwitchStatementNode extends StatementNode {
    /** @var Token */
    public $switchKeyword;
    /** @var Token */
    public $openParen;
    /** @var Expression */
    public $expression;
    /** @var Token */
    public $closeParen;
    /** @var Token|null */
    public $colon;
    /** @var Token|null */
    public $openBrace;
    /** @var CaseStatementNode[] */
    public $caseStatements;
    /** @var Token|null */
    public $closeBrace;
    /** @var Token|null */
    public $endswitch;
    /** @var Token|null */
    public $semicolon;

    const CHILD_NAMES = [
        'switchKeyword',
        'openParen',
        'expression',
        'closeParen',
        'colon',
        'openBrace',
        'caseStatements',
        'closeBrace',
        'endswitch',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class WhileStatement extends StatementNode {
    /** @var Token */
    public $whileToken;
    /** @var Token */
    public $openParen;
    /** @var Expression */
    public $expression;
    /** @var Token */
    public $closeParen;
    /** @var Token|null */
    public $colon;
    /** @var StatementNode|StatementNode[] */
    public $statements;
    /** @var Token|null */
    public $endWhile;
    /** @var Token|null */
    public $semicolon;

    const CHILD_NAMES = [
        'whileToken',
        'openParen',
        'expression',
        'closeParen',
        'colon',
        'statements',
        'endWhile',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class DoStatement extends StatementNode {
    /** @var Token */
    public $do;
    /** @var StatementNode */
    public $statement;
    /** @var Token */
    public $whileToken;
    /** @var Token */
    public $openParen;
    /** @var Expression */
    public $expression;
    /** @var Token */
    public $closeParen;
    /** @var Token|null */
    public $semicolon;

    const CHILD_NAMES = [
        'do',
        'statement',
        'whileToken',
        'openParen',
        'expression',
        'closeParen',
        'semicolon'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\ClassLike;
use Microsoft\PhpParser\NamespacedNameInterface;
use Microsoft\PhpParser\NamespacedNameTrait;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Node\TraitMembers;
use Microsoft\PhpParser\Token;

class TraitDeclaration extends StatementNode implements NamespacedNameInterface, ClassLike {
    use NamespacedNameTrait;

    /** @var Token */
    public $traitKeyword;

    /** @var Token */
    public $name;

    /** @var TraitMembers */
    public $traitMembers;

    const CHILD_NAMES = [
        'traitKeyword',
        'name',
        'traitMembers'
    ];

    public function getNameParts() : array {
        return [$this->name];
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\ClassLike;
use Microsoft\PhpParser\NamespacedNameInterface;
use Microsoft\PhpParser\NamespacedNameTrait;
use Microsoft\PhpParser\Node\InterfaceBaseClause;
use Microsoft\PhpParser\Node\InterfaceMembers;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class InterfaceDeclaration extends StatementNode implements NamespacedNameInterface, ClassLike {
    use NamespacedNameTrait;

    /** @var Token */
    public $interfaceKeyword;

    /** @var Token */
    public $name;

    /** @var InterfaceBaseClause|null */
    public $interfaceBaseClause;

    /** @var InterfaceMembers */
    public $interfaceMembers;

    const CHILD_NAMES = [
        'interfaceKeyword',
        'name',
        'interfaceBaseClause',
        'interfaceMembers'
    ];

    public function getNameParts() : array {
        return [$this->name];
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\ClassLike;
use Microsoft\PhpParser\NamespacedNameInterface;
use Microsoft\PhpParser\NamespacedNameTrait;
use Microsoft\PhpParser\Node\ClassBaseClause;
use Microsoft\PhpParser\Node\ClassInterfaceClause;
use Microsoft\PhpParser\Node\ClassMembersNode;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class ClassDeclaration extends StatementNode implements NamespacedNameInterface, ClassLike {
    use NamespacedNameTrait;

    /** @var Token */
    public $abstractOrFinalModifier;

    /** @var Token */
    public $classKeyword;

    /** @var Token */
    public $name;

    /** @var ClassBaseClause */
    public $classBaseClause;

    /** @var ClassInterfaceClause */
    public $classInterfaceClause;

    /** @var ClassMembersNode */
    public $classMembers;

    const CHILD_NAMES = [
        'abstractOrFinalModifier',
        'classKeyword',
        'name',
        'classBaseClause',
        'classInterfaceClause',
        'classMembers'
    ];

    public function getNameParts() : array {
        return [$this->name];
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\FunctionLike;
use Microsoft\PhpParser\NamespacedNameInterface;
use Microsoft\PhpParser\NamespacedNameTrait;
use Microsoft\PhpParser\Node\FunctionBody;
use Microsoft\PhpParser\Node\FunctionHeader;
use Microsoft\PhpParser\Node\FunctionReturnType;
use Microsoft\PhpParser\Node\StatementNode;

class FunctionDeclaration extends StatementNode implements NamespacedNameInterface, FunctionLike {
    use FunctionHeader, FunctionReturnType, FunctionBody;
    use NamespacedNameTrait;

    const CHILD_NAMES = [
        // FunctionHeader
        'functionKeyword',
        'byRefToken',
        'name',
        'openParen',
        'parameters',
        'closeParen',

        // FunctionReturnType
        'colonToken',
        'questionToken',
        'returnType',

        // FunctionBody
        'compoundStatementOrSemicolon'
    ];

    public function getNameParts() : array {
        return [$this->name];
    }
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class CompoundStatementNode extends StatementNode {
    /** @var Token */
    public $openBrace;

    /** @var array|Node[] */
    public $statements;

    /** @var Token */
    public $closeBrace;

    const CHILD_NAMES = [
        'openBrace',
        'statements',
        'closeBrace'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Statement;

use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Token;

class NamedLabelStatement extends StatementNode {
    /** @var Token */
    public $name;
    /** @var Token */
    public $colon;
    /** @var StatementNode */
    public $statement;

    const CHILD_NAMES = [
        'name',
        'colon',
        'statement'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Token;

class DeclareDirective extends Node {
    /** @var Token */
    public $name;
    /** @var Token */
    public $equals;
    /** @var Token */
    public $literal;

    const CHILD_NAMES = [
        'name',
        'equals',
        'literal'
    ];
}
<?php
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser;

class MissingToken extends Token {
    public function __construct(int $kind, int $fullStart) {
        parent::__construct($kind, $fullStart, $fullStart, 0);
    }

    public function jsonSerialize() {
        return array_merge(
            ["error" => $this->getTokenKindNameFromValue(TokenKind::MissingToken)],
            parent::jsonSerialize()
        );
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock\Tags\Formatter;

interface Tag
{
    public function getName();

    public static function create($body);

    public function render(Formatter $formatter = null);

    public function __toString();
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.3
 *
 * @author    Vasil Rangelov <boen.robot@gmail.com>
 * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}version tag in a Docblock.
 */
final class Version extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'version';

    /**
     * PCRE regular expression matching a version vector.
     * Assumes the "x" modifier.
     */
    const REGEX_VECTOR = '(?:
        # Normal release vectors.
        \d\S*
        |
        # VCS version vectors. Per PHPCS, they are expected to
        # follow the form of the VCS name, followed by ":", followed
        # by the version vector itself.
        # By convention, popular VCSes like CVS, SVN and GIT use "$"
        # around the actual version vector.
        [^\s\:]+\:\s*\$[^\$]+\$
    )';

    /** @var string The version vector. */
    private $version = '';

    public function __construct($version = null, Description $description = null)
    {
        Assert::nullOrStringNotEmpty($version);

        $this->version = $version;
        $this->description = $description;
    }

    /**
     * @return static
     */
    public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
    {
        Assert::nullOrString($body);
        if (empty($body)) {
            return new static();
        }

        $matches = [];
        if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
            return null;
        }

        return new static(
            $matches[1],
            $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context)
        );
    }

    /**
     * Gets the version section of the tag.
     *
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->version . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a @covers tag in a Docblock.
 */
final class Covers extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'covers';

    /** @var Fqsen */
    private $refers = null;

    /**
     * Initializes this tag.
     *
     * @param Fqsen $refers
     * @param Description $description
     */
    public function __construct(Fqsen $refers, Description $description = null)
    {
        $this->refers = $refers;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        DescriptionFactory $descriptionFactory = null,
        FqsenResolver $resolver = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::notEmpty($body);

        $parts = preg_split('/\s+/Su', $body, 2);

        return new static(
            $resolver->resolve($parts[0], $context),
            $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context)
        );
    }

    /**
     * Returns the structural element this tag refers to.
     *
     * @return Fqsen
     */
    public function getReference()
    {
        return $this->refers;
    }

    /**
     * Returns a string representation of this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Description;

/**
 * Parses a tag definition for a DocBlock.
 */
abstract class BaseTag implements DocBlock\Tag
{
    /** @var string Name of the tag */
    protected $name = '';

    /** @var Description|null Description of the tag. */
    protected $description;

    /**
     * Gets the name of this tag.
     *
     * @return string The name of this tag.
     */
    public function getName()
    {
        return $this->name;
    }

    public function getDescription()
    {
        return $this->description;
    }

    public function render(Formatter $formatter = null)
    {
        if ($formatter === null) {
            $formatter = new Formatter\PassthroughFormatter();
        }

        return $formatter->format($this);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen as FqsenRef;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Reference;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for an {@}see tag in a Docblock.
 */
class See extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'see';

    /** @var Reference */
    protected $refers = null;

    /**
     * Initializes this tag.
     *
     * @param Reference $refers
     * @param Description $description
     */
    public function __construct(Reference $refers, Description $description = null)
    {
        $this->refers = $refers;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        FqsenResolver $resolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::allNotNull([$resolver, $descriptionFactory]);

        $parts       = preg_split('/\s+/Su', $body, 2);
        $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null;

        // https://tools.ietf.org/html/rfc2396#section-3
        if (preg_match('/\w:\/\/\w/i', $parts[0])) {
            return new static(new Url($parts[0]), $description);
        }

        return new static(new FqsenRef($resolver->resolve($parts[0], $context)), $description);
    }

    /**
     * Returns the ref of this tag.
     *
     * @return Reference
     */
    public function getReference()
    {
        return $this->refers;
    }

    /**
     * Returns a string representation of this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Formatter;

use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;

class PassthroughFormatter implements Formatter
{
    /**
     * Formats the given tag to return a simple plain text version.
     *
     * @param Tag $tag
     *
     * @return string
     */
    public function format(Tag $tag)
    {
        return trim('@' . $tag->getName() . ' ' . (string)$tag);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @author    Jan Schneider <jan@horde.org>
 * @copyright 2017 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Formatter;

use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;

class AlignFormatter implements Formatter
{
    /** @var int The maximum tag name length. */
    protected $maxLen = 0;

    /**
     * Constructor.
     *
     * @param Tag[] $tags All tags that should later be aligned with the formatter.
     */
    public function __construct(array $tags)
    {
        foreach ($tags as $tag) {
            $this->maxLen = max($this->maxLen, strlen($tag->getName()));
        }
    }

    /**
     * Formats the given tag to return a simple plain text version.
     *
     * @param Tag $tag
     *
     * @return string
     */
    public function format(Tag $tag)
    {
        return '@' . $tag->getName() . str_repeat(' ', $this->maxLen - strlen($tag->getName()) + 1) . (string)$tag;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}property tag in a Docblock.
 */
class Property extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'property';

    /** @var Type */
    private $type;

    /** @var string */
    protected $variableName = '';

    /**
     * @param string      $variableName
     * @param Type        $type
     * @param Description $description
     */
    public function __construct($variableName, Type $type = null, Description $description = null)
    {
        Assert::string($variableName);

        $this->variableName = $variableName;
        $this->type = $type;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
        $type = null;
        $variableName = '';

        // if the first item that is encountered is not a variable; it is a type
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
            $type = $typeResolver->resolve(array_shift($parts), $context);
            array_shift($parts);
        }

        // if the next item starts with a $ or ...$ it must be the variable name
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) {
            $variableName = array_shift($parts);
            array_shift($parts);

            if (substr($variableName, 0, 1) === '$') {
                $variableName = substr($variableName, 1);
            }
        }

        $description = $descriptionFactory->create(implode('', $parts), $context);

        return new static($variableName, $type, $description);
    }

    /**
     * Returns the variable's name.
     *
     * @return string
     */
    public function getVariableName()
    {
        return $this->variableName;
    }

    /**
     * Returns the variable's type or null if unknown.
     *
     * @return Type|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->type ? $this->type . ' ' : '')
        . '$' . $this->variableName
        . ($this->description ? ' ' . $this->description : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Tag;

interface Formatter
{
    /**
     * Formats a tag into a string representation according to a specific format, such as Markdown.
     *
     * @param Tag $tag
     *
     * @return string
     */
    public function format(Tag $tag);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for the {@}param tag in a Docblock.
 */
final class Param extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'param';

    /** @var Type */
    private $type;

    /** @var string */
    private $variableName = '';

    /** @var bool determines whether this is a variadic argument */
    private $isVariadic = false;

    /**
     * @param string $variableName
     * @param Type $type
     * @param bool $isVariadic
     * @param Description $description
     */
    public function __construct($variableName, Type $type = null, $isVariadic = false, Description $description = null)
    {
        Assert::string($variableName);
        Assert::boolean($isVariadic);

        $this->variableName = $variableName;
        $this->type = $type;
        $this->isVariadic = $isVariadic;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
        $type = null;
        $variableName = '';
        $isVariadic = false;

        // if the first item that is encountered is not a variable; it is a type
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
            $type = $typeResolver->resolve(array_shift($parts), $context);
            array_shift($parts);
        }

        // if the next item starts with a $ or ...$ it must be the variable name
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$' || substr($parts[0], 0, 4) === '...$')) {
            $variableName = array_shift($parts);
            array_shift($parts);

            if (substr($variableName, 0, 3) === '...') {
                $isVariadic = true;
                $variableName = substr($variableName, 3);
            }

            if (substr($variableName, 0, 1) === '$') {
                $variableName = substr($variableName, 1);
            }
        }

        $description = $descriptionFactory->create(implode('', $parts), $context);

        return new static($variableName, $type, $isVariadic, $description);
    }

    /**
     * Returns the variable's name.
     *
     * @return string
     */
    public function getVariableName()
    {
        return $this->variableName;
    }

    /**
     * Returns the variable's type or null if unknown.
     *
     * @return Type|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns whether this tag is variadic.
     *
     * @return boolean
     */
    public function isVariadic()
    {
        return $this->isVariadic;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->type ? $this->type . ' ' : '')
        . ($this->isVariadic() ? '...' : '')
        . '$' . $this->variableName
        . ($this->description ? ' ' . $this->description : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}return tag in a Docblock.
 */
final class Return_ extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'return';

    /** @var Type */
    private $type;

    public function __construct(Type $type, Description $description = null)
    {
        $this->type = $type;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/\s+/Su', $body, 2);

        $type = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context);
        $description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context);

        return new static($type, $description);
    }

    /**
     * Returns the type section of the variable.
     *
     * @return Type
     */
    public function getType()
    {
        return $this->type;
    }

    public function __toString()
    {
        return $this->type . ' ' . $this->description;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;

interface Strategy
{
    public function create($body);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;

interface StaticMethod
{
    public static function create($body);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}throws tag in a Docblock.
 */
final class Throws extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'throws';

    /** @var Type */
    private $type;

    public function __construct(Type $type, Description $description = null)
    {
        $this->type        = $type;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/\s+/Su', $body, 2);

        $type        = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context);
        $description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context);

        return new static($type, $description);
    }

    /**
     * Returns the type section of the variable.
     *
     * @return Type
     */
    public function getType()
    {
        return $this->type;
    }

    public function __toString()
    {
        return $this->type . ' ' . $this->description;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}source tag in a Docblock.
 */
final class Source extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'source';

    /** @var int The starting line, relative to the structural element's location. */
    private $startingLine = 1;

    /** @var int|null The number of lines, relative to the starting line. NULL means "to the end". */
    private $lineCount = null;

    public function __construct($startingLine, $lineCount = null, Description $description = null)
    {
        Assert::integerish($startingLine);
        Assert::nullOrIntegerish($lineCount);

        $this->startingLine = (int)$startingLine;
        $this->lineCount    = $lineCount !== null ? (int)$lineCount : null;
        $this->description  = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
    {
        Assert::stringNotEmpty($body);
        Assert::notNull($descriptionFactory);

        $startingLine = 1;
        $lineCount    = null;
        $description  = null;

        // Starting line / Number of lines / Description
        if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $body, $matches)) {
            $startingLine = (int)$matches[1];
            if (isset($matches[2]) && $matches[2] !== '') {
                $lineCount = (int)$matches[2];
            }

            $description = $matches[3];
        }

        return new static($startingLine, $lineCount, $descriptionFactory->create($description, $context));
    }

    /**
     * Gets the starting line.
     *
     * @return int The starting line, relative to the structural element's
     *     location.
     */
    public function getStartingLine()
    {
        return $this->startingLine;
    }

    /**
     * Returns the number of lines.
     *
     * @return int|null The number of lines, relative to the starting line. NULL
     *     means "to the end".
     */
    public function getLineCount()
    {
        return $this->lineCount;
    }

    public function __toString()
    {
        return $this->startingLine
        . ($this->lineCount !== null ? ' ' . $this->lineCount : '')
        . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}property-write tag in a Docblock.
 */
class PropertyWrite extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'property-write';

    /** @var Type */
    private $type;

    /** @var string */
    protected $variableName = '';

    /**
     * @param string      $variableName
     * @param Type        $type
     * @param Description $description
     */
    public function __construct($variableName, Type $type = null, Description $description = null)
    {
        Assert::string($variableName);

        $this->variableName = $variableName;
        $this->type = $type;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
        $type = null;
        $variableName = '';

        // if the first item that is encountered is not a variable; it is a type
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
            $type = $typeResolver->resolve(array_shift($parts), $context);
            array_shift($parts);
        }

        // if the next item starts with a $ or ...$ it must be the variable name
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) {
            $variableName = array_shift($parts);
            array_shift($parts);

            if (substr($variableName, 0, 1) === '$') {
                $variableName = substr($variableName, 1);
            }
        }

        $description = $descriptionFactory->create(implode('', $parts), $context);

        return new static($variableName, $type, $description);
    }

    /**
     * Returns the variable's name.
     *
     * @return string
     */
    public function getVariableName()
    {
        return $this->variableName;
    }

    /**
     * Returns the variable's type or null if unknown.
     *
     * @return Type|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->type ? $this->type . ' ' : '')
        . '$' . $this->variableName
        . ($this->description ? ' ' . $this->description : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}property-read tag in a Docblock.
 */
class PropertyRead extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'property-read';

    /** @var Type */
    private $type;

    /** @var string */
    protected $variableName = '';

    /**
     * @param string      $variableName
     * @param Type        $type
     * @param Description $description
     */
    public function __construct($variableName, Type $type = null, Description $description = null)
    {
        Assert::string($variableName);

        $this->variableName = $variableName;
        $this->type = $type;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
        $type = null;
        $variableName = '';

        // if the first item that is encountered is not a variable; it is a type
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
            $type = $typeResolver->resolve(array_shift($parts), $context);
            array_shift($parts);
        }

        // if the next item starts with a $ or ...$ it must be the variable name
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) {
            $variableName = array_shift($parts);
            array_shift($parts);

            if (substr($variableName, 0, 1) === '$') {
                $variableName = substr($variableName, 1);
            }
        }

        $description = $descriptionFactory->create(implode('', $parts), $context);

        return new static($variableName, $type, $description);
    }

    /**
     * Returns the variable's name.
     *
     * @return string
     */
    public function getVariableName()
    {
        return $this->variableName;
    }

    /**
     * Returns the variable's type or null if unknown.
     *
     * @return Type|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->type ? $this->type . ' ' : '')
        . '$' . $this->variableName
        . ($this->description ? ' ' . $this->description : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use Webmozart\Assert\Assert;

/**
 * Reflection class for an {@}author tag in a Docblock.
 */
final class Author extends BaseTag implements Factory\StaticMethod
{
    /** @var string register that this is the author tag. */
    protected $name = 'author';

    /** @var string The name of the author */
    private $authorName = '';

    /** @var string The email of the author */
    private $authorEmail = '';

    /**
     * Initializes this tag with the author name and e-mail.
     *
     * @param string $authorName
     * @param string $authorEmail
     */
    public function __construct($authorName, $authorEmail)
    {
        Assert::string($authorName);
        Assert::string($authorEmail);
        if ($authorEmail && !filter_var($authorEmail, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException('The author tag does not have a valid e-mail address');
        }

        $this->authorName  = $authorName;
        $this->authorEmail = $authorEmail;
    }

    /**
     * Gets the author's name.
     *
     * @return string The author's name.
     */
    public function getAuthorName()
    {
        return $this->authorName;
    }

    /**
     * Returns the author's email.
     *
     * @return string The author's email.
     */
    public function getEmail()
    {
        return $this->authorEmail;
    }

    /**
     * Returns this tag in string form.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->authorName . (strlen($this->authorEmail) ? ' <' . $this->authorEmail . '>' : '');
    }

    /**
     * Attempts to create a new Author object based on †he tag body.
     *
     * @param string $body
     *
     * @return static
     */
    public static function create($body)
    {
        Assert::string($body);

        $splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches);
        if (!$splitTagContent) {
            return null;
        }

        $authorName = trim($matches[1]);
        $email = isset($matches[2]) ? trim($matches[2]) : '';

        return new static($authorName, $email);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tag;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}example tag in a Docblock.
 */
final class Example extends BaseTag
{
    /**
     * @var string Path to a file to use as an example. May also be an absolute URI.
     */
    private $filePath;

    /**
     * @var bool Whether the file path component represents an URI. This determines how the file portion
     *     appears at {@link getContent()}.
     */
    private $isURI = false;

    /**
     * @var int
     */
    private $startingLine;

    /**
     * @var int
     */
    private $lineCount;

    public function __construct($filePath, $isURI, $startingLine, $lineCount, $description)
    {
        Assert::notEmpty($filePath);
        Assert::integer($startingLine);
        Assert::greaterThanEq($startingLine, 0);

        $this->filePath = $filePath;
        $this->startingLine = $startingLine;
        $this->lineCount = $lineCount;
        $this->name = 'example';
        if ($description !== null) {
            $this->description = trim($description);
        }

        $this->isURI = $isURI;
    }

    /**
     * {@inheritdoc}
     */
    public function getContent()
    {
        if (null === $this->description) {
            $filePath = '"' . $this->filePath . '"';
            if ($this->isURI) {
                $filePath = $this->isUriRelative($this->filePath)
                    ? str_replace('%2F', '/', rawurlencode($this->filePath))
                    :$this->filePath;
            }

            return trim($filePath . ' ' . parent::getDescription());
        }

        return $this->description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create($body)
    {
        // File component: File path in quotes or File URI / Source information
        if (! preg_match('/^(?:\"([^\"]+)\"|(\S+))(?:\s+(.*))?$/sux', $body, $matches)) {
            return null;
        }

        $filePath = null;
        $fileUri  = null;
        if ('' !== $matches[1]) {
            $filePath = $matches[1];
        } else {
            $fileUri = $matches[2];
        }

        $startingLine = 1;
        $lineCount    = null;
        $description  = null;

        if (array_key_exists(3, $matches)) {
            $description = $matches[3];

            // Starting line / Number of lines / Description
            if (preg_match('/^([1-9]\d*)(?:\s+((?1))\s*)?(.*)$/sux', $matches[3], $contentMatches)) {
                $startingLine = (int)$contentMatches[1];
                if (isset($contentMatches[2]) && $contentMatches[2] !== '') {
                    $lineCount = (int)$contentMatches[2];
                }

                if (array_key_exists(3, $contentMatches)) {
                    $description = $contentMatches[3];
                }
            }
        }

        return new static(
            $filePath !== null?$filePath:$fileUri,
            $fileUri !== null,
            $startingLine,
            $lineCount,
            $description
        );
    }

    /**
     * Returns the file path.
     *
     * @return string Path to a file to use as an example.
     *     May also be an absolute URI.
     */
    public function getFilePath()
    {
        return $this->filePath;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->filePath . ($this->description ? ' ' . $this->description : '');
    }

    /**
     * Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute).
     *
     * @param string $uri
     *
     * @return bool
     */
    private function isUriRelative($uri)
    {
        return false === strpos($uri, ':');
    }

    /**
     * @return int
     */
    public function getStartingLine()
    {
        return $this->startingLine;
    }

    /**
     * @return int
     */
    public function getLineCount()
    {
        return $this->lineCount;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}since tag in a Docblock.
 */
final class Since extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'since';

    /**
     * PCRE regular expression matching a version vector.
     * Assumes the "x" modifier.
     */
    const REGEX_VECTOR = '(?:
        # Normal release vectors.
        \d\S*
        |
        # VCS version vectors. Per PHPCS, they are expected to
        # follow the form of the VCS name, followed by ":", followed
        # by the version vector itself.
        # By convention, popular VCSes like CVS, SVN and GIT use "$"
        # around the actual version vector.
        [^\s\:]+\:\s*\$[^\$]+\$
    )';

    /** @var string The version vector. */
    private $version = '';

    public function __construct($version = null, Description $description = null)
    {
        Assert::nullOrStringNotEmpty($version);

        $this->version     = $version;
        $this->description = $description;
    }

    /**
     * @return static
     */
    public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
    {
        Assert::nullOrString($body);
        if (empty($body)) {
            return new static();
        }

        $matches = [];
        if (! preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
            return null;
        }

        return new static(
            $matches[1],
            $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context)
        );
    }

    /**
     * Gets the version section of the tag.
     *
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->version . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 *  For the full copyright and license information, please view the LICENSE
 *  file that was distributed with this source code.
 *
 *  @copyright 2010-2017 Mike van Riel<mike@phpdoc.org>
 *  @license   http://www.opensource.org/licenses/mit-license.php MIT
 *  @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Reference;

/**
 * Interface for references in {@see phpDocumentor\Reflection\DocBlock\Tags\See}
 */
interface Reference
{
    public function __toString();
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 *  For the full copyright and license information, please view the LICENSE
 *  file that was distributed with this source code.
 *
 *  @copyright 2010-2017 Mike van Riel<mike@phpdoc.org>
 *  @license   http://www.opensource.org/licenses/mit-license.php MIT
 *  @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Reference;

use phpDocumentor\Reflection\Fqsen as RealFqsen;

/**
 * Fqsen reference used by {@see phpDocumentor\Reflection\DocBlock\Tags\See}
 */
final class Fqsen implements Reference
{
    /**
     * @var RealFqsen
     */
    private $fqsen;

    /**
     * Fqsen constructor.
     */
    public function __construct(RealFqsen $fqsen)
    {
        $this->fqsen = $fqsen;
    }

    /**
     * @return string string representation of the referenced fqsen
     */
    public function __toString()
    {
        return (string)$this->fqsen;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 *  For the full copyright and license information, please view the LICENSE
 *  file that was distributed with this source code.
 *
 *  @copyright 2010-2017 Mike van Riel<mike@phpdoc.org>
 *  @license   http://www.opensource.org/licenses/mit-license.php MIT
 *  @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Reference;

use Webmozart\Assert\Assert;

/**
 * Url reference used by {@see phpDocumentor\Reflection\DocBlock\Tags\See}
 */
final class Url implements Reference
{
    /**
     * @var string
     */
    private $uri;

    /**
     * Url constructor.
     */
    public function __construct($uri)
    {
        Assert::stringNotEmpty($uri);
        $this->uri = $uri;
    }

    public function __toString()
    {
        return $this->uri;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Parses a tag definition for a DocBlock.
 */
class Generic extends BaseTag implements Factory\StaticMethod
{
    /**
     * Parses a tag and populates the member variables.
     *
     * @param string $name Name of the tag.
     * @param Description $description The contents of the given tag.
     */
    public function __construct($name, Description $description = null)
    {
        $this->validateTagName($name);

        $this->name = $name;
        $this->description = $description;
    }

    /**
     * Creates a new tag that represents any unknown tag type.
     *
     * @param string             $body
     * @param string             $name
     * @param DescriptionFactory $descriptionFactory
     * @param TypeContext        $context
     *
     * @return static
     */
    public static function create(
        $body,
        $name = '',
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::stringNotEmpty($name);
        Assert::notNull($descriptionFactory);

        $description = $descriptionFactory && $body ? $descriptionFactory->create($body, $context) : null;

        return new static($name, $description);
    }

    /**
     * Returns the tag as a serialized string
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->description ? $this->description->render() : '');
    }

    /**
     * Validates if the tag name matches the expected format, otherwise throws an exception.
     *
     * @param string $name
     *
     * @return void
     */
    private function validateTagName($name)
    {
        if (! preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) {
            throw new \InvalidArgumentException(
                'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, '
                . 'hyphens and backslashes.'
            );
        }
    }
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.3
 *
 * @author    Ben Selby <benmatselby@gmail.com>
 * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a @link tag in a Docblock.
 */
final class Link extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'link';

    /** @var string */
    private $link = '';

    /**
     * Initializes a link to a URL.
     *
     * @param string      $link
     * @param Description $description
     */
    public function __construct($link, Description $description = null)
    {
        Assert::string($link);

        $this->link = $link;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
    {
        Assert::string($body);
        Assert::notNull($descriptionFactory);

        $parts = preg_split('/\s+/Su', $body, 2);
        $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null;

        return new static($parts[0], $description);
    }

    /**
    * Gets the link
    *
    * @return string
    */
    public function getLink()
    {
        return $this->link;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->link . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}var tag in a Docblock.
 */
class Var_ extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'var';

    /** @var Type */
    private $type;

    /** @var string */
    protected $variableName = '';

    /**
     * @param string      $variableName
     * @param Type        $type
     * @param Description $description
     */
    public function __construct($variableName, Type $type = null, Description $description = null)
    {
        Assert::string($variableName);

        $this->variableName = $variableName;
        $this->type         = $type;
        $this->description  = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts        = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
        $type         = null;
        $variableName = '';

        // if the first item that is encountered is not a variable; it is a type
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
            $type = $typeResolver->resolve(array_shift($parts), $context);
            array_shift($parts);
        }

        // if the next item starts with a $ or ...$ it must be the variable name
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) {
            $variableName = array_shift($parts);
            array_shift($parts);

            if (substr($variableName, 0, 1) === '$') {
                $variableName = substr($variableName, 1);
            }
        }

        $description = $descriptionFactory->create(implode('', $parts), $context);

        return new static($variableName, $type, $description);
    }

    /**
     * Returns the variable's name.
     *
     * @return string
     */
    public function getVariableName()
    {
        return $this->variableName;
    }

    /**
     * Returns the variable's type or null if unknown.
     *
     * @return Type|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->type ? $this->type . ' ' : '')
            . (empty($this->variableName) ? null : ('$' . $this->variableName))
            . ($this->description ? ' ' . $this->description : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}deprecated tag in a Docblock.
 */
final class Deprecated extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'deprecated';

    /**
     * PCRE regular expression matching a version vector.
     * Assumes the "x" modifier.
     */
    const REGEX_VECTOR = '(?:
        # Normal release vectors.
        \d\S*
        |
        # VCS version vectors. Per PHPCS, they are expected to
        # follow the form of the VCS name, followed by ":", followed
        # by the version vector itself.
        # By convention, popular VCSes like CVS, SVN and GIT use "$"
        # around the actual version vector.
        [^\s\:]+\:\s*\$[^\$]+\$
    )';

    /** @var string The version vector. */
    private $version = '';

    public function __construct($version = null, Description $description = null)
    {
        Assert::nullOrStringNotEmpty($version);

        $this->version = $version;
        $this->description = $description;
    }

    /**
     * @return static
     */
    public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
    {
        Assert::nullOrString($body);
        if (empty($body)) {
            return new static();
        }

        $matches = [];
        if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
            return new static(
                null,
                null !== $descriptionFactory ? $descriptionFactory->create($body, $context) : null
            );
        }

        return new static(
            $matches[1],
            $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context)
        );
    }

    /**
     * Gets the version section of the tag.
     *
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->version . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Types\Void_;
use Webmozart\Assert\Assert;

/**
 * Reflection class for an {@}method in a Docblock.
 */
final class Method extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'method';

    /** @var string */
    private $methodName = '';

    /** @var string[] */
    private $arguments = [];

    /** @var bool */
    private $isStatic = false;

    /** @var Type */
    private $returnType;

    public function __construct(
        $methodName,
        array $arguments = [],
        Type $returnType = null,
        $static = false,
        Description $description = null
    ) {
        Assert::stringNotEmpty($methodName);
        Assert::boolean($static);

        if ($returnType === null) {
            $returnType = new Void_();
        }

        $this->methodName  = $methodName;
        $this->arguments   = $this->filterArguments($arguments);
        $this->returnType  = $returnType;
        $this->isStatic    = $static;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([ $typeResolver, $descriptionFactory ]);

        // 1. none or more whitespace
        // 2. optionally the keyword "static" followed by whitespace
        // 3. optionally a word with underscores followed by whitespace : as
        //    type for the return value
        // 4. then optionally a word with underscores followed by () and
        //    whitespace : as method name as used by phpDocumentor
        // 5. then a word with underscores, followed by ( and any character
        //    until a ) and whitespace : as method name with signature
        // 6. any remaining text : as description
        if (!preg_match(
            '/^
                # Static keyword
                # Declares a static method ONLY if type is also present
                (?:
                    (static)
                    \s+
                )?
                # Return type
                (?:
                    (   
                        (?:[\w\|_\\\\]*\$this[\w\|_\\\\]*)
                        |
                        (?:
                            (?:[\w\|_\\\\]+)
                            # array notation           
                            (?:\[\])*
                        )*
                    )
                    \s+
                )?
                # Legacy method name (not captured)
                (?:
                    [\w_]+\(\)\s+
                )?
                # Method name
                ([\w\|_\\\\]+)
                # Arguments
                (?:
                    \(([^\)]*)\)
                )?
                \s*
                # Description
                (.*)
            $/sux',
            $body,
            $matches
        )) {
            return null;
        }

        list(, $static, $returnType, $methodName, $arguments, $description) = $matches;

        $static      = $static === 'static';

        if ($returnType === '') {
            $returnType = 'void';
        }

        $returnType  = $typeResolver->resolve($returnType, $context);
        $description = $descriptionFactory->create($description, $context);

        if (is_string($arguments) && strlen($arguments) > 0) {
            $arguments = explode(',', $arguments);
            foreach ($arguments as &$argument) {
                $argument = explode(' ', self::stripRestArg(trim($argument)), 2);
                if ($argument[0][0] === '$') {
                    $argumentName = substr($argument[0], 1);
                    $argumentType = new Void_();
                } else {
                    $argumentType = $typeResolver->resolve($argument[0], $context);
                    $argumentName = '';
                    if (isset($argument[1])) {
                        $argument[1] = self::stripRestArg($argument[1]);
                        $argumentName = substr($argument[1], 1);
                    }
                }

                $argument = [ 'name' => $argumentName, 'type' => $argumentType];
            }
        } else {
            $arguments = [];
        }

        return new static($methodName, $arguments, $returnType, $static, $description);
    }

    /**
     * Retrieves the method name.
     *
     * @return string
     */
    public function getMethodName()
    {
        return $this->methodName;
    }

    /**
     * @return string[]
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * Checks whether the method tag describes a static method or not.
     *
     * @return bool TRUE if the method declaration is for a static method, FALSE otherwise.
     */
    public function isStatic()
    {
        return $this->isStatic;
    }

    /**
     * @return Type
     */
    public function getReturnType()
    {
        return $this->returnType;
    }

    public function __toString()
    {
        $arguments = [];
        foreach ($this->arguments as $argument) {
            $arguments[] = $argument['type'] . ' $' . $argument['name'];
        }

        return trim(($this->isStatic() ? 'static ' : '')
            . (string)$this->returnType . ' '
            . $this->methodName
            . '(' . implode(', ', $arguments) . ')'
            . ($this->description ? ' ' . $this->description->render() : ''));
    }

    private function filterArguments($arguments)
    {
        foreach ($arguments as &$argument) {
            if (is_string($argument)) {
                $argument = [ 'name' => $argument ];
            }

            if (! isset($argument['type'])) {
                $argument['type'] = new Void_();
            }

            $keys = array_keys($argument);
            sort($keys);
            if ($keys !== [ 'name', 'type' ]) {
                throw new \InvalidArgumentException(
                    'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true)
                );
            }
        }

        return $arguments;
    }

    private static function stripRestArg($argument)
    {
        if (strpos($argument, '...') === 0) {
            $argument = trim(substr($argument, 3));
        }

        return $argument;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}uses tag in a Docblock.
 */
final class Uses extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'uses';

    /** @var Fqsen */
    protected $refers = null;

    /**
     * Initializes this tag.
     *
     * @param Fqsen       $refers
     * @param Description $description
     */
    public function __construct(Fqsen $refers, Description $description = null)
    {
        $this->refers      = $refers;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        FqsenResolver $resolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::allNotNull([$resolver, $descriptionFactory]);

        $parts = preg_split('/\s+/Su', $body, 2);

        return new static(
            $resolver->resolve($parts[0], $context),
            $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context)
        );
    }

    /**
     * Returns the structural element this tag refers to.
     *
     * @return Fqsen
     */
    public function getReference()
    {
        return $this->refers;
    }

    /**
     * Returns a string representation of this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->refers . ' ' . $this->description->render();
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\Types\Context as TypeContext;

/**
 * Creates a new Description object given a body of text.
 *
 * Descriptions in phpDocumentor are somewhat complex entities as they can contain one or more tags inside their
 * body that can be replaced with a readable output. The replacing is done by passing a Formatter object to the
 * Description object's `render` method.
 *
 * In addition to the above does a Description support two types of escape sequences:
 *
 * 1. `{@}` to escape the `@` character to prevent it from being interpreted as part of a tag, i.e. `{{@}link}`
 * 2. `{}` to escape the `}` character, this can be used if you want to use the `}` character in the description
 *    of an inline tag.
 *
 * If a body consists of multiple lines then this factory will also remove any superfluous whitespace at the beginning
 * of each line while maintaining any indentation that is used. This will prevent formatting parsers from tripping
 * over unexpected spaces as can be observed with tag descriptions.
 */
class DescriptionFactory
{
    /** @var TagFactory */
    private $tagFactory;

    /**
     * Initializes this factory with the means to construct (inline) tags.
     *
     * @param TagFactory $tagFactory
     */
    public function __construct(TagFactory $tagFactory)
    {
        $this->tagFactory = $tagFactory;
    }

    /**
     * Returns the parsed text of this description.
     *
     * @param string $contents
     * @param TypeContext $context
     *
     * @return Description
     */
    public function create($contents, TypeContext $context = null)
    {
        list($text, $tags) = $this->parse($this->lex($contents), $context);

        return new Description($text, $tags);
    }

    /**
     * Strips the contents from superfluous whitespace and splits the description into a series of tokens.
     *
     * @param string $contents
     *
     * @return string[] A series of tokens of which the description text is composed.
     */
    private function lex($contents)
    {
        $contents = $this->removeSuperfluousStartingWhitespace($contents);

        // performance optimalization; if there is no inline tag, don't bother splitting it up.
        if (strpos($contents, '{@') === false) {
            return [$contents];
        }

        return preg_split(
            '/\{
                # "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally.
                (?!@\})
                # We want to capture the whole tag line, but without the inline tag delimiters.
                (\@
                    # Match everything up to the next delimiter.
                    [^{}]*
                    # Nested inline tag content should not be captured, or it will appear in the result separately.
                    (?:
                        # Match nested inline tags.
                        (?:
                            # Because we did not catch the tag delimiters earlier, we must be explicit with them here.
                            # Notice that this also matches "{}", as a way to later introduce it as an escape sequence.
                            \{(?1)?\}
                            |
                            # Make sure we match hanging "{".
                            \{
                        )
                        # Match content after the nested inline tag.
                        [^{}]*
                    )* # If there are more inline tags, match them as well. We use "*" since there may not be any
                       # nested inline tags.
                )
            \}/Sux',
            $contents,
            null,
            PREG_SPLIT_DELIM_CAPTURE
        );
    }

    /**
     * Parses the stream of tokens in to a new set of tokens containing Tags.
     *
     * @param string[] $tokens
     * @param TypeContext $context
     *
     * @return string[]|Tag[]
     */
    private function parse($tokens, TypeContext $context)
    {
        $count = count($tokens);
        $tagCount = 0;
        $tags  = [];

        for ($i = 1; $i < $count; $i += 2) {
            $tags[] = $this->tagFactory->create($tokens[$i], $context);
            $tokens[$i] = '%' . ++$tagCount . '$s';
        }

        //In order to allow "literal" inline tags, the otherwise invalid
        //sequence "{@}" is changed to "@", and "{}" is changed to "}".
        //"%" is escaped to "%%" because of vsprintf.
        //See unit tests for examples.
        for ($i = 0; $i < $count; $i += 2) {
            $tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]);
        }

        return [implode('', $tokens), $tags];
    }

    /**
     * Removes the superfluous from a multi-line description.
     *
     * When a description has more than one line then it can happen that the second and subsequent lines have an
     * additional indentation. This is commonly in use with tags like this:
     *
     *     {@}since 1.1.0 This is an example
     *         description where we have an
     *         indentation in the second and
     *         subsequent lines.
     *
     * If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent
     * lines and this may cause rendering issues when, for example, using a Markdown converter.
     *
     * @param string $contents
     *
     * @return string
     */
    private function removeSuperfluousStartingWhitespace($contents)
    {
        $lines = explode("\n", $contents);

        // if there is only one line then we don't have lines with superfluous whitespace and
        // can use the contents as-is
        if (count($lines) <= 1) {
            return $contents;
        }

        // determine how many whitespace characters need to be stripped
        $startingSpaceCount = 9999999;
        for ($i = 1; $i < count($lines); $i++) {
            // lines with a no length do not count as they are not indented at all
            if (strlen(trim($lines[$i])) === 0) {
                continue;
            }

            // determine the number of prefixing spaces by checking the difference in line length before and after
            // an ltrim
            $startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i])));
        }

        // strip the number of spaces from each line
        if ($startingSpaceCount > 0) {
            for ($i = 1; $i < count($lines); $i++) {
                $lines[$i] = substr($lines[$i], $startingSpaceCount);
            }
        }

        return implode("\n", $lines);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\Types\Context as TypeContext;

interface TagFactory
{
    /**
     * Adds a parameter to the service locator that can be injected in a tag's factory method.
     *
     * When calling a tag's "create" method we always check the signature for dependencies to inject. One way is to
     * typehint a parameter in the signature so that we can use that interface or class name to inject a dependency
     * (see {@see addService()} for more information on that).
     *
     * Another way is to check the name of the argument against the names in the Service Locator. With this method
     * you can add a variable that will be inserted when a tag's create method is not typehinted and has a matching
     * name.
     *
     * Be aware that there are two reserved names:
     *
     * - name, representing the name of the tag.
     * - body, representing the complete body of the tag.
     *
     * These parameters are injected at the last moment and will override any existing parameter with those names.
     *
     * @param string $name
     * @param mixed  $value
     *
     * @return void
     */
    public function addParameter($name, $value);

    /**
     * Registers a service with the Service Locator using the FQCN of the class or the alias, if provided.
     *
     * When calling a tag's "create" method we always check the signature for dependencies to inject. If a parameter
     * has a typehint then the ServiceLocator is queried to see if a Service is registered for that typehint.
     *
     * Because interfaces are regularly used as type-hints this method provides an alias parameter; if the FQCN of the
     * interface is passed as alias then every time that interface is requested the provided service will be returned.
     *
     * @param object $service
     * @param string $alias
     *
     * @return void
     */
    public function addService($service);

    /**
     * Factory method responsible for instantiating the correct sub type.
     *
     * @param string $tagLine The text for this tag, including description.
     * @param TypeContext $context
     *
     * @throws \InvalidArgumentException if an invalid tag line was presented.
     *
     * @return Tag A new tag object.
     */
    public function create($tagLine, TypeContext $context = null);

    /**
     * Registers a handler for tags.
     *
     * If you want to use your own tags then you can use this method to instruct the TagFactory to register the name
     * of a tag with the FQCN of a 'Tag Handler'. The Tag handler should implement the {@see Tag} interface (and thus
     * the create method).
     *
     * @param string $tagName Name of tag to register a handler for. When registering a namespaced tag, the full
     *                        name, along with a prefixing slash MUST be provided.
     * @param string $handler FQCN of handler.
     *
     * @throws \InvalidArgumentException if the tag name is not a string
     * @throws \InvalidArgumentException if the tag name is namespaced (contains backslashes) but does not start with
     *     a backslash
     * @throws \InvalidArgumentException if the handler is not a string
     * @throws \InvalidArgumentException if the handler is not an existing class
     * @throws \InvalidArgumentException if the handler does not implement the {@see Tag} interface
     *
     * @return void
     */
    public function registerTagHandler($tagName, $handler);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock\Tags\Example;

/**
 * Class used to find an example file's location based on a given ExampleDescriptor.
 */
class ExampleFinder
{
    /** @var string */
    private $sourceDirectory = '';

    /** @var string[] */
    private $exampleDirectories = [];

    /**
     * Attempts to find the example contents for the given descriptor.
     *
     * @param Example $example
     *
     * @return string
     */
    public function find(Example $example)
    {
        $filename = $example->getFilePath();

        $file = $this->getExampleFileContents($filename);
        if (!$file) {
            return "** File not found : {$filename} **";
        }

        return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount()));
    }

    /**
     * Registers the project's root directory where an 'examples' folder can be expected.
     *
     * @param string $directory
     *
     * @return void
     */
    public function setSourceDirectory($directory = '')
    {
        $this->sourceDirectory = $directory;
    }

    /**
     * Returns the project's root directory where an 'examples' folder can be expected.
     *
     * @return string
     */
    public function getSourceDirectory()
    {
        return $this->sourceDirectory;
    }

    /**
     * Registers a series of directories that may contain examples.
     *
     * @param string[] $directories
     */
    public function setExampleDirectories(array $directories)
    {
        $this->exampleDirectories = $directories;
    }

    /**
     * Returns a series of directories that may contain examples.
     *
     * @return string[]
     */
    public function getExampleDirectories()
    {
        return $this->exampleDirectories;
    }

    /**
     * Attempts to find the requested example file and returns its contents or null if no file was found.
     *
     * This method will try several methods in search of the given example file, the first one it encounters is
     * returned:
     *
     * 1. Iterates through all examples folders for the given filename
     * 2. Checks the source folder for the given filename
     * 3. Checks the 'examples' folder in the current working directory for examples
     * 4. Checks the path relative to the current working directory for the given filename
     *
     * @param string $filename
     *
     * @return string|null
     */
    private function getExampleFileContents($filename)
    {
        $normalizedPath = null;

        foreach ($this->exampleDirectories as $directory) {
            $exampleFileFromConfig = $this->constructExamplePath($directory, $filename);
            if (is_readable($exampleFileFromConfig)) {
                $normalizedPath = $exampleFileFromConfig;
                break;
            }
        }

        if (!$normalizedPath) {
            if (is_readable($this->getExamplePathFromSource($filename))) {
                $normalizedPath = $this->getExamplePathFromSource($filename);
            } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) {
                $normalizedPath = $this->getExamplePathFromExampleDirectory($filename);
            } elseif (is_readable($filename)) {
                $normalizedPath = $filename;
            }
        }

        return $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : null;
    }

    /**
     * Get example filepath based on the example directory inside your project.
     *
     * @param string $file
     *
     * @return string
     */
    private function getExamplePathFromExampleDirectory($file)
    {
        return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file;
    }

    /**
     * Returns a path to the example file in the given directory..
     *
     * @param string $directory
     * @param string $file
     *
     * @return string
     */
    private function constructExamplePath($directory, $file)
    {
        return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file;
    }

    /**
     * Get example filepath based on sourcecode.
     *
     * @param string $file
     *
     * @return string
     */
    private function getExamplePathFromSource($file)
    {
        return sprintf(
            '%s%s%s',
            trim($this->getSourceDirectory(), '\\/'),
            DIRECTORY_SEPARATOR,
            trim($file, '"')
        );
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter;
use Webmozart\Assert\Assert;

/**
 * Object representing to description for a DocBlock.
 *
 * A Description object can consist of plain text but can also include tags. A Description Formatter can then combine
 * a body template with sprintf-style placeholders together with formatted tags in order to reconstitute a complete
 * description text using the format that you would prefer.
 *
 * Because parsing a Description text can be a verbose process this is handled by the {@see DescriptionFactory}. It is
 * thus recommended to use that to create a Description object, like this:
 *
 *     $description = $descriptionFactory->create('This is a {@see Description}', $context);
 *
 * The description factory will interpret the given body and create a body template and list of tags from them, and pass
 * that onto the constructor if this class.
 *
 * > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace
 * > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial
 * > type names and FQSENs.
 *
 * If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this:
 *
 *     $description = new Description(
 *         'This is a %1$s',
 *         [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ]
 *     );
 *
 * It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object
 * is mainly responsible for rendering.
 *
 * @see DescriptionFactory to create a new Description.
 * @see Description\Formatter for the formatting of the body and tags.
 */
class Description
{
    /** @var string */
    private $bodyTemplate;

    /** @var Tag[] */
    private $tags;

    /**
     * Initializes a Description with its body (template) and a listing of the tags used in the body template.
     *
     * @param string $bodyTemplate
     * @param Tag[] $tags
     */
    public function __construct($bodyTemplate, array $tags = [])
    {
        Assert::string($bodyTemplate);

        $this->bodyTemplate = $bodyTemplate;
        $this->tags = $tags;
    }

    /**
     * Returns the tags for this DocBlock.
     *
     * @return Tag[]
     */
    public function getTags()
    {
        return $this->tags;
    }

    /**
     * Renders this description as a string where the provided formatter will format the tags in the expected string
     * format.
     *
     * @param Formatter|null $formatter
     *
     * @return string
     */
    public function render(Formatter $formatter = null)
    {
        if ($formatter === null) {
            $formatter = new PassthroughFormatter();
        }

        $tags = [];
        foreach ($this->tags as $tag) {
            $tags[] = '{' . $formatter->format($tag) . '}';
        }

        return vsprintf($this->bodyTemplate, $tags);
    }

    /**
     * Returns a plain string representation of this description.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock;
use Webmozart\Assert\Assert;

/**
 * Converts a DocBlock back from an object to a complete DocComment including Asterisks.
 */
class Serializer
{
    /** @var string The string to indent the comment with. */
    protected $indentString = ' ';

    /** @var int The number of times the indent string is repeated. */
    protected $indent = 0;

    /** @var bool Whether to indent the first line with the given indent amount and string. */
    protected $isFirstLineIndented = true;

    /** @var int|null The max length of a line. */
    protected $lineLength = null;

    /** @var DocBlock\Tags\Formatter A custom tag formatter. */
    protected $tagFormatter = null;

    /**
     * Create a Serializer instance.
     *
     * @param int $indent The number of times the indent string is repeated.
     * @param string   $indentString    The string to indent the comment with.
     * @param bool     $indentFirstLine Whether to indent the first line.
     * @param int|null $lineLength The max length of a line or NULL to disable line wrapping.
     * @param DocBlock\Tags\Formatter $tagFormatter A custom tag formatter, defaults to PassthroughFormatter.
     */
    public function __construct($indent = 0, $indentString = ' ', $indentFirstLine = true, $lineLength = null, $tagFormatter = null)
    {
        Assert::integer($indent);
        Assert::string($indentString);
        Assert::boolean($indentFirstLine);
        Assert::nullOrInteger($lineLength);
        Assert::nullOrIsInstanceOf($tagFormatter, 'phpDocumentor\Reflection\DocBlock\Tags\Formatter');

        $this->indent = $indent;
        $this->indentString = $indentString;
        $this->isFirstLineIndented = $indentFirstLine;
        $this->lineLength = $lineLength;
        $this->tagFormatter = $tagFormatter ?: new DocBlock\Tags\Formatter\PassthroughFormatter();
    }

    /**
     * Generate a DocBlock comment.
     *
     * @param DocBlock $docblock The DocBlock to serialize.
     *
     * @return string The serialized doc block.
     */
    public function getDocComment(DocBlock $docblock)
    {
        $indent = str_repeat($this->indentString, $this->indent);
        $firstIndent = $this->isFirstLineIndented ? $indent : '';
        // 3 === strlen(' * ')
        $wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null;

        $text = $this->removeTrailingSpaces(
            $indent,
            $this->addAsterisksForEachLine(
                $indent,
                $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength)
            )
        );

        $comment = "{$firstIndent}/**\n";
        if ($text) {
            $comment .= "{$indent} * {$text}\n";
            $comment .= "{$indent} *\n";
        }

        $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment);
        $comment .= $indent . ' */';

        return $comment;
    }

    /**
     * @param $indent
     * @param $text
     * @return mixed
     */
    private function removeTrailingSpaces($indent, $text)
    {
        return str_replace("\n{$indent} * \n", "\n{$indent} *\n", $text);
    }

    /**
     * @param $indent
     * @param $text
     * @return mixed
     */
    private function addAsterisksForEachLine($indent, $text)
    {
        return str_replace("\n", "\n{$indent} * ", $text);
    }

    /**
     * @param DocBlock $docblock
     * @param $wrapLength
     * @return string
     */
    private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, $wrapLength)
    {
        $text = $docblock->getSummary() . ((string)$docblock->getDescription() ? "\n\n" . $docblock->getDescription()
                : '');
        if ($wrapLength !== null) {
            $text = wordwrap($text, $wrapLength);
            return $text;
        }

        return $text;
    }

    /**
     * @param DocBlock $docblock
     * @param $wrapLength
     * @param $indent
     * @param $comment
     * @return string
     */
    private function addTagBlock(DocBlock $docblock, $wrapLength, $indent, $comment)
    {
        foreach ($docblock->getTags() as $tag) {
            $tagText = $this->tagFormatter->format($tag);
            if ($wrapLength !== null) {
                $tagText = wordwrap($tagText, $wrapLength);
            }

            $tagText = str_replace("\n", "\n{$indent} * ", $tagText);

            $comment .= "{$indent} * {$tagText}\n";
        }

        return $comment;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock\Tags\Factory\StaticMethod;
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Creates a Tag object given the contents of a tag.
 *
 * This Factory is capable of determining the appropriate class for a tag and instantiate it using its `create`
 * factory method. The `create` factory method of a Tag can have a variable number of arguments; this way you can
 * pass the dependencies that you need to construct a tag object.
 *
 * > Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise
 * > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to
 * > verify that a dependency is actually passed.
 *
 * This Factory also features a Service Locator component that is used to pass the right dependencies to the
 * `create` method of a tag; each dependency should be registered as a service or as a parameter.
 *
 * When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass
 * the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface.
 */
final class StandardTagFactory implements TagFactory
{
    /** PCRE regular expression matching a tag name. */
    const REGEX_TAGNAME = '[\w\-\_\\\\]+';

    /**
     * @var string[] An array with a tag as a key, and an FQCN to a class that handles it as an array value.
     */
    private $tagHandlerMappings = [
        'author'         => '\phpDocumentor\Reflection\DocBlock\Tags\Author',
        'covers'         => '\phpDocumentor\Reflection\DocBlock\Tags\Covers',
        'deprecated'     => '\phpDocumentor\Reflection\DocBlock\Tags\Deprecated',
        // 'example'        => '\phpDocumentor\Reflection\DocBlock\Tags\Example',
        'link'           => '\phpDocumentor\Reflection\DocBlock\Tags\Link',
        'method'         => '\phpDocumentor\Reflection\DocBlock\Tags\Method',
        'param'          => '\phpDocumentor\Reflection\DocBlock\Tags\Param',
        'property-read'  => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyRead',
        'property'       => '\phpDocumentor\Reflection\DocBlock\Tags\Property',
        'property-write' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite',
        'return'         => '\phpDocumentor\Reflection\DocBlock\Tags\Return_',
        'see'            => '\phpDocumentor\Reflection\DocBlock\Tags\See',
        'since'          => '\phpDocumentor\Reflection\DocBlock\Tags\Since',
        'source'         => '\phpDocumentor\Reflection\DocBlock\Tags\Source',
        'throw'          => '\phpDocumentor\Reflection\DocBlock\Tags\Throws',
        'throws'         => '\phpDocumentor\Reflection\DocBlock\Tags\Throws',
        'uses'           => '\phpDocumentor\Reflection\DocBlock\Tags\Uses',
        'var'            => '\phpDocumentor\Reflection\DocBlock\Tags\Var_',
        'version'        => '\phpDocumentor\Reflection\DocBlock\Tags\Version'
    ];

    /**
     * @var \ReflectionParameter[][] a lazy-loading cache containing parameters for each tagHandler that has been used.
     */
    private $tagHandlerParameterCache = [];

    /**
     * @var FqsenResolver
     */
    private $fqsenResolver;

    /**
     * @var mixed[] an array representing a simple Service Locator where we can store parameters and
     *     services that can be inserted into the Factory Methods of Tag Handlers.
     */
    private $serviceLocator = [];

    /**
     * Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers.
     *
     * If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property
     * is used.
     *
     * @param FqsenResolver $fqsenResolver
     * @param string[]      $tagHandlers
     *
     * @see self::registerTagHandler() to add a new tag handler to the existing default list.
     */
    public function __construct(FqsenResolver $fqsenResolver, array $tagHandlers = null)
    {
        $this->fqsenResolver = $fqsenResolver;
        if ($tagHandlers !== null) {
            $this->tagHandlerMappings = $tagHandlers;
        }

        $this->addService($fqsenResolver, FqsenResolver::class);
    }

    /**
     * {@inheritDoc}
     */
    public function create($tagLine, TypeContext $context = null)
    {
        if (! $context) {
            $context = new TypeContext('');
        }

        list($tagName, $tagBody) = $this->extractTagParts($tagLine);

        if ($tagBody !== '' && $tagBody[0] === '[') {
            throw new \InvalidArgumentException(
                'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors'
            );
        }

        return $this->createTag($tagBody, $tagName, $context);
    }

    /**
     * {@inheritDoc}
     */
    public function addParameter($name, $value)
    {
        $this->serviceLocator[$name] = $value;
    }

    /**
     * {@inheritDoc}
     */
    public function addService($service, $alias = null)
    {
        $this->serviceLocator[$alias ?: get_class($service)] = $service;
    }

    /**
     * {@inheritDoc}
     */
    public function registerTagHandler($tagName, $handler)
    {
        Assert::stringNotEmpty($tagName);
        Assert::stringNotEmpty($handler);
        Assert::classExists($handler);
        Assert::implementsInterface($handler, StaticMethod::class);

        if (strpos($tagName, '\\') && $tagName[0] !== '\\') {
            throw new \InvalidArgumentException(
                'A namespaced tag must have a leading backslash as it must be fully qualified'
            );
        }

        $this->tagHandlerMappings[$tagName] = $handler;
    }

    /**
     * Extracts all components for a tag.
     *
     * @param string $tagLine
     *
     * @return string[]
     */
    private function extractTagParts($tagLine)
    {
        $matches = [];
        if (! preg_match('/^@(' . self::REGEX_TAGNAME . ')(?:\s*([^\s].*)|$)/us', $tagLine, $matches)) {
            throw new \InvalidArgumentException(
                'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors'
            );
        }

        if (count($matches) < 3) {
            $matches[] = '';
        }

        return array_slice($matches, 1);
    }

    /**
     * Creates a new tag object with the given name and body or returns null if the tag name was recognized but the
     * body was invalid.
     *
     * @param string  $body
     * @param string  $name
     * @param TypeContext $context
     *
     * @return Tag|null
     */
    private function createTag($body, $name, TypeContext $context)
    {
        $handlerClassName = $this->findHandlerClassName($name, $context);
        $arguments        = $this->getArgumentsForParametersFromWiring(
            $this->fetchParametersForHandlerFactoryMethod($handlerClassName),
            $this->getServiceLocatorWithDynamicParameters($context, $name, $body)
        );

        return call_user_func_array([$handlerClassName, 'create'], $arguments);
    }

    /**
     * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
     *
     * @param string  $tagName
     * @param TypeContext $context
     *
     * @return string
     */
    private function findHandlerClassName($tagName, TypeContext $context)
    {
        $handlerClassName = Generic::class;
        if (isset($this->tagHandlerMappings[$tagName])) {
            $handlerClassName = $this->tagHandlerMappings[$tagName];
        } elseif ($this->isAnnotation($tagName)) {
            // TODO: Annotation support is planned for a later stage and as such is disabled for now
            // $tagName = (string)$this->fqsenResolver->resolve($tagName, $context);
            // if (isset($this->annotationMappings[$tagName])) {
            //     $handlerClassName = $this->annotationMappings[$tagName];
            // }
        }

        return $handlerClassName;
    }

    /**
     * Retrieves the arguments that need to be passed to the Factory Method with the given Parameters.
     *
     * @param \ReflectionParameter[] $parameters
     * @param mixed[]                $locator
     *
     * @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters
     *     is provided with this method.
     */
    private function getArgumentsForParametersFromWiring($parameters, $locator)
    {
        $arguments = [];
        foreach ($parameters as $index => $parameter) {
            $typeHint = $parameter->getClass() ? $parameter->getClass()->getName() : null;
            if (isset($locator[$typeHint])) {
                $arguments[] = $locator[$typeHint];
                continue;
            }

            $parameterName = $parameter->getName();
            if (isset($locator[$parameterName])) {
                $arguments[] = $locator[$parameterName];
                continue;
            }

            $arguments[] = null;
        }

        return $arguments;
    }

    /**
     * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given
     * tag handler class name.
     *
     * @param string $handlerClassName
     *
     * @return \ReflectionParameter[]
     */
    private function fetchParametersForHandlerFactoryMethod($handlerClassName)
    {
        if (! isset($this->tagHandlerParameterCache[$handlerClassName])) {
            $methodReflection                                  = new \ReflectionMethod($handlerClassName, 'create');
            $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters();
        }

        return $this->tagHandlerParameterCache[$handlerClassName];
    }

    /**
     * Returns a copy of this class' Service Locator with added dynamic parameters, such as the tag's name, body and
     * Context.
     *
     * @param TypeContext $context The Context (namespace and aliasses) that may be passed and is used to resolve FQSENs.
     * @param string      $tagName The name of the tag that may be passed onto the factory method of the Tag class.
     * @param string      $tagBody The body of the tag that may be passed onto the factory method of the Tag class.
     *
     * @return mixed[]
     */
    private function getServiceLocatorWithDynamicParameters(TypeContext $context, $tagName, $tagBody)
    {
        $locator = array_merge(
            $this->serviceLocator,
            [
                'name'             => $tagName,
                'body'             => $tagBody,
                TypeContext::class => $context
            ]
        );

        return $locator;
    }

    /**
     * Returns whether the given tag belongs to an annotation.
     *
     * @param string $tagContent
     *
     * @todo this method should be populated once we implement Annotation notation support.
     *
     * @return bool
     */
    private function isAnnotation($tagContent)
    {
        // 1. Contains a namespace separator
        // 2. Contains parenthesis
        // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part
        //    of the annotation class name matches the found tag name

        return false;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

use phpDocumentor\Reflection\DocBlock\Tag;
use Webmozart\Assert\Assert;

final class DocBlock
{
    /** @var string The opening line for this docblock. */
    private $summary = '';

    /** @var DocBlock\Description The actual description for this docblock. */
    private $description = null;

    /** @var Tag[] An array containing all the tags in this docblock; except inline. */
    private $tags = [];

    /** @var Types\Context Information about the context of this DocBlock. */
    private $context = null;

    /** @var Location Information about the location of this DocBlock. */
    private $location = null;

    /** @var bool Is this DocBlock (the start of) a template? */
    private $isTemplateStart = false;

    /** @var bool Does this DocBlock signify the end of a DocBlock template? */
    private $isTemplateEnd = false;

    /**
     * @param string $summary
     * @param DocBlock\Description $description
     * @param DocBlock\Tag[] $tags
     * @param Types\Context $context The context in which the DocBlock occurs.
     * @param Location $location The location within the file that this DocBlock occurs in.
     * @param bool $isTemplateStart
     * @param bool $isTemplateEnd
     */
    public function __construct(
        $summary = '',
        DocBlock\Description $description = null,
        array $tags = [],
        Types\Context $context = null,
        Location $location = null,
        $isTemplateStart = false,
        $isTemplateEnd = false
    ) {
        Assert::string($summary);
        Assert::boolean($isTemplateStart);
        Assert::boolean($isTemplateEnd);
        Assert::allIsInstanceOf($tags, Tag::class);

        $this->summary = $summary;
        $this->description = $description ?: new DocBlock\Description('');
        foreach ($tags as $tag) {
            $this->addTag($tag);
        }

        $this->context = $context;
        $this->location = $location;

        $this->isTemplateEnd = $isTemplateEnd;
        $this->isTemplateStart = $isTemplateStart;
    }

    /**
     * @return string
     */
    public function getSummary()
    {
        return $this->summary;
    }

    /**
     * @return DocBlock\Description
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Returns the current context.
     *
     * @return Types\Context
     */
    public function getContext()
    {
        return $this->context;
    }

    /**
     * Returns the current location.
     *
     * @return Location
     */
    public function getLocation()
    {
        return $this->location;
    }

    /**
     * Returns whether this DocBlock is the start of a Template section.
     *
     * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker
     * (`#@+`) that is appended directly after the opening `/**` of a DocBlock.
     *
     * An example of such an opening is:
     *
     * ```
     * /**#@+
     *  * My DocBlock
     *  * /
     * ```
     *
     * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all
     * elements that follow until another DocBlock is found that contains the closing marker (`#@-`).
     *
     * @see self::isTemplateEnd() for the check whether a closing marker was provided.
     *
     * @return boolean
     */
    public function isTemplateStart()
    {
        return $this->isTemplateStart;
    }

    /**
     * Returns whether this DocBlock is the end of a Template section.
     *
     * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality.
     *
     * @return boolean
     */
    public function isTemplateEnd()
    {
        return $this->isTemplateEnd;
    }

    /**
     * Returns the tags for this DocBlock.
     *
     * @return Tag[]
     */
    public function getTags()
    {
        return $this->tags;
    }

    /**
     * Returns an array of tags matching the given name. If no tags are found
     * an empty array is returned.
     *
     * @param string $name String to search by.
     *
     * @return Tag[]
     */
    public function getTagsByName($name)
    {
        Assert::string($name);

        $result = [];

        /** @var Tag $tag */
        foreach ($this->getTags() as $tag) {
            if ($tag->getName() !== $name) {
                continue;
            }

            $result[] = $tag;
        }

        return $result;
    }

    /**
     * Checks if a tag of a certain type is present in this DocBlock.
     *
     * @param string $name Tag name to check for.
     *
     * @return bool
     */
    public function hasTag($name)
    {
        Assert::string($name);

        /** @var Tag $tag */
        foreach ($this->getTags() as $tag) {
            if ($tag->getName() === $name) {
                return true;
            }
        }

        return false;
    }

    /**
     * Remove a tag from this DocBlock.
     *
     * @param Tag $tag The tag to remove.
     *
     * @return void
     */
    public function removeTag(Tag $tagToRemove)
    {
        foreach ($this->tags as $key => $tag) {
            if ($tag === $tagToRemove) {
                unset($this->tags[$key]);
                break;
            }
        }
    }

    /**
     * Adds a tag to this DocBlock.
     *
     * @param Tag $tag The tag to add.
     *
     * @return void
     */
    private function addTag(Tag $tag)
    {
        $this->tags[] = $tag;
    }
}
<?php
namespace phpDocumentor\Reflection;

interface DocBlockFactoryInterface
{
    /**
     * Factory method for easy instantiation.
     *
     * @param string[] $additionalTags
     *
     * @return DocBlockFactory
     */
    public static function createInstance(array $additionalTags = []);

    /**
     * @param string $docblock
     * @param Types\Context $context
     * @param Location $location
     *
     * @return DocBlock
     */
    public function create($docblock, Types\Context $context = null, Location $location = null);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\TagFactory;
use Webmozart\Assert\Assert;

final class DocBlockFactory implements DocBlockFactoryInterface
{
    /** @var DocBlock\DescriptionFactory */
    private $descriptionFactory;

    /** @var DocBlock\TagFactory */
    private $tagFactory;

    /**
     * Initializes this factory with the required subcontractors.
     *
     * @param DescriptionFactory $descriptionFactory
     * @param TagFactory         $tagFactory
     */
    public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory)
    {
        $this->descriptionFactory = $descriptionFactory;
        $this->tagFactory = $tagFactory;
    }

    /**
     * Factory method for easy instantiation.
     *
     * @param string[] $additionalTags
     *
     * @return DocBlockFactory
     */
    public static function createInstance(array $additionalTags = [])
    {
        $fqsenResolver = new FqsenResolver();
        $tagFactory = new StandardTagFactory($fqsenResolver);
        $descriptionFactory = new DescriptionFactory($tagFactory);

        $tagFactory->addService($descriptionFactory);
        $tagFactory->addService(new TypeResolver($fqsenResolver));

        $docBlockFactory = new self($descriptionFactory, $tagFactory);
        foreach ($additionalTags as $tagName => $tagHandler) {
            $docBlockFactory->registerTagHandler($tagName, $tagHandler);
        }

        return $docBlockFactory;
    }

    /**
     * @param object|string $docblock A string containing the DocBlock to parse or an object supporting the
     *                                getDocComment method (such as a ReflectionClass object).
     * @param Types\Context $context
     * @param Location      $location
     *
     * @return DocBlock
     */
    public function create($docblock, Types\Context $context = null, Location $location = null)
    {
        if (is_object($docblock)) {
            if (!method_exists($docblock, 'getDocComment')) {
                $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method';
                throw new \InvalidArgumentException($exceptionMessage);
            }

            $docblock = $docblock->getDocComment();
        }

        Assert::stringNotEmpty($docblock);

        if ($context === null) {
            $context = new Types\Context('');
        }

        $parts = $this->splitDocBlock($this->stripDocComment($docblock));
        list($templateMarker, $summary, $description, $tags) = $parts;

        return new DocBlock(
            $summary,
            $description ? $this->descriptionFactory->create($description, $context) : null,
            array_filter($this->parseTagBlock($tags, $context), function ($tag) {
                return $tag instanceof Tag;
            }),
            $context,
            $location,
            $templateMarker === '#@+',
            $templateMarker === '#@-'
        );
    }

    public function registerTagHandler($tagName, $handler)
    {
        $this->tagFactory->registerTagHandler($tagName, $handler);
    }

    /**
     * Strips the asterisks from the DocBlock comment.
     *
     * @param string $comment String containing the comment text.
     *
     * @return string
     */
    private function stripDocComment($comment)
    {
        $comment = trim(preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u', '$1', $comment));

        // reg ex above is not able to remove */ from a single line docblock
        if (substr($comment, -2) === '*/') {
            $comment = trim(substr($comment, 0, -2));
        }

        return str_replace(["\r\n", "\r"], "\n", $comment);
    }

    /**
     * Splits the DocBlock into a template marker, summary, description and block of tags.
     *
     * @param string $comment Comment to split into the sub-parts.
     *
     * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split.
     * @author Mike van Riel <me@mikevanriel.com> for extending the regex with template marker support.
     *
     * @return string[] containing the template marker (if any), summary, description and a string containing the tags.
     */
    private function splitDocBlock($comment)
    {
        // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This
        // method does not split tags so we return this verbatim as the fourth result (tags). This saves us the
        // performance impact of running a regular expression
        if (strpos($comment, '@') === 0) {
            return ['', '', '', $comment];
        }

        // clears all extra horizontal whitespace from the line endings to prevent parsing issues
        $comment = preg_replace('/\h*$/Sum', '', $comment);

        /*
         * Splits the docblock into a template marker, summary, description and tags section.
         *
         * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may
         *   occur after it and will be stripped).
         * - The short description is started from the first character until a dot is encountered followed by a
         *   newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing
         *   errors). This is optional.
         * - The long description, any character until a new line is encountered followed by an @ and word
         *   characters (a tag). This is optional.
         * - Tags; the remaining characters
         *
         * Big thanks to RichardJ for contributing this Regular Expression
         */
        preg_match(
            '/
            \A
            # 1. Extract the template marker
            (?:(\#\@\+|\#\@\-)\n?)?

            # 2. Extract the summary
            (?:
              (?! @\pL ) # The summary may not start with an @
              (
                [^\n.]+
                (?:
                  (?! \. \n | \n{2} )     # End summary upon a dot followed by newline or two newlines
                  [\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line
                  [^\n.]+                 # Include anything else
                )*
                \.?
              )?
            )

            # 3. Extract the description
            (?:
              \s*        # Some form of whitespace _must_ precede a description because a summary must be there
              (?! @\pL ) # The description may not start with an @
              (
                [^\n]+
                (?: \n+
                  (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line
                  [^\n]+            # Include anything else
                )*
              )
            )?

            # 4. Extract the tags (anything that follows)
            (\s+ [\s\S]*)? # everything that follows
            /ux',
            $comment,
            $matches
        );
        array_shift($matches);

        while (count($matches) < 4) {
            $matches[] = '';
        }

        return $matches;
    }

    /**
     * Creates the tag objects.
     *
     * @param string $tags Tag block to parse.
     * @param Types\Context $context Context of the parsed Tag
     *
     * @return DocBlock\Tag[]
     */
    private function parseTagBlock($tags, Types\Context $context)
    {
        $tags = $this->filterTagBlock($tags);
        if (!$tags) {
            return [];
        }

        $result = $this->splitTagBlockIntoTagLines($tags);
        foreach ($result as $key => $tagLine) {
            $result[$key] = $this->tagFactory->create(trim($tagLine), $context);
        }

        return $result;
    }

    /**
     * @param string $tags
     *
     * @return string[]
     */
    private function splitTagBlockIntoTagLines($tags)
    {
        $result = [];
        foreach (explode("\n", $tags) as $tag_line) {
            if (isset($tag_line[0]) && ($tag_line[0] === '@')) {
                $result[] = $tag_line;
            } else {
                $result[count($result) - 1] .= "\n" . $tag_line;
            }
        }

        return $result;
    }

    /**
     * @param $tags
     * @return string
     */
    private function filterTagBlock($tags)
    {
        $tags = trim($tags);
        if (!$tags) {
            return null;
        }

        if ('@' !== $tags[0]) {
            // @codeCoverageIgnoreStart
            // Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that
            // we didn't foresee.
            throw new \LogicException('A tag block started with text instead of an at-sign(@): ' . $tags);
            // @codeCoverageIgnoreEnd
        }

        return $tags;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

interface Type
{
    public function __toString();
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Represents an array type as described in the PSR-5, the PHPDoc Standard.
 *
 * An array can be represented in two forms:
 *
 * 1. Untyped (`array`), where the key and value type is unknown and hence classified as 'Mixed_'.
 * 2. Types (`string[]`), where the value type is provided by preceding an opening and closing square bracket with a
 *    type name.
 */
final class Array_ implements Type
{
    /** @var Type */
    private $valueType;

    /** @var Type */
    private $keyType;

    /**
     * Initializes this representation of an array with the given Type or Fqsen.
     *
     * @param Type $valueType
     * @param Type $keyType
     */
    public function __construct(Type $valueType = null, Type $keyType = null)
    {
        if ($keyType === null) {
            $keyType = new Compound([ new String_(), new Integer() ]);
        }
        if ($valueType === null) {
            $valueType = new Mixed_();
        }

        $this->valueType = $valueType;
        $this->keyType = $keyType;
    }

    /**
     * Returns the type for the keys of this array.
     *
     * @return Type
     */
    public function getKeyType()
    {
        return $this->keyType;
    }

    /**
     * Returns the value for the keys of this array.
     *
     * @return Type
     */
    public function getValueType()
    {
        return $this->valueType;
    }

    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        if ($this->valueType instanceof Mixed_) {
            return 'array';
        }

        return $this->valueType . '[]';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

final class Integer implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'int';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a Callable type.
 */
final class Callable_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'callable';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a Boolean type.
 */
final class Boolean implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'bool';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the 'static' type.
 *
 * Self, as a Type, represents the class in which the associated element was called. This differs from self as self does
 * not take inheritance into account but static means that the return type is always that of the class of the called
 * element.
 *
 * See the documentation on late static binding in the PHP Documentation for more information on the difference between
 * static and self.
 */
final class Static_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'static';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use ArrayIterator;
use IteratorAggregate;
use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a Compound Type.
 *
 * A Compound Type is not so much a special keyword or object reference but is a series of Types that are separated
 * using an OR operator (`|`). This combination of types signifies that whatever is associated with this compound type
 * may contain a value with any of the given types.
 */
final class Compound implements Type, IteratorAggregate
{
    /** @var Type[] */
    private $types;

    /**
     * Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface.
     *
     * @param Type[] $types
     * @throws \InvalidArgumentException when types are not all instance of Type
     */
    public function __construct(array $types)
    {
        foreach ($types as $type) {
            if (!$type instanceof Type) {
                throw new \InvalidArgumentException('A compound type can only have other types as elements');
            }
        }

        $this->types = $types;
    }

    /**
     * Returns the type at the given index.
     *
     * @param integer $index
     *
     * @return Type|null
     */
    public function get($index)
    {
        if (!$this->has($index)) {
            return null;
        }

        return $this->types[$index];
    }

    /**
     * Tests if this compound type has a type with the given index.
     *
     * @param integer $index
     *
     * @return bool
     */
    public function has($index)
    {
        return isset($this->types[$index]);
    }

    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return implode('|', $this->types);
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
        return new ArrayIterator($this->types);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

/**
 * Provides information about the Context in which the DocBlock occurs that receives this context.
 *
 * A DocBlock does not know of its own accord in which namespace it occurs and which namespace aliases are applicable
 * for the block of code in which it is in. This information is however necessary to resolve Class names in tags since
 * you can provide a short form or make use of namespace aliases.
 *
 * The phpDocumentor Reflection component knows how to create this class but if you use the DocBlock parser from your
 * own application it is possible to generate a Context class using the ContextFactory; this will analyze the file in
 * which an associated class resides for its namespace and imports.
 *
 * @see ContextFactory::createFromClassReflector()
 * @see ContextFactory::createForNamespace()
 */
final class Context
{
    /** @var string The current namespace. */
    private $namespace;

    /** @var array List of namespace aliases => Fully Qualified Namespace. */
    private $namespaceAliases;

    /**
     * Initializes the new context and normalizes all passed namespaces to be in Qualified Namespace Name (QNN)
     * format (without a preceding `\`).
     *
     * @param string $namespace The namespace where this DocBlock resides in.
     * @param array $namespaceAliases List of namespace aliases => Fully Qualified Namespace.
     */
    public function __construct($namespace, array $namespaceAliases = [])
    {
        $this->namespace = ('global' !== $namespace && 'default' !== $namespace)
            ? trim((string)$namespace, '\\')
            : '';

        foreach ($namespaceAliases as $alias => $fqnn) {
            if ($fqnn[0] === '\\') {
                $fqnn = substr($fqnn, 1);
            }
            if ($fqnn[strlen($fqnn) - 1] === '\\') {
                $fqnn = substr($fqnn, 0, -1);
            }

            $namespaceAliases[$alias] = $fqnn;
        }

        $this->namespaceAliases = $namespaceAliases;
    }

    /**
     * Returns the Qualified Namespace Name (thus without `\` in front) where the associated element is in.
     *
     * @return string
     */
    public function getNamespace()
    {
        return $this->namespace;
    }

    /**
     * Returns a list of Qualified Namespace Names (thus without `\` in front) that are imported, the keys represent
     * the alias for the imported Namespace.
     *
     * @return string[]
     */
    public function getNamespaceAliases()
    {
        return $this->namespaceAliases;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2017 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing iterable type
 */
final class Iterable_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'iterable';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the '$this' pseudo-type.
 *
 * $this, as a Type, represents the instance of the class associated with the element as it was called. $this is
 * commonly used when documenting fluent interfaces since it represents that the same object is returned.
 */
final class This implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return '$this';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the 'scalar' pseudo-type, which is either a string, integer, float or boolean.
 */
final class Scalar implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'scalar';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the pseudo-type 'void'.
 *
 * Void is generally only used when working with return types as it signifies that the method intentionally does not
 * return any value.
 */
final class Void_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'void';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a Float.
 */
final class Float_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'float';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the type 'string'.
 */
final class String_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'string';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a null value or type.
 */
final class Null_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'null';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2017 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a nullable type. The real type is wrapped.
 */
final class Nullable implements Type
{
    /**
     * @var Type
     */
    private $realType;

    /**
     * Initialises this nullable type using the real type embedded
     *
     * @param Type $realType
     */
    public function __construct(Type $realType)
    {
        $this->realType = $realType;
    }

    /**
     * Provide access to the actual type directly, if needed.
     *
     * @return Type
     */
    public function getActualType()
    {
        return $this->realType;
    }

    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return '?' . $this->realType->__toString();
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the 'parent' type.
 *
 * Parent, as a Type, represents the parent class of class in which the associated element was defined.
 */
final class Parent_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'parent';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type;

/**
 * Value Object representing an object.
 *
 * An object can be either typed or untyped. When an object is typed it means that it has an identifier, the FQSEN,
 * pointing to an element in PHP. Object types that are untyped do not refer to a specific class but represent objects
 * in general.
 */
final class Object_ implements Type
{
    /** @var Fqsen|null */
    private $fqsen;

    /**
     * Initializes this object with an optional FQSEN, if not provided this object is considered 'untyped'.
     *
     * @param Fqsen $fqsen
     * @throws \InvalidArgumentException when provided $fqsen is not a valid type.
     */
    public function __construct(Fqsen $fqsen = null)
    {
        if (strpos((string)$fqsen, '::') !== false || strpos((string)$fqsen, '()') !== false) {
            throw new \InvalidArgumentException(
                'Object types can only refer to a class, interface or trait but a method, function, constant or '
                . 'property was received: ' . (string)$fqsen
            );
        }

        $this->fqsen = $fqsen;
    }

    /**
     * Returns the FQSEN associated with this object.
     *
     * @return Fqsen|null
     */
    public function getFqsen()
    {
        return $this->fqsen;
    }

    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        if ($this->fqsen) {
            return (string)$this->fqsen;
        }

        return 'object';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing an unknown, or mixed, type.
 */
final class Mixed_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'mixed';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the 'self' type.
 *
 * Self, as a Type, represents the class in which the associated element was defined.
 */
final class Self_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'self';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

/**
 * Convenience class to create a Context for DocBlocks when not using the Reflection Component of phpDocumentor.
 *
 * For a DocBlock to be able to resolve types that use partial namespace names or rely on namespace imports we need to
 * provide a bit of context so that the DocBlock can read that and based on it decide how to resolve the types to
 * Fully Qualified names.
 *
 * @see Context for more information.
 */
final class ContextFactory
{
    /** The literal used at the end of a use statement. */
    const T_LITERAL_END_OF_USE = ';';

    /** The literal used between sets of use statements */
    const T_LITERAL_USE_SEPARATOR = ',';

    /**
     * Build a Context given a Class Reflection.
     *
     * @param \Reflector $reflector
     *
     * @see Context for more information on Contexts.
     *
     * @return Context
     */
    public function createFromReflector(\Reflector $reflector)
    {
        if (method_exists($reflector, 'getDeclaringClass')) {
            $reflector = $reflector->getDeclaringClass();
        }

        $fileName = $reflector->getFileName();
        $namespace = $reflector->getNamespaceName();

        if (file_exists($fileName)) {
            return $this->createForNamespace($namespace, file_get_contents($fileName));
        }

        return new Context($namespace, []);
    }

    /**
     * Build a Context for a namespace in the provided file contents.
     *
     * @param string $namespace It does not matter if a `\` precedes the namespace name, this method first normalizes.
     * @param string $fileContents the file's contents to retrieve the aliases from with the given namespace.
     *
     * @see Context for more information on Contexts.
     *
     * @return Context
     */
    public function createForNamespace($namespace, $fileContents)
    {
        $namespace = trim($namespace, '\\');
        $useStatements = [];
        $currentNamespace = '';
        $tokens = new \ArrayIterator(token_get_all($fileContents));

        while ($tokens->valid()) {
            switch ($tokens->current()[0]) {
                case T_NAMESPACE:
                    $currentNamespace = $this->parseNamespace($tokens);
                    break;
                case T_CLASS:
                    // Fast-forward the iterator through the class so that any
                    // T_USE tokens found within are skipped - these are not
                    // valid namespace use statements so should be ignored.
                    $braceLevel = 0;
                    $firstBraceFound = false;
                    while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) {
                        if ($tokens->current() === '{'
                            || $tokens->current()[0] === T_CURLY_OPEN
                            || $tokens->current()[0] === T_DOLLAR_OPEN_CURLY_BRACES) {
                            if (!$firstBraceFound) {
                                $firstBraceFound = true;
                            }
                            $braceLevel++;
                        }

                        if ($tokens->current() === '}') {
                            $braceLevel--;
                        }
                        $tokens->next();
                    }
                    break;
                case T_USE:
                    if ($currentNamespace === $namespace) {
                        $useStatements = array_merge($useStatements, $this->parseUseStatement($tokens));
                    }
                    break;
            }
            $tokens->next();
        }

        return new Context($namespace, $useStatements);
    }

    /**
     * Deduce the name from tokens when we are at the T_NAMESPACE token.
     *
     * @param \ArrayIterator $tokens
     *
     * @return string
     */
    private function parseNamespace(\ArrayIterator $tokens)
    {
        // skip to the first string or namespace separator
        $this->skipToNextStringOrNamespaceSeparator($tokens);

        $name = '';
        while ($tokens->valid() && ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR)
        ) {
            $name .= $tokens->current()[1];
            $tokens->next();
        }

        return $name;
    }

    /**
     * Deduce the names of all imports when we are at the T_USE token.
     *
     * @param \ArrayIterator $tokens
     *
     * @return string[]
     */
    private function parseUseStatement(\ArrayIterator $tokens)
    {
        $uses = [];
        $continue = true;

        while ($continue) {
            $this->skipToNextStringOrNamespaceSeparator($tokens);

            list($alias, $fqnn) = $this->extractUseStatement($tokens);
            $uses[$alias] = $fqnn;
            if ($tokens->current()[0] === self::T_LITERAL_END_OF_USE) {
                $continue = false;
            }
        }

        return $uses;
    }

    /**
     * Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token.
     *
     * @param \ArrayIterator $tokens
     *
     * @return void
     */
    private function skipToNextStringOrNamespaceSeparator(\ArrayIterator $tokens)
    {
        while ($tokens->valid() && ($tokens->current()[0] !== T_STRING) && ($tokens->current()[0] !== T_NS_SEPARATOR)) {
            $tokens->next();
        }
    }

    /**
     * Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of
     * a USE statement yet.
     *
     * @param \ArrayIterator $tokens
     *
     * @return string
     */
    private function extractUseStatement(\ArrayIterator $tokens)
    {
        $result = [''];
        while ($tokens->valid()
            && ($tokens->current()[0] !== self::T_LITERAL_USE_SEPARATOR)
            && ($tokens->current()[0] !== self::T_LITERAL_END_OF_USE)
        ) {
            if ($tokens->current()[0] === T_AS) {
                $result[] = '';
            }
            if ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) {
                $result[count($result) - 1] .= $tokens->current()[1];
            }
            $tokens->next();
        }

        if (count($result) == 1) {
            $backslashPos = strrpos($result[0], '\\');

            if (false !== $backslashPos) {
                $result[] = substr($result[0], $backslashPos + 1);
            } else {
                $result[] = $result[0];
            }
        }

        return array_reverse($result);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the 'resource' Type.
 */
final class Resource_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'resource';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

use phpDocumentor\Reflection\Types\Context;

class FqsenResolver
{
    /** @var string Definition of the NAMESPACE operator in PHP */
    const OPERATOR_NAMESPACE = '\\';

    public function resolve($fqsen, Context $context = null)
    {
        if ($context === null) {
            $context = new Context('');
        }

        if ($this->isFqsen($fqsen)) {
            return new Fqsen($fqsen);
        }

        return $this->resolvePartialStructuralElementName($fqsen, $context);
    }

    /**
     * Tests whether the given type is a Fully Qualified Structural Element Name.
     *
     * @param string $type
     *
     * @return bool
     */
    private function isFqsen($type)
    {
        return strpos($type, self::OPERATOR_NAMESPACE) === 0;
    }

    /**
     * Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation
     * (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context.
     *
     * @param string $type
     * @param Context $context
     *
     * @return Fqsen
     * @throws \InvalidArgumentException when type is not a valid FQSEN.
     */
    private function resolvePartialStructuralElementName($type, Context $context)
    {
        $typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2);

        $namespaceAliases = $context->getNamespaceAliases();

        // if the first segment is not an alias; prepend namespace name and return
        if (!isset($namespaceAliases[$typeParts[0]])) {
            $namespace = $context->getNamespace();
            if ('' !== $namespace) {
                $namespace .= self::OPERATOR_NAMESPACE;
            }

            return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type);
        }

        $typeParts[0] = $namespaceAliases[$typeParts[0]];

        return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts));
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Iterable_;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\Object_;

final class TypeResolver
{
    /** @var string Definition of the ARRAY operator for types */
    const OPERATOR_ARRAY = '[]';

    /** @var string Definition of the NAMESPACE operator in PHP */
    const OPERATOR_NAMESPACE = '\\';

    /** @var string[] List of recognized keywords and unto which Value Object they map */
    private $keywords = array(
        'string' => Types\String_::class,
        'int' => Types\Integer::class,
        'integer' => Types\Integer::class,
        'bool' => Types\Boolean::class,
        'boolean' => Types\Boolean::class,
        'float' => Types\Float_::class,
        'double' => Types\Float_::class,
        'object' => Object_::class,
        'mixed' => Types\Mixed_::class,
        'array' => Array_::class,
        'resource' => Types\Resource_::class,
        'void' => Types\Void_::class,
        'null' => Types\Null_::class,
        'scalar' => Types\Scalar::class,
        'callback' => Types\Callable_::class,
        'callable' => Types\Callable_::class,
        'false' => Types\Boolean::class,
        'true' => Types\Boolean::class,
        'self' => Types\Self_::class,
        '$this' => Types\This::class,
        'static' => Types\Static_::class,
        'parent' => Types\Parent_::class,
        'iterable' => Iterable_::class,
    );

    /** @var FqsenResolver */
    private $fqsenResolver;

    /**
     * Initializes this TypeResolver with the means to create and resolve Fqsen objects.
     *
     * @param FqsenResolver $fqsenResolver
     */
    public function __construct(FqsenResolver $fqsenResolver = null)
    {
        $this->fqsenResolver = $fqsenResolver ?: new FqsenResolver();
    }

    /**
     * Analyzes the given type and returns the FQCN variant.
     *
     * When a type is provided this method checks whether it is not a keyword or
     * Fully Qualified Class Name. If so it will use the given namespace and
     * aliases to expand the type to a FQCN representation.
     *
     * This method only works as expected if the namespace and aliases are set;
     * no dynamic reflection is being performed here.
     *
     * @param string $type     The relative or absolute type.
     * @param Context $context
     *
     * @uses Context::getNamespace()        to determine with what to prefix the type name.
     * @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be
     *     replaced with another namespace.
     *
     * @return Type|null
     */
    public function resolve($type, Context $context = null)
    {
        if (!is_string($type)) {
            throw new \InvalidArgumentException(
                'Attempted to resolve type but it appeared not to be a string, received: ' . var_export($type, true)
            );
        }

        $type = trim($type);
        if (!$type) {
            throw new \InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty');
        }

        if ($context === null) {
            $context = new Context('');
        }

        switch (true) {
            case $this->isNullableType($type):
                return $this->resolveNullableType($type, $context);
            case $this->isKeyword($type):
                return $this->resolveKeyword($type);
            case ($this->isCompoundType($type)):
                return $this->resolveCompoundType($type, $context);
            case $this->isTypedArray($type):
                return $this->resolveTypedArray($type, $context);
            case $this->isFqsen($type):
                return $this->resolveTypedObject($type);
            case $this->isPartialStructuralElementName($type):
                return $this->resolveTypedObject($type, $context);
            // @codeCoverageIgnoreStart
            default:
                // I haven't got the foggiest how the logic would come here but added this as a defense.
                throw new \RuntimeException(
                    'Unable to resolve type "' . $type . '", there is no known method to resolve it'
                );
        }
        // @codeCoverageIgnoreEnd
    }

    /**
     * Adds a keyword to the list of Keywords and associates it with a specific Value Object.
     *
     * @param string $keyword
     * @param string $typeClassName
     *
     * @return void
     */
    public function addKeyword($keyword, $typeClassName)
    {
        if (!class_exists($typeClassName)) {
            throw new \InvalidArgumentException(
                'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class'
                . ' but we could not find the class ' . $typeClassName
            );
        }

        if (!in_array(Type::class, class_implements($typeClassName))) {
            throw new \InvalidArgumentException(
                'The class "' . $typeClassName . '" must implement the interface "phpDocumentor\Reflection\Type"'
            );
        }

        $this->keywords[$keyword] = $typeClassName;
    }

    /**
     * Detects whether the given type represents an array.
     *
     * @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
     *
     * @return bool
     */
    private function isTypedArray($type)
    {
        return substr($type, -2) === self::OPERATOR_ARRAY;
    }

    /**
     * Detects whether the given type represents a PHPDoc keyword.
     *
     * @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
     *
     * @return bool
     */
    private function isKeyword($type)
    {
        return in_array(strtolower($type), array_keys($this->keywords), true);
    }

    /**
     * Detects whether the given type represents a relative structural element name.
     *
     * @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
     *
     * @return bool
     */
    private function isPartialStructuralElementName($type)
    {
        return ($type[0] !== self::OPERATOR_NAMESPACE) && !$this->isKeyword($type);
    }

    /**
     * Tests whether the given type is a Fully Qualified Structural Element Name.
     *
     * @param string $type
     *
     * @return bool
     */
    private function isFqsen($type)
    {
        return strpos($type, self::OPERATOR_NAMESPACE) === 0;
    }

    /**
     * Tests whether the given type is a compound type (i.e. `string|int`).
     *
     * @param string $type
     *
     * @return bool
     */
    private function isCompoundType($type)
    {
        return strpos($type, '|') !== false;
    }

    /**
     * Test whether the given type is a nullable type (i.e. `?string`)
     *
     * @param string $type
     *
     * @return bool
     */
    private function isNullableType($type)
    {
        return $type[0] === '?';
    }

    /**
     * Resolves the given typed array string (i.e. `string[]`) into an Array object with the right types set.
     *
     * @param string $type
     * @param Context $context
     *
     * @return Array_
     */
    private function resolveTypedArray($type, Context $context)
    {
        return new Array_($this->resolve(substr($type, 0, -2), $context));
    }

    /**
     * Resolves the given keyword (such as `string`) into a Type object representing that keyword.
     *
     * @param string $type
     *
     * @return Type
     */
    private function resolveKeyword($type)
    {
        $className = $this->keywords[strtolower($type)];

        return new $className();
    }

    /**
     * Resolves the given FQSEN string into an FQSEN object.
     *
     * @param string $type
     * @param Context|null $context
     *
     * @return Object_
     */
    private function resolveTypedObject($type, Context $context = null)
    {
        return new Object_($this->fqsenResolver->resolve($type, $context));
    }

    /**
     * Resolves a compound type (i.e. `string|int`) into the appropriate Type objects or FQSEN.
     *
     * @param string $type
     * @param Context $context
     *
     * @return Compound
     */
    private function resolveCompoundType($type, Context $context)
    {
        $types = [];

        foreach (explode('|', $type) as $part) {
            $types[] = $this->resolve($part, $context);
        }

        return new Compound($types);
    }

    /**
     * Resolve nullable types (i.e. `?string`) into a Nullable type wrapper
     *
     * @param string $type
     * @param Context $context
     *
     * @return Nullable
     */
    private function resolveNullableType($type, Context $context)
    {
        return new Nullable($this->resolve(ltrim($type, '?'), $context));
    }
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.5
 *
 * @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

/**
 * Interface for project. Since the definition of a project can be different per factory this interface will be small.
 */
interface Project
{
    /**
     * Returns the name of the project.
     *
     * @return string
     */
    public function getName();
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

/**
 * Interface for files processed by the ProjectFactory
 */
interface File
{
    /**
     * Returns the content of the file as a string.
     *
     * @return string
     */
    public function getContents();

    /**
     * Returns md5 hash of the file.
     *
     * @return string
     */
    public function md5();

    /**
     * Returns an relative path to the file.
     *
     * @return string
     */
    public function path();
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.5
 *
 * @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */
namespace phpDocumentor\Reflection;

/**
 * Interface for project factories. A project factory shall convert a set of files
 * into an object implementing the Project interface.
 */
interface ProjectFactory
{
    /**
     * Creates a project from the set of files.
     *
     * @param string $name
     * @param File[] $files
     * @return Project
     */
    public function create($name, array $files);
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.5
 *
 * @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

/**
 * Value Object for Fqsen.
 *
 * @link https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc-meta.md
 */
final class Fqsen
{
    /**
     * @var string full quallified class name
     */
    private $fqsen;

    /**
     * @var string name of the element without path.
     */
    private $name;

    /**
     * Initializes the object.
     *
     * @param string $fqsen
     *
     * @throws \InvalidArgumentException when $fqsen is not matching the format.
     */
    public function __construct($fqsen)
    {
        $matches = array();
        $result = preg_match(
            '/^\\\\([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff\\\\]*)?(?:[:]{2}\\$?([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*))?(?:\\(\\))?$/',
                $fqsen,
                $matches
        );

        if ($result === 0) {
            throw new \InvalidArgumentException(
                sprintf('"%s" is not a valid Fqsen.', $fqsen)
            );
        }

        $this->fqsen = $fqsen;

        if (isset($matches[2])) {
            $this->name = $matches[2];
        } else {
            $matches = explode('\\', $fqsen);
            $this->name = trim(end($matches), '()');
        }
    }

    /**
     * converts this class to string.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->fqsen;
    }

    /**
     * Returns the name of the element without path.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

/**
 * The location where an element occurs within a file.
 */
final class Location
{
    /** @var int  */
    private $lineNumber = 0;

    /** @var int */
    private $columnNumber = 0;

    /**
     * Initializes the location for an element using its line number in the file and optionally the column number.
     *
     * @param int $lineNumber
     * @param int $columnNumber
     */
    public function __construct($lineNumber, $columnNumber = 0)
    {
        $this->lineNumber   = $lineNumber;
        $this->columnNumber = $columnNumber;
    }

    /**
     * Returns the line number that is covered by this location.
     *
     * @return integer
     */
    public function getLineNumber()
    {
        return $this->lineNumber;
    }

    /**
     * Returns the column number (character position on a line) for this location object.
     *
     * @return integer
     */
    public function getColumnNumber()
    {
        return $this->columnNumber;
    }
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.5
 *
 * @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

/**
 * Interface for Api Elements
 */
interface Element
{
    /**
     * Returns the Fqsen of the element.
     *
     * @return Fqsen
     */
    public function getFqsen();

    /**
     * Returns the name of the element.
     *
     * @return string
     */
    public function getName();
}<?php

namespace Psr\Log;

/**
 * This Logger can be used to avoid conditional log calls.
 *
 * Logging should always be optional, and if no logger is provided to your
 * library creating a NullLogger instance to have something to throw logs at
 * is a good way to avoid littering your code with `if ($this->logger) { }`
 * blocks.
 */
class NullLogger extends AbstractLogger
{
    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function log($level, $message, array $context = array())
    {
        // noop
    }
}
<?php

namespace Psr\Log;

/**
 * Describes log levels.
 */
class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}
<?php

namespace Psr\Log;

class InvalidArgumentException extends \InvalidArgumentException
{
}
<?php

namespace Psr\Log;

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
    /**
     * The logger instance.
     *
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * Sets a logger.
     *
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
}
<?php

namespace Psr\Log;

/**
 * This is a simple Logger implementation that other Loggers can inherit from.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
abstract class AbstractLogger implements LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array())
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array())
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array())
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }
}
<?php

namespace Psr\Log;

/**
 * Describes a logger-aware instance.
 */
interface LoggerAwareInterface
{
    /**
     * Sets a logger instance on the object.
     *
     * @param LoggerInterface $logger
     *
     * @return void
     */
    public function setLogger(LoggerInterface $logger);
}
<?php

namespace Psr\Log\Test;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

/**
 * Provides a base test class for ensuring compliance with the LoggerInterface.
 *
 * Implementors can extend the class and implement abstract methods to run this
 * as part of their test suite.
 */
abstract class LoggerInterfaceTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @return LoggerInterface
     */
    abstract public function getLogger();

    /**
     * This must return the log messages in order.
     *
     * The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>".
     *
     * Example ->error('Foo') would yield "error Foo".
     *
     * @return string[]
     */
    abstract public function getLogs();

    public function testImplements()
    {
        $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
    }

    /**
     * @dataProvider provideLevelsAndMessages
     */
    public function testLogsAtAllLevels($level, $message)
    {
        $logger = $this->getLogger();
        $logger->{$level}($message, array('user' => 'Bob'));
        $logger->log($level, $message, array('user' => 'Bob'));

        $expected = array(
            $level.' message of level '.$level.' with context: Bob',
            $level.' message of level '.$level.' with context: Bob',
        );
        $this->assertEquals($expected, $this->getLogs());
    }

    public function provideLevelsAndMessages()
    {
        return array(
            LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
            LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
            LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
            LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
            LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
            LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
            LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
            LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
        );
    }

    /**
     * @expectedException \Psr\Log\InvalidArgumentException
     */
    public function testThrowsOnInvalidLevel()
    {
        $logger = $this->getLogger();
        $logger->log('invalid level', 'Foo');
    }

    public function testContextReplacement()
    {
        $logger = $this->getLogger();
        $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));

        $expected = array('info {Message {nothing} Bob Bar a}');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testObjectCastToString()
    {
        if (method_exists($this, 'createPartialMock')) {
            $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
        } else {
            $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
        }
        $dummy->expects($this->once())
            ->method('__toString')
            ->will($this->returnValue('DUMMY'));

        $this->getLogger()->warning($dummy);

        $expected = array('warning DUMMY');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testContextCanContainAnything()
    {
        $context = array(
            'bool' => true,
            'null' => null,
            'string' => 'Foo',
            'int' => 0,
            'float' => 0.5,
            'nested' => array('with object' => new DummyTest),
            'object' => new \DateTime,
            'resource' => fopen('php://memory', 'r'),
        );

        $this->getLogger()->warning('Crazy context data', $context);

        $expected = array('warning Crazy context data');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testContextExceptionKeyCanBeExceptionOrOtherValues()
    {
        $logger = $this->getLogger();
        $logger->warning('Random message', array('exception' => 'oops'));
        $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));

        $expected = array(
            'warning Random message',
            'critical Uncaught Exception!'
        );
        $this->assertEquals($expected, $this->getLogs());
    }
}

class DummyTest
{
    public function __toString()
    {
    }
}
<?php

namespace Psr\Log;

/**
 * Describes a logger instance.
 *
 * The message MUST be a string or object implementing __toString().
 *
 * The message MAY contain placeholders in the form: {foo} where foo
 * will be replaced by the context data in key "foo".
 *
 * The context array can contain arbitrary data. The only assumption that
 * can be made by implementors is that if an Exception instance is given
 * to produce a stack trace, it MUST be in a key named "exception".
 *
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 * for the full interface specification.
 */
interface LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array());

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array());

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array());

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array());

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array());

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array());

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array());

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array());

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function log($level, $message, array $context = array());
}
<?php

namespace Psr\Log;

/**
 * This is a simple Logger trait that classes unable to extend AbstractLogger
 * (because they extend another class, etc) can include.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
trait LoggerTrait
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array())
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array())
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array())
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    abstract public function log($level, $message, array $context = array());
}
<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitc2bcc8b87b5bd6e082150780bb130a51::getLoader();
<?php

/*
 * This file is part of the webmozart/assert package.
 *
 * (c) Bernhard Schussek <bschussek@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Webmozart\Assert;

use ArrayAccess;
use BadMethodCallException;
use Closure;
use Countable;
use Exception;
use InvalidArgumentException;
use Throwable;
use Traversable;

/**
 * Efficient assertions to validate the input/output of your methods.
 *
 * @method static void nullOrString($value, $message = '')
 * @method static void nullOrStringNotEmpty($value, $message = '')
 * @method static void nullOrInteger($value, $message = '')
 * @method static void nullOrIntegerish($value, $message = '')
 * @method static void nullOrFloat($value, $message = '')
 * @method static void nullOrNumeric($value, $message = '')
 * @method static void nullOrBoolean($value, $message = '')
 * @method static void nullOrScalar($value, $message = '')
 * @method static void nullOrObject($value, $message = '')
 * @method static void nullOrResource($value, $type = null, $message = '')
 * @method static void nullOrIsCallable($value, $message = '')
 * @method static void nullOrIsArray($value, $message = '')
 * @method static void nullOrIsTraversable($value, $message = '')
 * @method static void nullOrIsArrayAccessible($value, $message = '')
 * @method static void nullOrIsCountable($value, $message = '')
 * @method static void nullOrIsInstanceOf($value, $class, $message = '')
 * @method static void nullOrNotInstanceOf($value, $class, $message = '')
 * @method static void nullOrIsInstanceOfAny($value, $classes, $message = '')
 * @method static void nullOrIsEmpty($value, $message = '')
 * @method static void nullOrNotEmpty($value, $message = '')
 * @method static void nullOrTrue($value, $message = '')
 * @method static void nullOrFalse($value, $message = '')
 * @method static void nullOrEq($value, $value2, $message = '')
 * @method static void nullOrNotEq($value,$value2,  $message = '')
 * @method static void nullOrSame($value, $value2, $message = '')
 * @method static void nullOrNotSame($value, $value2, $message = '')
 * @method static void nullOrGreaterThan($value, $value2, $message = '')
 * @method static void nullOrGreaterThanEq($value, $value2, $message = '')
 * @method static void nullOrLessThan($value, $value2, $message = '')
 * @method static void nullOrLessThanEq($value, $value2, $message = '')
 * @method static void nullOrRange($value, $min, $max, $message = '')
 * @method static void nullOrOneOf($value, $values, $message = '')
 * @method static void nullOrContains($value, $subString, $message = '')
 * @method static void nullOrNotContains($value, $subString, $message = '')
 * @method static void nullOrNotWhitespaceOnly($value, $message = '')
 * @method static void nullOrStartsWith($value, $prefix, $message = '')
 * @method static void nullOrStartsWithLetter($value, $message = '')
 * @method static void nullOrEndsWith($value, $suffix, $message = '')
 * @method static void nullOrRegex($value, $pattern, $message = '')
 * @method static void nullOrAlpha($value, $message = '')
 * @method static void nullOrDigits($value, $message = '')
 * @method static void nullOrAlnum($value, $message = '')
 * @method static void nullOrLower($value, $message = '')
 * @method static void nullOrUpper($value, $message = '')
 * @method static void nullOrLength($value, $length, $message = '')
 * @method static void nullOrMinLength($value, $min, $message = '')
 * @method static void nullOrMaxLength($value, $max, $message = '')
 * @method static void nullOrLengthBetween($value, $min, $max, $message = '')
 * @method static void nullOrFileExists($value, $message = '')
 * @method static void nullOrFile($value, $message = '')
 * @method static void nullOrDirectory($value, $message = '')
 * @method static void nullOrReadable($value, $message = '')
 * @method static void nullOrWritable($value, $message = '')
 * @method static void nullOrClassExists($value, $message = '')
 * @method static void nullOrSubclassOf($value, $class, $message = '')
 * @method static void nullOrImplementsInterface($value, $interface, $message = '')
 * @method static void nullOrPropertyExists($value, $property, $message = '')
 * @method static void nullOrPropertyNotExists($value, $property, $message = '')
 * @method static void nullOrMethodExists($value, $method, $message = '')
 * @method static void nullOrMethodNotExists($value, $method, $message = '')
 * @method static void nullOrKeyExists($value, $key, $message = '')
 * @method static void nullOrKeyNotExists($value, $key, $message = '')
 * @method static void nullOrCount($value, $key, $message = '')
 * @method static void nullOrMinCount($value, $min, $message = '')
 * @method static void nullOrMaxCount($value, $max, $message = '')
 * @method static void nullCountBetween($value, $min, $max, $message = '')
 * @method static void nullOrUuid($values, $message = '')
 * @method static void allString($values, $message = '')
 * @method static void allStringNotEmpty($values, $message = '')
 * @method static void allInteger($values, $message = '')
 * @method static void allIntegerish($values, $message = '')
 * @method static void allFloat($values, $message = '')
 * @method static void allNumeric($values, $message = '')
 * @method static void allBoolean($values, $message = '')
 * @method static void allScalar($values, $message = '')
 * @method static void allObject($values, $message = '')
 * @method static void allResource($values, $type = null, $message = '')
 * @method static void allIsCallable($values, $message = '')
 * @method static void allIsArray($values, $message = '')
 * @method static void allIsTraversable($values, $message = '')
 * @method static void allIsArrayAccessible($values, $message = '')
 * @method static void allIsCountable($values, $message = '')
 * @method static void allIsInstanceOf($values, $class, $message = '')
 * @method static void allNotInstanceOf($values, $class, $message = '')
 * @method static void allIsInstanceOfAny($values, $classes, $message = '')
 * @method static void allNull($values, $message = '')
 * @method static void allNotNull($values, $message = '')
 * @method static void allIsEmpty($values, $message = '')
 * @method static void allNotEmpty($values, $message = '')
 * @method static void allTrue($values, $message = '')
 * @method static void allFalse($values, $message = '')
 * @method static void allEq($values, $value2, $message = '')
 * @method static void allNotEq($values,$value2,  $message = '')
 * @method static void allSame($values, $value2, $message = '')
 * @method static void allNotSame($values, $value2, $message = '')
 * @method static void allGreaterThan($values, $value2, $message = '')
 * @method static void allGreaterThanEq($values, $value2, $message = '')
 * @method static void allLessThan($values, $value2, $message = '')
 * @method static void allLessThanEq($values, $value2, $message = '')
 * @method static void allRange($values, $min, $max, $message = '')
 * @method static void allOneOf($values, $values, $message = '')
 * @method static void allContains($values, $subString, $message = '')
 * @method static void allNotContains($values, $subString, $message = '')
 * @method static void allNotWhitespaceOnly($values, $message = '')
 * @method static void allStartsWith($values, $prefix, $message = '')
 * @method static void allStartsWithLetter($values, $message = '')
 * @method static void allEndsWith($values, $suffix, $message = '')
 * @method static void allRegex($values, $pattern, $message = '')
 * @method static void allAlpha($values, $message = '')
 * @method static void allDigits($values, $message = '')
 * @method static void allAlnum($values, $message = '')
 * @method static void allLower($values, $message = '')
 * @method static void allUpper($values, $message = '')
 * @method static void allLength($values, $length, $message = '')
 * @method static void allMinLength($values, $min, $message = '')
 * @method static void allMaxLength($values, $max, $message = '')
 * @method static void allLengthBetween($values, $min, $max, $message = '')
 * @method static void allFileExists($values, $message = '')
 * @method static void allFile($values, $message = '')
 * @method static void allDirectory($values, $message = '')
 * @method static void allReadable($values, $message = '')
 * @method static void allWritable($values, $message = '')
 * @method static void allClassExists($values, $message = '')
 * @method static void allSubclassOf($values, $class, $message = '')
 * @method static void allImplementsInterface($values, $interface, $message = '')
 * @method static void allPropertyExists($values, $property, $message = '')
 * @method static void allPropertyNotExists($values, $property, $message = '')
 * @method static void allMethodExists($values, $method, $message = '')
 * @method static void allMethodNotExists($values, $method, $message = '')
 * @method static void allKeyExists($values, $key, $message = '')
 * @method static void allKeyNotExists($values, $key, $message = '')
 * @method static void allCount($values, $key, $message = '')
 * @method static void allMinCount($values, $min, $message = '')
 * @method static void allMaxCount($values, $max, $message = '')
 * @method static void allCountBetween($values, $min, $max, $message = '')
 * @method static void allUuid($values, $message = '')
 *
 * @since  1.0
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class Assert
{
    public static function string($value, $message = '')
    {
        if (!is_string($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a string. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function stringNotEmpty($value, $message = '')
    {
        static::string($value, $message);
        static::notEq($value, '', $message);
    }

    public static function integer($value, $message = '')
    {
        if (!is_int($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an integer. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function integerish($value, $message = '')
    {
        if (!is_numeric($value) || $value != (int) $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an integerish value. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function float($value, $message = '')
    {
        if (!is_float($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a float. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function numeric($value, $message = '')
    {
        if (!is_numeric($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a numeric. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function natural($value, $message = '')
    {
        if (!is_int($value) || $value < 0) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a non-negative integer. Got %s',
                static::valueToString($value)
            ));
        }
    }

    public static function boolean($value, $message = '')
    {
        if (!is_bool($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a boolean. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function scalar($value, $message = '')
    {
        if (!is_scalar($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a scalar. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function object($value, $message = '')
    {
        if (!is_object($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an object. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function resource($value, $type = null, $message = '')
    {
        if (!is_resource($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a resource. Got: %s',
                static::typeToString($value)
            ));
        }

        if ($type && $type !== get_resource_type($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a resource of type %2$s. Got: %s',
                static::typeToString($value),
                $type
            ));
        }
    }

    public static function isCallable($value, $message = '')
    {
        if (!is_callable($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a callable. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isArray($value, $message = '')
    {
        if (!is_array($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isTraversable($value, $message = '')
    {
        @trigger_error(
            sprintf(
                'The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.',
                __METHOD__
            ),
            E_USER_DEPRECATED
        );

        if (!is_array($value) && !($value instanceof Traversable)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a traversable. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isArrayAccessible($value, $message = '')
    {
        if (!is_array($value) && !($value instanceof ArrayAccess)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array accessible. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isCountable($value, $message = '')
    {
        if (!is_array($value) && !($value instanceof Countable)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a countable. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isIterable($value, $message = '')
    {
        if (!is_array($value) && !($value instanceof Traversable)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an iterable. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isInstanceOf($value, $class, $message = '')
    {
        if (!($value instanceof $class)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an instance of %2$s. Got: %s',
                static::typeToString($value),
                $class
            ));
        }
    }

    public static function notInstanceOf($value, $class, $message = '')
    {
        if ($value instanceof $class) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an instance other than %2$s. Got: %s',
                static::typeToString($value),
                $class
            ));
        }
    }

    public static function isInstanceOfAny($value, array $classes, $message = '')
    {
        foreach ($classes as $class) {
            if ($value instanceof $class) {
                return;
            }
        }

        static::reportInvalidArgument(sprintf(
            $message ?: 'Expected an instance of any of %2$s. Got: %s',
            static::typeToString($value),
            implode(', ', array_map(array('static', 'valueToString'), $classes))
        ));
    }

    public static function isEmpty($value, $message = '')
    {
        if (!empty($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an empty value. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function notEmpty($value, $message = '')
    {
        if (empty($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a non-empty value. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function null($value, $message = '')
    {
        if (null !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected null. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function notNull($value, $message = '')
    {
        if (null === $value) {
            static::reportInvalidArgument(
                $message ?: 'Expected a value other than null.'
            );
        }
    }

    public static function true($value, $message = '')
    {
        if (true !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to be true. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function false($value, $message = '')
    {
        if (false !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to be false. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function eq($value, $value2, $message = '')
    {
        if ($value2 != $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value equal to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($value2)
            ));
        }
    }

    public static function notEq($value, $value2, $message = '')
    {
        if ($value2 == $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a different value than %s.',
                static::valueToString($value2)
            ));
        }
    }

    public static function same($value, $value2, $message = '')
    {
        if ($value2 !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value identical to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($value2)
            ));
        }
    }

    public static function notSame($value, $value2, $message = '')
    {
        if ($value2 === $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value not identical to %s.',
                static::valueToString($value2)
            ));
        }
    }

    public static function greaterThan($value, $limit, $message = '')
    {
        if ($value <= $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value greater than %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function greaterThanEq($value, $limit, $message = '')
    {
        if ($value < $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value greater than or equal to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function lessThan($value, $limit, $message = '')
    {
        if ($value >= $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value less than %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function lessThanEq($value, $limit, $message = '')
    {
        if ($value > $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value less than or equal to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function range($value, $min, $max, $message = '')
    {
        if ($value < $min || $value > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value between %2$s and %3$s. Got: %s',
                static::valueToString($value),
                static::valueToString($min),
                static::valueToString($max)
            ));
        }
    }

    public static function oneOf($value, array $values, $message = '')
    {
        if (!in_array($value, $values, true)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected one of: %2$s. Got: %s',
                static::valueToString($value),
                implode(', ', array_map(array('static', 'valueToString'), $values))
            ));
        }
    }

    public static function contains($value, $subString, $message = '')
    {
        if (false === strpos($value, $subString)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($subString)
            ));
        }
    }

    public static function notContains($value, $subString, $message = '')
    {
        if (false !== strpos($value, $subString)) {
            static::reportInvalidArgument(sprintf(
                $message ?: '%2$s was not expected to be contained in a value. Got: %s',
                static::valueToString($value),
                static::valueToString($subString)
            ));
        }
    }

    public static function notWhitespaceOnly($value, $message = '')
    {
        if (preg_match('/^\s*$/', $value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a non-whitespace string. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function startsWith($value, $prefix, $message = '')
    {
        if (0 !== strpos($value, $prefix)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to start with %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($prefix)
            ));
        }
    }

    public static function startsWithLetter($value, $message = '')
    {
        $valid = isset($value[0]);

        if ($valid) {
            $locale = setlocale(LC_CTYPE, 0);
            setlocale(LC_CTYPE, 'C');
            $valid = ctype_alpha($value[0]);
            setlocale(LC_CTYPE, $locale);
        }

        if (!$valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to start with a letter. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function endsWith($value, $suffix, $message = '')
    {
        if ($suffix !== substr($value, -static::strlen($suffix))) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to end with %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($suffix)
            ));
        }
    }

    public static function regex($value, $pattern, $message = '')
    {
        if (!preg_match($pattern, $value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The value %s does not match the expected pattern.',
                static::valueToString($value)
            ));
        }
    }

    public static function alpha($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_alpha($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain only letters. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function digits($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_digit($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain digits only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function alnum($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_alnum($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain letters and digits only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function lower($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_lower($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain lowercase characters only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function upper($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_upper($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain uppercase characters only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function length($value, $length, $message = '')
    {
        if ($length !== static::strlen($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain %2$s characters. Got: %s',
                static::valueToString($value),
                $length
            ));
        }
    }

    public static function minLength($value, $min, $message = '')
    {
        if (static::strlen($value) < $min) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain at least %2$s characters. Got: %s',
                static::valueToString($value),
                $min
            ));
        }
    }

    public static function maxLength($value, $max, $message = '')
    {
        if (static::strlen($value) > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain at most %2$s characters. Got: %s',
                static::valueToString($value),
                $max
            ));
        }
    }

    public static function lengthBetween($value, $min, $max, $message = '')
    {
        $length = static::strlen($value);

        if ($length < $min || $length > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s',
                static::valueToString($value),
                $min,
                $max
            ));
        }
    }

    public static function fileExists($value, $message = '')
    {
        static::string($value);

        if (!file_exists($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The file %s does not exist.',
                static::valueToString($value)
            ));
        }
    }

    public static function file($value, $message = '')
    {
        static::fileExists($value, $message);

        if (!is_file($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is not a file.',
                static::valueToString($value)
            ));
        }
    }

    public static function directory($value, $message = '')
    {
        static::fileExists($value, $message);

        if (!is_dir($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is no directory.',
                static::valueToString($value)
            ));
        }
    }

    public static function readable($value, $message = '')
    {
        if (!is_readable($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is not readable.',
                static::valueToString($value)
            ));
        }
    }

    public static function writable($value, $message = '')
    {
        if (!is_writable($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is not writable.',
                static::valueToString($value)
            ));
        }
    }

    public static function classExists($value, $message = '')
    {
        if (!class_exists($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an existing class name. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function subclassOf($value, $class, $message = '')
    {
        if (!is_subclass_of($value, $class)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a sub-class of %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($class)
            ));
        }
    }

    public static function implementsInterface($value, $interface, $message = '')
    {
        if (!in_array($interface, class_implements($value))) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an implementation of %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($interface)
            ));
        }
    }

    public static function propertyExists($classOrObject, $property, $message = '')
    {
        if (!property_exists($classOrObject, $property)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the property %s to exist.',
                static::valueToString($property)
            ));
        }
    }

    public static function propertyNotExists($classOrObject, $property, $message = '')
    {
        if (property_exists($classOrObject, $property)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the property %s to not exist.',
                static::valueToString($property)
            ));
        }
    }

    public static function methodExists($classOrObject, $method, $message = '')
    {
        if (!method_exists($classOrObject, $method)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the method %s to exist.',
                static::valueToString($method)
            ));
        }
    }

    public static function methodNotExists($classOrObject, $method, $message = '')
    {
        if (method_exists($classOrObject, $method)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the method %s to not exist.',
                static::valueToString($method)
            ));
        }
    }

    public static function keyExists($array, $key, $message = '')
    {
        if (!array_key_exists($key, $array)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the key %s to exist.',
                static::valueToString($key)
            ));
        }
    }

    public static function keyNotExists($array, $key, $message = '')
    {
        if (array_key_exists($key, $array)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the key %s to not exist.',
                static::valueToString($key)
            ));
        }
    }

    public static function count($array, $number, $message = '')
    {
        static::eq(
            count($array),
            $number,
            $message ?: sprintf('Expected an array to contain %d elements. Got: %d.', $number, count($array))
        );
    }

    public static function minCount($array, $min, $message = '')
    {
        if (count($array) < $min) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array to contain at least %2$d elements. Got: %d',
                count($array),
                $min
            ));
        }
    }

    public static function maxCount($array, $max, $message = '')
    {
        if (count($array) > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array to contain at most %2$d elements. Got: %d',
                count($array),
                $max
            ));
        }
    }

    public static function countBetween($array, $min, $max, $message = '')
    {
        $count = count($array);

        if ($count < $min || $count > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d',
                $count,
                $min,
                $max
            ));
        }
    }

    public static function uuid($value, $message = '')
    {
        $value = str_replace(array('urn:', 'uuid:', '{', '}'), '', $value);

        // The nil UUID is special form of UUID that is specified to have all
        // 128 bits set to zero.
        if ('00000000-0000-0000-0000-000000000000' === $value) {
            return;
        }

        if (!preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Value %s is not a valid UUID.',
                static::valueToString($value)
            ));
        }
    }

    public static function throws(Closure $expression, $class = 'Exception', $message = '')
    {
        static::string($class);

        $actual = 'none';

        try {
            $expression();
        } catch (Exception $e) {
            $actual = get_class($e);
            if ($e instanceof $class) {
                return;
            }
        } catch (Throwable $e) {
            $actual = get_class($e);
            if ($e instanceof $class) {
                return;
            }
        }

        static::reportInvalidArgument($message ?: sprintf(
            'Expected to throw "%s", got "%s"',
            $class,
            $actual
        ));
    }

    public static function __callStatic($name, $arguments)
    {
        if ('nullOr' === substr($name, 0, 6)) {
            if (null !== $arguments[0]) {
                $method = lcfirst(substr($name, 6));
                call_user_func_array(array('static', $method), $arguments);
            }

            return;
        }

        if ('all' === substr($name, 0, 3)) {
            static::isIterable($arguments[0]);

            $method = lcfirst(substr($name, 3));
            $args = $arguments;

            foreach ($arguments[0] as $entry) {
                $args[0] = $entry;

                call_user_func_array(array('static', $method), $args);
            }

            return;
        }

        throw new BadMethodCallException('No such method: '.$name);
    }

    protected static function valueToString($value)
    {
        if (null === $value) {
            return 'null';
        }

        if (true === $value) {
            return 'true';
        }

        if (false === $value) {
            return 'false';
        }

        if (is_array($value)) {
            return 'array';
        }

        if (is_object($value)) {
            return get_class($value);
        }

        if (is_resource($value)) {
            return 'resource';
        }

        if (is_string($value)) {
            return '"'.$value.'"';
        }

        return (string) $value;
    }

    protected static function typeToString($value)
    {
        return is_object($value) ? get_class($value) : gettype($value);
    }

    protected static function strlen($value)
    {
        if (!function_exists('mb_detect_encoding')) {
            return strlen($value);
        }

        if (false === $encoding = mb_detect_encoding($value)) {
            return strlen($value);
        }

        return mb_strwidth($value, $encoding);
    }

    protected static function reportInvalidArgument($message)
    {
        throw new InvalidArgumentException($message);
    }

    private function __construct()
    {
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc\Tests;

use StdClass;
use PHPUnit\Framework\TestCase;
use AdvancedJsonRpc\{Message, Request, Response, SuccessResponse, ErrorResponse, Notification};

class MessageTest extends TestCase
{
    public function testParseRequest()
    {
        $msg = Message::parse('{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}');
        $this->assertInstanceOf(Request::class, $msg);
        $this->assertEquals([
            'jsonrpc' => '2.0',
            'method' => 'subtract',
            'params' => [42, 23],
            'id' => 1
        ], get_object_vars($msg));
    }

    public function testParseNotification()
    {
        $msg = Message::parse('{"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}');
        $this->assertInstanceOf(Notification::class, $msg);
        $this->assertEquals([
            'jsonrpc' => '2.0',
            'method' => 'update',
            'params' => [1,2,3,4,5]
        ], get_object_vars($msg));
    }

    public function testParseSuccessResponse()
    {
        $msg = Message::parse('{"jsonrpc": "2.0", "result": 19, "id": 1}');
        $this->assertInstanceOf(SuccessResponse::class, $msg);
        $this->assertEquals([
            'jsonrpc' => '2.0',
            'result' => 19,
            'id' => 1
        ], get_object_vars($msg));
    }

    public function testParseErrorResponse()
    {
        $msg = Message::parse('{"jsonrpc": "2.0", "error": {"code": -32602, "message": "invalid params"}, "id": 1}');
        $this->assertInstanceOf(ErrorResponse::class, $msg);
        $this->assertEquals([
            'jsonrpc' => '2.0',
            'error' => [
                'code' => -32602,
                'message' => 'invalid params',
                'data' => null
            ],
            'id' => 1
        ], json_decode(json_encode($msg), true));
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc\Tests;

class Target
{
    public $nestedTarget;

    private $calls = [];

    public function __construct(array &$calls)
    {
        $this->calls = &$calls;
    }

    public function someMethodWithoutArgs()
    {
        $this->calls[] = new MethodCall('someMethodWithoutArgs', func_get_args());
        return 'Hello World';
    }

    public function someMethodWithTypeHint(Argument $arg)
    {
        $this->calls[] = new MethodCall('someMethodWithTypeHint', func_get_args());
        return 'Hello World';
    }

    /**
     * @param Argument[] $arg
     */
    public function someMethodWithArrayParamTag($arg)
    {
        $this->calls[] = new MethodCall('someMethodWithArrayParamTag', func_get_args());
        return 'Hello World';
    }

    /**
     * @param null|Argument[] $arg
     */
    public function someMethodWithUnionTypeParamTag($arg)
    {
        $this->calls[] = new MethodCall('someMethodWithUnionTypeParamTag', func_get_args());
        return 'Hello World';
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc\Tests;

/**
 * Serves as type hint for arguments
 */
class Argument
{
    /**
     * @var string
     */
    public $aProperty;

    public function __construct(string $aProperty = null)
    {
        $this->aProperty = $aProperty;
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc\Tests;

use StdClass;
use PHPUnit\Framework\TestCase;
use AdvancedJsonRpc\{Dispatcher, Request};

class DispatcherTest extends TestCase
{
    private $calls;
    private $callsOfNestedTarget;
    private $dispatcher;

    public function setUp()
    {
        $this->calls = [];
        $this->callsOfNestedTarget = [];
        $target = new Target($this->calls);
        $target->nestedTarget = new Target($this->callsOfNestedTarget);
        $this->dispatcher = new Dispatcher($target);
    }

    public function testCallMethodWithoutArgs()
    {
        $result = $this->dispatcher->dispatch((string)new Request(1, 'someMethodWithoutArgs'));
        $this->assertEquals('Hello World', $result);
        $this->assertEquals($this->calls, [new MethodCall('someMethodWithoutArgs', [])]);
    }

    public function testCallMethodWithTypeHintWithPositionalArgs()
    {
        $result = $this->dispatcher->dispatch((string)new Request(1, 'someMethodWithTypeHint', [new Argument('whatever')]));
        $this->assertEquals('Hello World', $result);
        $this->assertEquals($this->calls, [new MethodCall('someMethodWithTypeHint', [new Argument('whatever')])]);
    }

    public function testCallMethodWithTypeHintWithNamedArgs()
    {
        $result = $this->dispatcher->dispatch((string)new Request(1, 'someMethodWithTypeHint', ['arg' => new Argument('whatever')]));
        $this->assertEquals('Hello World', $result);
        $this->assertEquals($this->calls, [new MethodCall('someMethodWithTypeHint', [new Argument('whatever')])]);
    }

    public function testCallMethodWithArrayParamTag()
    {
        $result = $this->dispatcher->dispatch((string)new Request(1, 'someMethodWithArrayParamTag', ['arg' => [new Argument('whatever')]]));
        $this->assertEquals('Hello World', $result);
        $this->assertEquals($this->calls, [new MethodCall('someMethodWithArrayParamTag', [[new Argument('whatever')]])]);
    }

    public function testCallMethodWithUnionTypeParamTag()
    {
        $result = $this->dispatcher->dispatch((string)new Request(1, 'someMethodWithUnionTypeParamTag', ['arg' => [new Argument('whatever')]]));
        $this->assertEquals('Hello World', $result);
        $this->assertEquals($this->calls, [new MethodCall('someMethodWithUnionTypeParamTag', [[new Argument('whatever')]])]);
    }

    public function testCallMethodWithTypeHintWithNamedArgsOnNestedTarget()
    {
        $result = $this->dispatcher->dispatch((string)new Request(1, 'nestedTarget->someMethodWithTypeHint', ['arg' => new Argument('whatever')]));
        $this->assertEquals('Hello World', $result);
        $this->assertEquals($this->calls, []);
        $this->assertEquals($this->callsOfNestedTarget, [new MethodCall('someMethodWithTypeHint', [new Argument('whatever')])]);
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc\Tests;

class MethodCall
{
    public $method;
    public $args;

    public function __construct(string $method, array $args)
    {
        $this->method = $method;
        $this->args = $args;
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc;

/**
 * When a rpc call is made, the Server MUST reply with a Response, except for in the case of Notifications. The Response
 * is expressed as a single JSON Object, with the following members:
 */
abstract class Response extends Message
{
    /**
     * This member is REQUIRED. It MUST be the same as the value of the id member in the Request Object. If there was an
     * error in detecting the id in the Request object (e.g. Parse error/Invalid Request), it MUST be Null.
     *
     * @var int|string
     */
    public $id;

    /**
     * A message is considered a Response if it has an ID and either a result or an error
     *
     * @param object $msg A decoded message body
     * @return bool
     */
    public static function isResponse($msg): bool
    {
        return is_object($msg) && property_exists($msg, 'id') && (property_exists($msg, 'result') || isset($msg->error));
    }

    /**
     * @param int|string $id
     * @param mixed $result
     * @param ResponseError $error
     */
    public function __construct($id)
    {
        $this->id = $id;
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc;

use Sabre\Event\Loop;
use JsonMapper;
use JsonMapper_Exception;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\Types;
use ReflectionException;
use ReflectionMethod;

class Dispatcher
{
    /**
     * @var object
     */
    private $target;

    /**
     * @var string
     */
    private $delimiter;

    /**
     * method => ReflectionMethod[]
     *
     * @var ReflectionMethod
     */
    private $methods;

    /**
     * @var \phpDocumentor\Reflection\DocBlockFactory
     */
    private $docBlockFactory;

    /**
     * @var \phpDocumentor\Reflection\Types\ContextFactory
     */
    private $contextFactory;

    /**
     * @param object $target    The target object that should receive the method calls
     * @param string $delimiter A delimiter for method calls on properties, for example someProperty->someMethod
     */
    public function __construct($target, $delimiter = '->')
    {
        $this->target = $target;
        $this->delimiter = $delimiter;
        $this->docBlockFactory = DocBlockFactory::createInstance();
        $this->contextFactory = new Types\ContextFactory();
        $this->mapper = new JsonMapper();
    }

    /**
     * Calls the appropiate method handler for an incoming Message
     *
     * @param string|object $msg The incoming message
     * @return Result|void
     */
    public function dispatch($msg)
    {
        if (is_string($msg)) {
            $msg = json_decode($msg);
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new Error(json_last_error_msg(), ErrorCode::PARSE_ERROR);
            }
        }
        // Find out the object and function that should be called
        $obj = $this->target;
        $parts = explode($this->delimiter, $msg->method);
        // The function to call is always the last part of the method
        $fn = array_pop($parts);
        // For namespaced methods like textDocument/didOpen, call the didOpen method on the $textDocument property
        // For simple methods like initialize, shutdown, exit, this loop will simply not be entered and $obj will be
        // the target
        foreach ($parts as $part) {
            if (!isset($obj->$part)) {
                throw new Error("Method {$msg->method} is not implemented", ErrorCode::METHOD_NOT_FOUND);
            }
            $obj = $obj->$part;
        }
        if (!isset($this->methods[$msg->method])) {
            try {
                $method = new ReflectionMethod($obj, $fn);
                $this->methods[$msg->method] = $method;
            } catch (ReflectionException $e) {
                throw new Error($e->getMessage(), ErrorCode::METHOD_NOT_FOUND, null, $e);
            }
        }
        $method = $this->methods[$msg->method];
        $parameters = $method->getParameters();
        if ($method->getDocComment()) {
            $docBlock = $this->docBlockFactory->create(
                $method->getDocComment(),
                $this->contextFactory->createFromReflector($method->getDeclaringClass())
            );
            $paramTags = $docBlock->getTagsByName('param');
        }
        $args = [];
        if (isset($msg->params)) {
            // Find out the position
            if (is_array($msg->params)) {
                $args = $msg->params;
            } else if (is_object($msg->params)) {
                foreach (get_object_vars($msg->params) as $key => $value) {
                    $position = -1;
                    foreach ($parameters as $pos => $parameter) {
                        if ($parameter->name === $key) {
                            $position = $pos;
                            break;
                        }
                    }
                    if ($position === -1) {
                        // Unknown parameter, ignore
                        continue;
                    }
                    $args[$position] = $value;
                }
            } else {
                throw new Error('Params must be structured or omitted', ErrorCode::INVALID_REQUEST);
            }
            foreach ($args as $position => $value) {
                try {
                    // If the type is structured (array or object), map it with JsonMapper
                    if (is_object($value)) {
                        // Does the parameter have a type hint?
                        $param = $parameters[$position];
                        if ($param->hasType()) {
                            $class = (string)$param->getType();
                            $value = $this->mapper->map($value, new $class());
                        }
                    } else if (is_array($value) && isset($docBlock)) {
                        // Get the array type from the DocBlock
                        $type = $paramTags[$position]->getType();
                        // For union types, use the first one that is a class array (often it is SomeClass[]|null)
                        if ($type instanceof Types\Compound) {
                            for ($i = 0; $t = $type->get($i); $i++) {
                                if (
                                    $t instanceof Types\Array_
                                    && $t->getValueType() instanceof Types\Object_
                                    && (string)$t->getValueType() !== 'object'
                                ) {
                                    $class = (string)$t->getValueType()->getFqsen();
                                    $value = $this->mapper->mapArray($value, [], $class);
                                    break;
                                }
                            }
                        } else if ($type instanceof Types\Array_) {
                            $class = (string)$type->getValueType()->getFqsen();
                            $value = $this->mapper->mapArray($value, [], $class);
                        } else {
                            throw new Error('Type is not matching @param tag', ErrorCode::INVALID_PARAMS);
                        }
                    }
                } catch (JsonMapper_Exception $e) {
                    throw new Error($e->getMessage(), ErrorCode::INVALID_PARAMS, null, $e);
                }
                $args[$position] = $value;
            }
        }
        ksort($args);
        $result = $obj->$fn(...$args);
        return $result;
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc;

use Exception;
use Throwable;

class Error extends Exception
{
    /**
     * A Number that indicates the error type that occurred. This MUST be an integer.
     *
     * @var int
     */
    public $code;

    /**
     * A String providing a short description of the error. The message SHOULD be limited to a concise single sentence.
     *
     * @var string
     */
    public $message;

    /**
     * A Primitive or Structured value that contains additional information about the error. This may be omitted. The
     * value of this member is defined by the Server (e.g. detailed error information, nested errors etc.).
     *
     * @var mixed
     */
    public $data;

    public function __construct(string $message, int $code, $data = null, Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
        $this->data = $data;
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc;

/**
 * When a rpc call is made, the Server MUST reply with a Response, except for in the case of Notifications. The Response
 * is expressed as a single JSON Object, with the following members:
 */
class SuccessResponse extends Response
{
    /**
     * This member is REQUIRED on success. This member MUST NOT exist if there was an error invoking the method. The
     * value of this member is determined by the method invoked on the Server.
     *
     * @var mixed
     */
    public $result;

    /**
     * A message is considered a SuccessResponse if it has an ID and either a result
     *
     * @param object $msg A decoded message body
     * @return bool
     */
    public static function isSuccessResponse($msg): bool
    {
        return is_object($msg) && property_exists($msg, 'id') && property_exists($msg, 'result');
    }

    /**
     * @param int|string $id
     * @param mixed $result
     */
    public function __construct($id, $result)
    {
        parent::__construct($id);
        $this->result = $result;
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc;

/**
 * When a rpc call is made, the Server MUST reply with a Response, except for in the case of Notifications. The Response
 * is expressed as a single JSON Object, with the following members:
 */
class ErrorResponse extends Response
{
    /**
     * This member is REQUIRED on error. This member MUST NOT exist if there was no error triggered during invocation.
     * The value for this member MUST be an Object as defined in section 5.1.
     *
     * @var \AdvancedJsonRpc\Error
     */
    public $error;

    /**
     * A message is considered a Response if it has an ID and either a result or an error
     *
     * @param object $msg A decoded message body
     * @return bool
     */
    public static function isErrorResponse($msg): bool
    {
        return is_object($msg) && isset($msg->id) && isset($msg->error);
    }

    /**
     * @param int|string $id
     * @param \AdvancedJsonRpc\Error $error
     */
    public function __construct($id, Error $error)
    {
        parent::__construct($id);
        $this->error = $error;
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc;

/**
 * A Notification is a Request object without an "id" member. A Request object that is a Notification signifies the
 * Client's lack of interest in the corresponding Response object, and as such no Response object needs to be returned
 * to the client. The Server MUST NOT reply to a Notification, including those that are within a batch request.
 * Notifications are not confirmable by definition, since they do not have a Response object to be returned. As such,
 * the Client would not be aware of any errors (like e.g. "Invalid params","Internal error").
 */
class Notification extends Message
{
    /**
     * A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by a
     * period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be used
     * for anything else.
     *
     * @var string
     */
    public $method;

    /**
     * A Structured value that holds the parameter values to be used during the invocation of the method. This member
     * MAY be omitted. If present, parameters for the rpc call MUST be provided as a Structured value. Either
     * by-position through an Array or by-name through an Object. by-position: params MUST be an Array, containing the
     * values in the Server expected order. by-name: params MUST be an Object, with member names that match the Server
     * expected parameter names. The absence of expected names MAY result in an error being generated. The names MUST
     * match exactly, including case, to the method's expected parameters.
     *
     * @var object|array|null
     */
    public $params;

    /**
     * A message is considered a Notification if it has a method but no ID.
     *
     * @param object $msg A decoded message body
     * @return bool
     */
    public static function isNotification($msg): bool
    {
        return is_object($msg) && !property_exists($msg, 'id') && isset($msg->method);
    }

    /**
     * @param string $method
     * @param mixed $params
     */
    public function __construct(string $method, $params = null)
    {
        $this->method = $method;
        $this->params = $params;
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc;

/**
 * The error codes from and including -32768 to -32000 are reserved for pre-defined errors. Any code within this range,
 * but not defined explicitly below is reserved for future use. The error codes are nearly the same as those suggested
 * for XML-RPC at the following url: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
 * The remainder of the space is available for application defined errors.
 */
abstract class ErrorCode
{
    /**
     * Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
     */
    const PARSE_ERROR = -32700;

    /**
     * The JSON sent is not a valid Request object.
     */
    const INVALID_REQUEST = -32600;

    /**
     * The method does not exist / is not available.
     */
    const METHOD_NOT_FOUND = -32601;

    /**
     * Invalid method parameter(s).
     */
    const INVALID_PARAMS = -32602;

    /**
     * Internal JSON-RPC error.
     */
    const INTERNAL_ERROR = -32603;

    /**
     * Reserved for implementation-defined server-errors.
     */
    const SERVER_ERROR_START = -32099;

    /**
     * Reserved for implementation-defined server-errors.
     */
    const SERVER_ERROR_END = -32000;
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc;

/**
 * Base message
 */
abstract class Message
{
    /**
     * A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
     *
     * @var string
     */
    public $jsonrpc = '2.0';

    /**
     * Returns the appropiate Message subclass
     *
     * @param string $msg
     * @return Message
     */
    public static function parse(string $msg): Message
    {
        $decoded = json_decode($msg);
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Error(json_last_error_msg(), ErrorCode::PARSE_ERROR);
        }
        if (Notification::isNotification($decoded)) {
            $obj = new Notification($decoded->method, $decoded->params ?? null);
        } else if (Request::isRequest($decoded)) {
            $obj = new Request($decoded->id, $decoded->method, $decoded->params ?? null);
        } else if (SuccessResponse::isSuccessResponse($decoded)) {
            $obj = new SuccessResponse($decoded->id, $decoded->result);
        } else if (ErrorResponse::isErrorResponse($decoded)) {
            $obj = new ErrorResponse($decoded->id, new Error($decoded->error->message, $decoded->error->code, $decoded->error->data ?? null));
        } else {
            throw new Error('Invalid message', ErrorCode::INVALID_REQUEST);
        }
        return $obj;
    }

    public function __toString(): string
    {
        $encoded = json_encode($this);
        if ($encoded === false) {
            throw new Error(json_last_error_msg(), ErrorCode::INTERNAL_ERROR);
        }
        return $encoded;
    }
}
<?php
declare(strict_types = 1);

namespace AdvancedJsonRpc;

/**
 * A rpc call is represented by sending a Request object to a Server
 */
class Request extends Message
{
    /**
     * An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is
     * not included it is assumed to be a notification. The value SHOULD normally not be NULL and Numbers SHOULD NOT
     * contain fractional parts.
     *
     * @var int|string
     */
    public $id;

    /**
     * A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by a
     * period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be used
     * for anything else.
     *
     * @var string
     */
    public $method;

    /**
     * A Structured value that holds the parameter values to be used during the invocation of the method. This member
     * MAY be omitted. If present, parameters for the rpc call MUST be provided as a Structured value. Either
     * by-position through an Array or by-name through an Object. by-position: params MUST be an Array, containing the
     * values in the Server expected order. by-name: params MUST be an Object, with member names that match the Server
     * expected parameter names. The absence of expected names MAY result in an error being generated. The names MUST
     * match exactly, including case, to the method's expected parameters.
     *
     * @var object|array|null
     */
    public $params;

    /**
     * A message is considered a Request if it has an ID and a method.
     *
     * @param object $msg A decoded message body
     * @return bool
     */
    public static function isRequest($msg): bool
    {
        return is_object($msg) && property_exists($msg, 'id') && isset($msg->method);
    }

    /**
     * @param string|int $id
     * @param string $method
     * @param object|array $params
     */
    public function __construct($id, string $method, $params = null)
    {
        $this->id = $id;
        $this->method = $method;
        $this->params = $params;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines a constraint.
 */
class Constraint implements ConstraintInterface
{
    /* operator integer values */
    const OP_EQ = 0;
    const OP_LT = 1;
    const OP_LE = 2;
    const OP_GT = 3;
    const OP_GE = 4;
    const OP_NE = 5;

    /**
     * Operator to integer translation table.
     *
     * @var array
     */
    private static $transOpStr = array(
        '=' => self::OP_EQ,
        '==' => self::OP_EQ,
        '<' => self::OP_LT,
        '<=' => self::OP_LE,
        '>' => self::OP_GT,
        '>=' => self::OP_GE,
        '<>' => self::OP_NE,
        '!=' => self::OP_NE,
    );

    /**
     * Integer to operator translation table.
     *
     * @var array
     */
    private static $transOpInt = array(
        self::OP_EQ => '==',
        self::OP_LT => '<',
        self::OP_LE => '<=',
        self::OP_GT => '>',
        self::OP_GE => '>=',
        self::OP_NE => '!=',
    );

    /** @var string */
    protected $operator;

    /** @var string */
    protected $version;

    /** @var string */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if ($provider instanceof $this) {
            return $this->matchSpecific($provider);
        }

        // turn matching around to find a match
        return $provider->matches($this);
    }

    /**
     * @param string $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    /**
     * Get all supported comparison operators.
     *
     * @return array
     */
    public static function getSupportedOperators()
    {
        return array_keys(self::$transOpStr);
    }

    /**
     * Sets operator and version to compare with.
     *
     * @param string $operator
     * @param string $version
     *
     * @throws \InvalidArgumentException if invalid operator is given.
     */
    public function __construct($operator, $version)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf(
                'Invalid operator "%s" given, expected one of: %s',
                $operator,
                implode(', ', self::getSupportedOperators())
            ));
        }

        $this->operator = self::$transOpStr[$operator];
        $this->version = $version;
    }

    /**
     * @param string $a
     * @param string $b
     * @param string $operator
     * @param bool $compareBranches
     *
     * @throws \InvalidArgumentException if invalid operator is given.
     *
     * @return bool
     */
    public function versionCompare($a, $b, $operator, $compareBranches = false)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf(
                'Invalid operator "%s" given, expected one of: %s',
                $operator,
                implode(', ', self::getSupportedOperators())
            ));
        }

        $aIsBranch = 'dev-' === substr($a, 0, 4);
        $bIsBranch = 'dev-' === substr($b, 0, 4);

        if ($aIsBranch && $bIsBranch) {
            return $operator === '==' && $a === $b;
        }

        // when branches are not comparable, we make sure dev branches never match anything
        if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
            return false;
        }

        return version_compare($a, $b, $operator);
    }

    /**
     * @param Constraint $provider
     * @param bool $compareBranches
     *
     * @return bool
     */
    public function matchSpecific(Constraint $provider, $compareBranches = false)
    {
        $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]);
        $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]);

        $isEqualOp = self::OP_EQ === $this->operator;
        $isNonEqualOp = self::OP_NE === $this->operator;
        $isProviderEqualOp = self::OP_EQ === $provider->operator;
        $isProviderNonEqualOp = self::OP_NE === $provider->operator;

        // '!=' operator is match when other operator is not '==' operator or version is not match
        // these kinds of comparisons always have a solution
        if ($isNonEqualOp || $isProviderNonEqualOp) {
            return !$isEqualOp && !$isProviderEqualOp
                || $this->versionCompare($provider->version, $this->version, '!=', $compareBranches);
        }

        // an example for the condition is <= 2.0 & < 1.0
        // these kinds of comparisons always have a solution
        if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) {
            return true;
        }

        if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->operator], $compareBranches)) {
            // special case, e.g. require >= 1.0 and provide < 1.0
            // 1.0 >= 1.0 but 1.0 is outside of the provided interval
            if ($provider->version === $this->version
                && self::$transOpInt[$provider->operator] === $providerNoEqualOp
                && self::$transOpInt[$this->operator] !== $noEqualOp) {
                return false;
            }

            return true;
        }

        return false;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return self::$transOpInt[$this->operator] . ' ' . $this->version;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

interface ConstraintInterface
{
    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider);

    /**
     * @return string
     */
    public function getPrettyString();

    /**
     * @return string
     */
    public function __toString();
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines a conjunctive or disjunctive set of constraints.
 */
class MultiConstraint implements ConstraintInterface
{
    /** @var ConstraintInterface[] */
    protected $constraints;

    /** @var string */
    protected $prettyString;

    /** @var bool */
    protected $conjunctive;

    /**
     * @param ConstraintInterface[] $constraints A set of constraints
     * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive
     */
    public function __construct(array $constraints, $conjunctive = true)
    {
        $this->constraints = $constraints;
        $this->conjunctive = $conjunctive;
    }

    /**
     * @return ConstraintInterface[]
     */
    public function getConstraints()
    {
        return $this->constraints;
    }

    /**
     * @return bool
     */
    public function isConjunctive()
    {
        return $this->conjunctive;
    }

    /**
     * @return bool
     */
    public function isDisjunctive()
    {
        return !$this->conjunctive;
    }

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if (false === $this->conjunctive) {
            foreach ($this->constraints as $constraint) {
                if ($constraint->matches($provider)) {
                    return true;
                }
            }

            return false;
        }

        foreach ($this->constraints as $constraint) {
            if (!$constraint->matches($provider)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param string $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    /**
     * @return string
     */
    public function __toString()
    {
        $constraints = array();
        foreach ($this->constraints as $constraint) {
            $constraints[] = (string) $constraint;
        }

        return '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']';
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines the absence of a constraint.
 */
class EmptyConstraint implements ConstraintInterface
{
    /** @var string */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        return true;
    }

    /**
     * @param $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return '[]';
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

trigger_error('The ' . __CLASS__ . ' abstract class is deprecated, there is no replacement for it, it will be removed in the next major version.', E_USER_DEPRECATED);

/**
 * Base constraint class.
 */
abstract class AbstractConstraint implements ConstraintInterface
{
    /** @var string */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if ($provider instanceof $this) {
            // see note at bottom of this class declaration
            return $this->matchSpecific($provider);
        }

        // turn matching around to find a match
        return $provider->matches($this);
    }

    /**
     * @param string $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    // implementations must implement a method of this format:
    // not declared abstract here because type hinting violates parameter coherence (TODO right word?)
    // public function matchSpecific(<SpecificConstraintType> $provider);
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Semver
{
    const SORT_ASC = 1;
    const SORT_DESC = -1;

    /** @var VersionParser */
    private static $versionParser;

    /**
     * Determine if given version satisfies given constraints.
     *
     * @param string $version
     * @param string $constraints
     *
     * @return bool
     */
    public static function satisfies($version, $constraints)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }

        $versionParser = self::$versionParser;
        $provider = new Constraint('==', $versionParser->normalize($version));
        $constraints = $versionParser->parseConstraints($constraints);

        return $constraints->matches($provider);
    }

    /**
     * Return all versions that satisfy given constraints.
     *
     * @param array $versions
     * @param string $constraints
     *
     * @return array
     */
    public static function satisfiedBy(array $versions, $constraints)
    {
        $versions = array_filter($versions, function ($version) use ($constraints) {
            return Semver::satisfies($version, $constraints);
        });

        return array_values($versions);
    }

    /**
     * Sort given array of versions.
     *
     * @param array $versions
     *
     * @return array
     */
    public static function sort(array $versions)
    {
        return self::usort($versions, self::SORT_ASC);
    }

    /**
     * Sort given array of versions in reverse.
     *
     * @param array $versions
     *
     * @return array
     */
    public static function rsort(array $versions)
    {
        return self::usort($versions, self::SORT_DESC);
    }

    /**
     * @param array $versions
     * @param int $direction
     *
     * @return array
     */
    private static function usort(array $versions, $direction)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }

        $versionParser = self::$versionParser;
        $normalized = array();

        // Normalize outside of usort() scope for minor performance increase.
        // Creates an array of arrays: [[normalized, key], ...]
        foreach ($versions as $key => $version) {
            $normalized[] = array($versionParser->normalize($version), $key);
        }

        usort($normalized, function (array $left, array $right) use ($direction) {
            if ($left[0] === $right[0]) {
                return 0;
            }

            if (Comparator::lessThan($left[0], $right[0])) {
                return -$direction;
            }

            return $direction;
        });

        // Recreate input array, using the original indexes which are now in sorted order.
        $sorted = array();
        foreach ($normalized as $item) {
            $sorted[] = $versions[$item[1]];
        }

        return $sorted;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\EmptyConstraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Constraint\Constraint;

/**
 * Version parser.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VersionParser
{
    /**
     * Regex to match pre-release data (sort of).
     *
     * Due to backwards compatibility:
     *   - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted.
     *   - Only stabilities as recognized by Composer are allowed to precede a numerical identifier.
     *   - Numerical-only pre-release identifiers are not supported, see tests.
     *
     *                        |--------------|
     * [major].[minor].[patch] -[pre-release] +[build-metadata]
     *
     * @var string
     */
    private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';

    /** @var array */
    private static $stabilities = array('stable', 'RC', 'beta', 'alpha', 'dev');

    /**
     * Returns the stability of a version.
     *
     * @param string $version
     *
     * @return string
     */
    public static function parseStability($version)
    {
        $version = preg_replace('{#.+$}i', '', $version);

        if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) {
            return 'dev';
        }

        preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match);
        if (!empty($match[3])) {
            return 'dev';
        }

        if (!empty($match[1])) {
            if ('beta' === $match[1] || 'b' === $match[1]) {
                return 'beta';
            }
            if ('alpha' === $match[1] || 'a' === $match[1]) {
                return 'alpha';
            }
            if ('rc' === $match[1]) {
                return 'RC';
            }
        }

        return 'stable';
    }

    /**
     * @param string $stability
     *
     * @return string
     */
    public static function normalizeStability($stability)
    {
        $stability = strtolower($stability);

        return $stability === 'rc' ? 'RC' : $stability;
    }

    /**
     * Normalizes a version string to be able to perform comparisons on it.
     *
     * @param string $version
     * @param string $fullVersion optional complete version string to give more context
     *
     * @throws \UnexpectedValueException
     *
     * @return string
     */
    public function normalize($version, $fullVersion = null)
    {
        $version = trim($version);
        if (null === $fullVersion) {
            $fullVersion = $version;
        }

        // strip off aliasing
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
            $version = $match[1];
        }

        // match master-like branches
        if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
            return '9999999-dev';
        }

        // if requirement is branch-like, use full name
        if ('dev-' === strtolower(substr($version, 0, 4))) {
            return 'dev-' . substr($version, 4);
        }

        // strip off build metadata
        if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
            $version = $match[1];
        }

        // match classical versioning
        if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = $matches[1]
                . (!empty($matches[2]) ? $matches[2] : '.0')
                . (!empty($matches[3]) ? $matches[3] : '.0')
                . (!empty($matches[4]) ? $matches[4] : '.0');
            $index = 5;
        // match date(time) based versioning
        } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = preg_replace('{\D}', '.', $matches[1]);
            $index = 2;
        }

        // add version modifiers if a version was matched
        if (isset($index)) {
            if (!empty($matches[$index])) {
                if ('stable' === $matches[$index]) {
                    return $version;
                }
                $version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? ltrim($matches[$index + 1], '.-') : '');
            }

            if (!empty($matches[$index + 2])) {
                $version .= '-dev';
            }

            return $version;
        }

        // match dev branches
        if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) {
            try {
                return $this->normalizeBranch($match[1]);
            } catch (\Exception $e) {
            }
        }

        $extraMessage = '';
        if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
        } elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
        }

        throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage);
    }

    /**
     * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison.
     *
     * @param string $branch Branch name (e.g. 2.1.x-dev)
     *
     * @return string|false Numeric prefix if present (e.g. 2.1.) or false
     */
    public function parseNumericAliasPrefix($branch)
    {
        if (preg_match('{^(?P<version>(\d++\\.)*\d++)(?:\.x)?-dev$}i', $branch, $matches)) {
            return $matches['version'] . '.';
        }

        return false;
    }

    /**
     * Normalizes a branch name to be able to perform comparisons on it.
     *
     * @param string $name
     *
     * @return string
     */
    public function normalizeBranch($name)
    {
        $name = trim($name);

        if (in_array($name, array('master', 'trunk', 'default'))) {
            return $this->normalize($name);
        }

        if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) {
            $version = '';
            for ($i = 1; $i < 5; ++$i) {
                $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
            }

            return str_replace('x', '9999999', $version) . '-dev';
        }

        return 'dev-' . $name;
    }

    /**
     * Parses a constraint string into MultiConstraint and/or Constraint objects.
     *
     * @param string $constraints
     *
     * @return ConstraintInterface
     */
    public function parseConstraints($constraints)
    {
        $prettyConstraint = $constraints;

        if (preg_match('{^([^,\s]*?)@(' . implode('|', self::$stabilities) . ')$}i', $constraints, $match)) {
            $constraints = empty($match[1]) ? '*' : $match[1];
        }

        if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraints, $match)) {
            $constraints = $match[1];
        }

        $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints));
        $orGroups = array();
        foreach ($orConstraints as $constraints) {
            $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $constraints);
            if (count($andConstraints) > 1) {
                $constraintObjects = array();
                foreach ($andConstraints as $constraint) {
                    foreach ($this->parseConstraint($constraint) as $parsedConstraint) {
                        $constraintObjects[] = $parsedConstraint;
                    }
                }
            } else {
                $constraintObjects = $this->parseConstraint($andConstraints[0]);
            }

            if (1 === count($constraintObjects)) {
                $constraint = $constraintObjects[0];
            } else {
                $constraint = new MultiConstraint($constraintObjects);
            }

            $orGroups[] = $constraint;
        }

        if (1 === count($orGroups)) {
            $constraint = $orGroups[0];
        } elseif (2 === count($orGroups)
            // parse the two OR groups and if they are contiguous we collapse
            // them into one constraint
            && $orGroups[0] instanceof MultiConstraint
            && $orGroups[1] instanceof MultiConstraint
            && 2 === count($orGroups[0]->getConstraints())
            && 2 === count($orGroups[1]->getConstraints())
            && ($a = (string) $orGroups[0])
            && substr($a, 0, 3) === '[>=' && (false !== ($posA = strpos($a, '<', 4)))
            && ($b = (string) $orGroups[1])
            && substr($b, 0, 3) === '[>=' && (false !== ($posB = strpos($b, '<', 4)))
            && substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5)
        ) {
            $constraint = new MultiConstraint(array(
                new Constraint('>=', substr($a, 4, $posA - 5)),
                new Constraint('<', substr($b, $posB + 2, -1)),
            ));
        } else {
            $constraint = new MultiConstraint($orGroups, false);
        }

        $constraint->setPrettyString($prettyConstraint);

        return $constraint;
    }

    /**
     * @param string $constraint
     *
     * @throws \UnexpectedValueException
     *
     * @return array
     */
    private function parseConstraint($constraint)
    {
        if (preg_match('{^([^,\s]+?)@(' . implode('|', self::$stabilities) . ')$}i', $constraint, $match)) {
            $constraint = $match[1];
            if ($match[2] !== 'stable') {
                $stabilityModifier = $match[2];
            }
        }

        if (preg_match('{^v?[xX*](\.[xX*])*$}i', $constraint)) {
            return array(new EmptyConstraint());
        }

        $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?' . self::$modifierRegex . '(?:\+[^\s]+)?';

        // Tilde Range
        //
        // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
        // version, to ensure that unstable instances of the current version are allowed. However, if a stability
        // suffix is added to the constraint, then a >= match on the current version is used instead.
        if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) {
            if (substr($constraint, 0, 2) === '~>') {
                throw new \UnexpectedValueException(
                    'Could not parse version constraint ' . $constraint . ': ' .
                    'Invalid operator "~>", you probably meant to use the "~" operator'
                );
            }

            // Work out which position in the version we are operating at
            if (isset($matches[4]) && '' !== $matches[4]) {
                $position = 4;
            } elseif (isset($matches[3]) && '' !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }

            // Calculate the stability suffix
            $stabilitySuffix = '';
            if (!empty($matches[5])) {
                $stabilitySuffix .= '-' . $this->expandStability($matches[5]) . (!empty($matches[6]) ? $matches[6] : '');
            }

            if (!empty($matches[7])) {
                $stabilitySuffix .= '-dev';
            }

            if (!$stabilitySuffix) {
                $stabilitySuffix = '-dev';
            }

            $lowVersion = $this->manipulateVersionString($matches, $position, 0) . $stabilitySuffix;
            $lowerBound = new Constraint('>=', $lowVersion);

            // For upper bound, we increment the position of one more significance,
            // but highPosition = 0 would be illegal
            $highPosition = max(1, $position - 1);
            $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // Caret Range
        //
        // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple.
        // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for
        // versions 0.X >=0.1.0, and no updates for versions 0.0.X
        if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
            // Work out which position in the version we are operating at
            if ('0' !== $matches[1] || '' === $matches[2]) {
                $position = 1;
            } elseif ('0' !== $matches[2] || '' === $matches[3]) {
                $position = 2;
            } else {
                $position = 3;
            }

            // Calculate the stability suffix
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7])) {
                $stabilitySuffix .= '-dev';
            }

            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);

            // For upper bound, we increment the position of one more significance,
            // but highPosition = 0 would be illegal
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // X Range
        //
        // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple.
        // A partial version range is treated as an X-Range, so the special character is in fact optional.
        if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
            if (isset($matches[3]) && '' !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }

            $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';

            if ($lowVersion === '0.0.0.0-dev') {
                return array(new Constraint('<', $highVersion));
            }

            return array(
                new Constraint('>=', $lowVersion),
                new Constraint('<', $highVersion),
            );
        }

        // Hyphen Range
        //
        // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range,
        // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in
        // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but
        // nothing that would be greater than the provided tuple parts.
        if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
            // Calculate the stability suffix
            $lowStabilitySuffix = '';
            if (empty($matches[6]) && empty($matches[8])) {
                $lowStabilitySuffix = '-dev';
            }

            $lowVersion = $this->normalize($matches['from']);
            $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);

            $empty = function ($x) {
                return ($x === 0 || $x === '0') ? false : empty($x);
            };

            if ((!$empty($matches[11]) && !$empty($matches[12])) || !empty($matches[14]) || !empty($matches[16])) {
                $highVersion = $this->normalize($matches['to']);
                $upperBound = new Constraint('<=', $highVersion);
            } else {
                $highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]);
                $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev';
                $upperBound = new Constraint('<', $highVersion);
            }

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // Basic Comparators
        if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
            try {
                $version = $this->normalize($matches[2]);

                if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') {
                    $version .= '-' . $stabilityModifier;
                } elseif ('<' === $matches[1] || '>=' === $matches[1]) {
                    if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
                        if (substr($matches[2], 0, 4) !== 'dev-') {
                            $version .= '-dev';
                        }
                    }
                }

                return array(new Constraint($matches[1] ?: '=', $version));
            } catch (\Exception $e) {
            }
        }

        $message = 'Could not parse version constraint ' . $constraint;
        if (isset($e)) {
            $message .= ': ' . $e->getMessage();
        }

        throw new \UnexpectedValueException($message);
    }

    /**
     * Increment, decrement, or simply pad a version number.
     *
     * Support function for {@link parseConstraint()}
     *
     * @param array $matches Array with version parts in array indexes 1,2,3,4
     * @param int $position 1,2,3,4 - which segment of the version to increment/decrement
     * @param int $increment
     * @param string $pad The string to pad version parts after $position
     *
     * @return string The new version
     */
    private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0')
    {
        for ($i = 4; $i > 0; --$i) {
            if ($i > $position) {
                $matches[$i] = $pad;
            } elseif ($i === $position && $increment) {
                $matches[$i] += $increment;
                // If $matches[$i] was 0, carry the decrement
                if ($matches[$i] < 0) {
                    $matches[$i] = $pad;
                    --$position;

                    // Return null on a carry overflow
                    if ($i === 1) {
                        return;
                    }
                }
            }
        }

        return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4];
    }

    /**
     * Expand shorthand stability string to long version.
     *
     * @param string $stability
     *
     * @return string
     */
    private function expandStability($stability)
    {
        $stability = strtolower($stability);

        switch ($stability) {
            case 'a':
                return 'alpha';
            case 'b':
                return 'beta';
            case 'p':
            case 'pl':
                return 'patch';
            case 'rc':
                return 'RC';
            default:
                return $stability;
        }
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Comparator
{
    /**
     * Evaluates the expression: $version1 > $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function greaterThan($version1, $version2)
    {
        return self::compare($version1, '>', $version2);
    }

    /**
     * Evaluates the expression: $version1 >= $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function greaterThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '>=', $version2);
    }

    /**
     * Evaluates the expression: $version1 < $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function lessThan($version1, $version2)
    {
        return self::compare($version1, '<', $version2);
    }

    /**
     * Evaluates the expression: $version1 <= $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function lessThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '<=', $version2);
    }

    /**
     * Evaluates the expression: $version1 == $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function equalTo($version1, $version2)
    {
        return self::compare($version1, '==', $version2);
    }

    /**
     * Evaluates the expression: $version1 != $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function notEqualTo($version1, $version2)
    {
        return self::compare($version1, '!=', $version2);
    }

    /**
     * Evaluates the expression: $version1 $operator $version2.
     *
     * @param string $version1
     * @param string $operator
     * @param string $version2
     *
     * @return bool
     */
    public static function compare($version1, $operator, $version2)
    {
        $constraint = new Constraint($operator, $version2);

        return $constraint->matches(new Constraint('==', $version1));
    }
}
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'JsonMapper' => array($vendorDir . '/netresearch/jsonmapper/src'),
);
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    http://www.php-fig.org/psr/psr-0/
 * @see    http://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    // PSR-4
    private $prefixLengthsPsr4 = array();
    private $prefixDirsPsr4 = array();
    private $fallbackDirsPsr4 = array();

    // PSR-0
    private $prefixesPsr0 = array();
    private $fallbackDirsPsr0 = array();

    private $useIncludePath = false;
    private $classMap = array();
    private $classMapAuthoritative = false;
    private $missingClasses = array();
    private $apcuPrefix;

    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', $this->prefixesPsr0);
        }

        return array();
    }

    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array $classMap Class to filename map
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string       $prefix  The prefix
     * @param array|string $paths   The PSR-0 root directories
     * @param bool         $prepend Whether to prepend the directories
     */
    public function add($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    (array) $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = (array) $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                (array) $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string       $prefix  The prefix/namespace, with trailing '\\'
     * @param array|string $paths   The PSR-4 base directories
     * @param bool         $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    (array) $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                (array) $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string       $prefix The prefix
     * @param array|string $paths  The PSR-0 base directories
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string       $prefix The prefix/namespace, with trailing '\\'
     * @param array|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

    /**
     * Unregisters this instance as an autoloader.
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return bool|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath.'\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'AdvancedJsonRpc\\Dispatcher' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Dispatcher.php',
    'AdvancedJsonRpc\\Error' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Error.php',
    'AdvancedJsonRpc\\ErrorCode' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/ErrorCode.php',
    'AdvancedJsonRpc\\ErrorResponse' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/ErrorResponse.php',
    'AdvancedJsonRpc\\Message' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Message.php',
    'AdvancedJsonRpc\\Notification' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Notification.php',
    'AdvancedJsonRpc\\Request' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Request.php',
    'AdvancedJsonRpc\\Response' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/Response.php',
    'AdvancedJsonRpc\\SuccessResponse' => $vendorDir . '/felixfbecker/advanced-json-rpc/lib/SuccessResponse.php',
    'Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php',
    'Composer\\Semver\\Constraint\\AbstractConstraint' => $vendorDir . '/composer/semver/src/Constraint/AbstractConstraint.php',
    'Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php',
    'Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php',
    'Composer\\Semver\\Constraint\\EmptyConstraint' => $vendorDir . '/composer/semver/src/Constraint/EmptyConstraint.php',
    'Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php',
    'Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php',
    'Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php',
    'Composer\\XdebugHandler\\Process' => $vendorDir . '/composer/xdebug-handler/src/Process.php',
    'Composer\\XdebugHandler\\Status' => $vendorDir . '/composer/xdebug-handler/src/Status.php',
    'Composer\\XdebugHandler\\XdebugHandler' => $vendorDir . '/composer/xdebug-handler/src/XdebugHandler.php',
    'JsonMapper' => $vendorDir . '/netresearch/jsonmapper/src/JsonMapper.php',
    'JsonMapper_Exception' => $vendorDir . '/netresearch/jsonmapper/src/JsonMapper/Exception.php',
    'Microsoft\\PhpParser\\Associativity' => $vendorDir . '/microsoft/tolerant-php-parser/src/Parser.php',
    'Microsoft\\PhpParser\\CharacterCodes' => $vendorDir . '/microsoft/tolerant-php-parser/src/CharacterCodes.php',
    'Microsoft\\PhpParser\\ClassLike' => $vendorDir . '/microsoft/tolerant-php-parser/src/ClassLike.php',
    'Microsoft\\PhpParser\\Diagnostic' => $vendorDir . '/microsoft/tolerant-php-parser/src/Diagnostic.php',
    'Microsoft\\PhpParser\\DiagnosticKind' => $vendorDir . '/microsoft/tolerant-php-parser/src/DiagnosticKind.php',
    'Microsoft\\PhpParser\\DiagnosticsProvider' => $vendorDir . '/microsoft/tolerant-php-parser/src/DiagnosticsProvider.php',
    'Microsoft\\PhpParser\\FilePositionMap' => $vendorDir . '/microsoft/tolerant-php-parser/src/FilePositionMap.php',
    'Microsoft\\PhpParser\\FunctionLike' => $vendorDir . '/microsoft/tolerant-php-parser/src/FunctionLike.php',
    'Microsoft\\PhpParser\\LineCharacterPosition' => $vendorDir . '/microsoft/tolerant-php-parser/src/LineCharacterPosition.php',
    'Microsoft\\PhpParser\\MissingToken' => $vendorDir . '/microsoft/tolerant-php-parser/src/MissingToken.php',
    'Microsoft\\PhpParser\\NamespacedNameInterface' => $vendorDir . '/microsoft/tolerant-php-parser/src/NamespacedNameInterface.php',
    'Microsoft\\PhpParser\\NamespacedNameTrait' => $vendorDir . '/microsoft/tolerant-php-parser/src/NamespacedNameTrait.php',
    'Microsoft\\PhpParser\\Node' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node.php',
    'Microsoft\\PhpParser\\Node\\AnonymousFunctionUseClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/AnonymousFunctionUseClause.php',
    'Microsoft\\PhpParser\\Node\\ArrayElement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ArrayElement.php',
    'Microsoft\\PhpParser\\Node\\CaseStatementNode' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/CaseStatementNode.php',
    'Microsoft\\PhpParser\\Node\\CatchClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/CatchClause.php',
    'Microsoft\\PhpParser\\Node\\ClassBaseClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ClassBaseClause.php',
    'Microsoft\\PhpParser\\Node\\ClassConstDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ClassConstDeclaration.php',
    'Microsoft\\PhpParser\\Node\\ClassInterfaceClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ClassInterfaceClause.php',
    'Microsoft\\PhpParser\\Node\\ClassMembersNode' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ClassMembersNode.php',
    'Microsoft\\PhpParser\\Node\\ConstElement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ConstElement.php',
    'Microsoft\\PhpParser\\Node\\DeclareDirective' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DeclareDirective.php',
    'Microsoft\\PhpParser\\Node\\DefaultStatementNode' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DefaultStatementNode.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\ArgumentExpressionList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ArgumentExpressionList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\ArrayElementList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ArrayElementList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\ConstElementList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ConstElementList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\ExpressionList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ExpressionList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\ListExpressionList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ListExpressionList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\NamespaceUseClauseList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/NamespaceUseClauseList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\NamespaceUseGroupClauseList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/NamespaceUseGroupClauseList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\ParameterDeclarationList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ParameterDeclarationList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\QualifiedNameList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/QualifiedNameList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\QualifiedNameParts' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/QualifiedNameParts.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\StaticVariableNameList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/StaticVariableNameList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\TraitSelectOrAliasClauseList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/TraitSelectOrAliasClauseList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\UseVariableNameList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/UseVariableNameList.php',
    'Microsoft\\PhpParser\\Node\\DelimitedList\\VariableNameList' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/VariableNameList.php',
    'Microsoft\\PhpParser\\Node\\ElseClauseNode' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ElseClauseNode.php',
    'Microsoft\\PhpParser\\Node\\ElseIfClauseNode' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ElseIfClauseNode.php',
    'Microsoft\\PhpParser\\Node\\Expression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\AnonymousFunctionCreationExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/AnonymousFunctionCreationExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\ArgumentExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/ArgumentExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\ArrayCreationExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/ArrayCreationExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\AssignmentExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/AssignmentExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\BinaryExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/BinaryExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\BracedExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/BracedExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\CallExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/CallExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\CastExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/CastExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\CloneExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/CloneExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\EchoExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/EchoExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\EmptyIntrinsicExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/EmptyIntrinsicExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\ErrorControlExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/ErrorControlExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\EvalIntrinsicExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/EvalIntrinsicExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\ExitIntrinsicExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/ExitIntrinsicExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\IssetIntrinsicExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/IssetIntrinsicExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\ListIntrinsicExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/ListIntrinsicExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\MemberAccessExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/MemberAccessExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\ObjectCreationExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/ObjectCreationExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\ParenthesizedExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/ParenthesizedExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\PostfixUpdateExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/PostfixUpdateExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\PrefixUpdateExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/PrefixUpdateExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\PrintIntrinsicExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/PrintIntrinsicExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\ScopedPropertyAccessExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/ScopedPropertyAccessExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\ScriptInclusionExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/ScriptInclusionExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\SubscriptExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/SubscriptExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\TernaryExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/TernaryExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\UnaryExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/UnaryExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\UnaryOpExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/UnaryOpExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\UnsetIntrinsicExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/UnsetIntrinsicExpression.php',
    'Microsoft\\PhpParser\\Node\\Expression\\Variable' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/Variable.php',
    'Microsoft\\PhpParser\\Node\\Expression\\YieldExpression' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Expression/YieldExpression.php',
    'Microsoft\\PhpParser\\Node\\FinallyClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/FinallyClause.php',
    'Microsoft\\PhpParser\\Node\\ForeachKey' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ForeachKey.php',
    'Microsoft\\PhpParser\\Node\\ForeachValue' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ForeachValue.php',
    'Microsoft\\PhpParser\\Node\\FunctionBody' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/FunctionBody.php',
    'Microsoft\\PhpParser\\Node\\FunctionHeader' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/FunctionHeader.php',
    'Microsoft\\PhpParser\\Node\\FunctionReturnType' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/FunctionReturnType.php',
    'Microsoft\\PhpParser\\Node\\FunctionUseClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/FunctionUseClause.php',
    'Microsoft\\PhpParser\\Node\\InterfaceBaseClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/InterfaceBaseClause.php',
    'Microsoft\\PhpParser\\Node\\InterfaceMembers' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/InterfaceMembers.php',
    'Microsoft\\PhpParser\\Node\\MethodDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/MethodDeclaration.php',
    'Microsoft\\PhpParser\\Node\\MissingMemberDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/MissingMemberDeclaration.php',
    'Microsoft\\PhpParser\\Node\\NamespaceAliasingClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/NamespaceAliasingClause.php',
    'Microsoft\\PhpParser\\Node\\NamespaceUseClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/NamespaceUseClause.php',
    'Microsoft\\PhpParser\\Node\\NamespaceUseGroupClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/NamespaceUseGroupClause.php',
    'Microsoft\\PhpParser\\Node\\NumericLiteral' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/NumericLiteral.php',
    'Microsoft\\PhpParser\\Node\\Parameter' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Parameter.php',
    'Microsoft\\PhpParser\\Node\\PropertyDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/PropertyDeclaration.php',
    'Microsoft\\PhpParser\\Node\\QualifiedName' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/QualifiedName.php',
    'Microsoft\\PhpParser\\Node\\RelativeSpecifier' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/RelativeSpecifier.php',
    'Microsoft\\PhpParser\\Node\\ReservedWord' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/ReservedWord.php',
    'Microsoft\\PhpParser\\Node\\SourceFileNode' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/SourceFileNode.php',
    'Microsoft\\PhpParser\\Node\\StatementNode' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/StatementNode.php',
    'Microsoft\\PhpParser\\Node\\Statement\\BreakOrContinueStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/BreakOrContinueStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\ClassDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/ClassDeclaration.php',
    'Microsoft\\PhpParser\\Node\\Statement\\CompoundStatementNode' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/CompoundStatementNode.php',
    'Microsoft\\PhpParser\\Node\\Statement\\ConstDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/ConstDeclaration.php',
    'Microsoft\\PhpParser\\Node\\Statement\\DeclareStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/DeclareStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\DoStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/DoStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\EmptyStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/EmptyStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\ExpressionStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/ExpressionStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\ForStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/ForStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\ForeachStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/ForeachStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\FunctionDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/FunctionDeclaration.php',
    'Microsoft\\PhpParser\\Node\\Statement\\FunctionStaticDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/FunctionStaticDeclaration.php',
    'Microsoft\\PhpParser\\Node\\Statement\\GlobalDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/GlobalDeclaration.php',
    'Microsoft\\PhpParser\\Node\\Statement\\GotoStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/GotoStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\IfStatementNode' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/IfStatementNode.php',
    'Microsoft\\PhpParser\\Node\\Statement\\InlineHtml' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/InlineHtml.php',
    'Microsoft\\PhpParser\\Node\\Statement\\InterfaceDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/InterfaceDeclaration.php',
    'Microsoft\\PhpParser\\Node\\Statement\\NamedLabelStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/NamedLabelStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\NamespaceDefinition' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/NamespaceDefinition.php',
    'Microsoft\\PhpParser\\Node\\Statement\\NamespaceUseDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/NamespaceUseDeclaration.php',
    'Microsoft\\PhpParser\\Node\\Statement\\ReturnStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/ReturnStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\SwitchStatementNode' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/SwitchStatementNode.php',
    'Microsoft\\PhpParser\\Node\\Statement\\ThrowStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/ThrowStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\TraitDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/TraitDeclaration.php',
    'Microsoft\\PhpParser\\Node\\Statement\\TryStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/TryStatement.php',
    'Microsoft\\PhpParser\\Node\\Statement\\WhileStatement' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/Statement/WhileStatement.php',
    'Microsoft\\PhpParser\\Node\\StaticVariableDeclaration' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/StaticVariableDeclaration.php',
    'Microsoft\\PhpParser\\Node\\StringLiteral' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/StringLiteral.php',
    'Microsoft\\PhpParser\\Node\\TraitMembers' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/TraitMembers.php',
    'Microsoft\\PhpParser\\Node\\TraitSelectOrAliasClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/TraitSelectOrAliasClause.php',
    'Microsoft\\PhpParser\\Node\\TraitUseClause' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/TraitUseClause.php',
    'Microsoft\\PhpParser\\Node\\UseVariableName' => $vendorDir . '/microsoft/tolerant-php-parser/src/Node/UseVariableName.php',
    'Microsoft\\PhpParser\\ParseContext' => $vendorDir . '/microsoft/tolerant-php-parser/src/ParseContext.php',
    'Microsoft\\PhpParser\\Parser' => $vendorDir . '/microsoft/tolerant-php-parser/src/Parser.php',
    'Microsoft\\PhpParser\\PhpTokenizer' => $vendorDir . '/microsoft/tolerant-php-parser/src/PhpTokenizer.php',
    'Microsoft\\PhpParser\\PositionUtilities' => $vendorDir . '/microsoft/tolerant-php-parser/src/PositionUtilities.php',
    'Microsoft\\PhpParser\\Range' => $vendorDir . '/microsoft/tolerant-php-parser/src/Range.php',
    'Microsoft\\PhpParser\\ResolvedName' => $vendorDir . '/microsoft/tolerant-php-parser/src/ResolvedName.php',
    'Microsoft\\PhpParser\\SkippedToken' => $vendorDir . '/microsoft/tolerant-php-parser/src/SkippedToken.php',
    'Microsoft\\PhpParser\\TextEdit' => $vendorDir . '/microsoft/tolerant-php-parser/src/TextEdit.php',
    'Microsoft\\PhpParser\\Token' => $vendorDir . '/microsoft/tolerant-php-parser/src/Token.php',
    'Microsoft\\PhpParser\\TokenKind' => $vendorDir . '/microsoft/tolerant-php-parser/src/TokenKind.php',
    'Microsoft\\PhpParser\\TokenStreamProviderFactory' => $vendorDir . '/microsoft/tolerant-php-parser/src/TokenStreamProviderFactory.php',
    'Microsoft\\PhpParser\\TokenStreamProviderInterface' => $vendorDir . '/microsoft/tolerant-php-parser/src/TokenStreamProviderInterface.php',
    'Microsoft\\PhpParser\\TokenStringMaps' => $vendorDir . '/microsoft/tolerant-php-parser/src/TokenStringMaps.php',
    'Phan\\AST\\ASTSimplifier' => $baseDir . '/src/Phan/AST/ASTSimplifier.php',
    'Phan\\AST\\AnalysisVisitor' => $baseDir . '/src/Phan/AST/AnalysisVisitor.php',
    'Phan\\AST\\ContextNode' => $baseDir . '/src/Phan/AST/ContextNode.php',
    'Phan\\AST\\Parser' => $baseDir . '/src/Phan/AST/Parser.php',
    'Phan\\AST\\PhanAnnotationAdder' => $baseDir . '/src/Phan/AST/PhanAnnotationAdder.php',
    'Phan\\AST\\TolerantASTConverter\\InvalidNodeException' => $baseDir . '/src/Phan/AST/TolerantASTConverter/InvalidNodeException.php',
    'Phan\\AST\\TolerantASTConverter\\NodeDumper' => $baseDir . '/src/Phan/AST/TolerantASTConverter/NodeDumper.php',
    'Phan\\AST\\TolerantASTConverter\\ParseException' => $baseDir . '/src/Phan/AST/TolerantASTConverter/ParseException.php',
    'Phan\\AST\\TolerantASTConverter\\String_' => $baseDir . '/src/Phan/AST/TolerantASTConverter/String_.php',
    'Phan\\AST\\TolerantASTConverter\\TolerantASTConverter' => $baseDir . '/src/Phan/AST/TolerantASTConverter/TolerantASTConverter.php',
    'Phan\\AST\\UnionTypeVisitor' => $baseDir . '/src/Phan/AST/UnionTypeVisitor.php',
    'Phan\\AST\\Visitor\\Element' => $baseDir . '/src/Phan/AST/Visitor/Element.php',
    'Phan\\AST\\Visitor\\FlagVisitor' => $baseDir . '/src/Phan/AST/Visitor/FlagVisitor.php',
    'Phan\\AST\\Visitor\\FlagVisitorImplementation' => $baseDir . '/src/Phan/AST/Visitor/FlagVisitorImplementation.php',
    'Phan\\AST\\Visitor\\KindVisitor' => $baseDir . '/src/Phan/AST/Visitor/KindVisitor.php',
    'Phan\\AST\\Visitor\\KindVisitorImplementation' => $baseDir . '/src/Phan/AST/Visitor/KindVisitorImplementation.php',
    'Phan\\Analysis' => $baseDir . '/src/Phan/Analysis.php',
    'Phan\\Analysis\\AbstractMethodAnalyzer' => $baseDir . '/src/Phan/Analysis/AbstractMethodAnalyzer.php',
    'Phan\\Analysis\\Analyzable' => $baseDir . '/src/Phan/Analysis/Analyzable.php',
    'Phan\\Analysis\\ArgumentType' => $baseDir . '/src/Phan/Analysis/ArgumentType.php',
    'Phan\\Analysis\\AssignOperatorFlagVisitor' => $baseDir . '/src/Phan/Analysis/AssignOperatorFlagVisitor.php',
    'Phan\\Analysis\\AssignmentVisitor' => $baseDir . '/src/Phan/Analysis/AssignmentVisitor.php',
    'Phan\\Analysis\\BinaryOperatorFlagVisitor' => $baseDir . '/src/Phan/Analysis/BinaryOperatorFlagVisitor.php',
    'Phan\\Analysis\\BlockExitStatusChecker' => $baseDir . '/src/Phan/Analysis/BlockExitStatusChecker.php',
    'Phan\\Analysis\\ClassInheritanceAnalyzer' => $baseDir . '/src/Phan/Analysis/ClassInheritanceAnalyzer.php',
    'Phan\\Analysis\\CompositionAnalyzer' => $baseDir . '/src/Phan/Analysis/CompositionAnalyzer.php',
    'Phan\\Analysis\\ConditionVisitor' => $baseDir . '/src/Phan/Analysis/ConditionVisitor.php',
    'Phan\\Analysis\\ConditionVisitorUtil' => $baseDir . '/src/Phan/Analysis/ConditionVisitorUtil.php',
    'Phan\\Analysis\\ContextMergeVisitor' => $baseDir . '/src/Phan/Analysis/ContextMergeVisitor.php',
    'Phan\\Analysis\\DuplicateClassAnalyzer' => $baseDir . '/src/Phan/Analysis/DuplicateClassAnalyzer.php',
    'Phan\\Analysis\\DuplicateFunctionAnalyzer' => $baseDir . '/src/Phan/Analysis/DuplicateFunctionAnalyzer.php',
    'Phan\\Analysis\\NegatedConditionVisitor' => $baseDir . '/src/Phan/Analysis/NegatedConditionVisitor.php',
    'Phan\\Analysis\\ParameterTypesAnalyzer' => $baseDir . '/src/Phan/Analysis/ParameterTypesAnalyzer.php',
    'Phan\\Analysis\\ParentConstructorCalledAnalyzer' => $baseDir . '/src/Phan/Analysis/ParentConstructorCalledAnalyzer.php',
    'Phan\\Analysis\\PostOrderAnalysisVisitor' => $baseDir . '/src/Phan/Analysis/PostOrderAnalysisVisitor.php',
    'Phan\\Analysis\\PreOrderAnalysisVisitor' => $baseDir . '/src/Phan/Analysis/PreOrderAnalysisVisitor.php',
    'Phan\\Analysis\\PropertyTypesAnalyzer' => $baseDir . '/src/Phan/Analysis/PropertyTypesAnalyzer.php',
    'Phan\\Analysis\\ReferenceCountsAnalyzer' => $baseDir . '/src/Phan/Analysis/ReferenceCountsAnalyzer.php',
    'Phan\\Analysis\\ReturnTypesAnalyzer' => $baseDir . '/src/Phan/Analysis/ReturnTypesAnalyzer.php',
    'Phan\\Analysis\\ScopeVisitor' => $baseDir . '/src/Phan/Analysis/ScopeVisitor.php',
    'Phan\\Analysis\\ThrowsTypesAnalyzer' => $baseDir . '/src/Phan/Analysis/ThrowsTypesAnalyzer.php',
    'Phan\\BlockAnalysisVisitor' => $baseDir . '/src/Phan/BlockAnalysisVisitor.php',
    'Phan\\CLI' => $baseDir . '/src/Phan/CLI.php',
    'Phan\\CodeBase' => $baseDir . '/src/Phan/CodeBase.php',
    'Phan\\CodeBase\\ClassMap' => $baseDir . '/src/Phan/CodeBase/ClassMap.php',
    'Phan\\CodeBase\\UndoTracker' => $baseDir . '/src/Phan/CodeBase/UndoTracker.php',
    'Phan\\Config' => $baseDir . '/src/Phan/Config.php',
    'Phan\\Config\\InitializedSettings' => $baseDir . '/src/Phan/Config/InitializedSettings.php',
    'Phan\\Config\\Initializer' => $baseDir . '/src/Phan/Config/Initializer.php',
    'Phan\\Daemon' => $baseDir . '/src/Phan/Daemon.php',
    'Phan\\Daemon\\ExitException' => $baseDir . '/src/Phan/Daemon/ExitException.php',
    'Phan\\Daemon\\Request' => $baseDir . '/src/Phan/Daemon/Request.php',
    'Phan\\Daemon\\Transport\\CapturerResponder' => $baseDir . '/src/Phan/Daemon/Transport/CapturerResponder.php',
    'Phan\\Daemon\\Transport\\Responder' => $baseDir . '/src/Phan/Daemon/Transport/Responder.php',
    'Phan\\Daemon\\Transport\\StreamResponder' => $baseDir . '/src/Phan/Daemon/Transport/StreamResponder.php',
    'Phan\\Debug' => $baseDir . '/src/Phan/Debug.php',
    'Phan\\Debug\\DebugUnionType' => $baseDir . '/src/Phan/Debug/DebugUnionType.php',
    'Phan\\Exception\\CodeBaseException' => $baseDir . '/src/Phan/Exception/CodeBaseException.php',
    'Phan\\Exception\\EmptyFQSENException' => $baseDir . '/src/Phan/Exception/EmptyFQSENException.php',
    'Phan\\Exception\\IssueException' => $baseDir . '/src/Phan/Exception/IssueException.php',
    'Phan\\Exception\\NodeException' => $baseDir . '/src/Phan/Exception/NodeException.php',
    'Phan\\Exception\\TypeException' => $baseDir . '/src/Phan/Exception/TypeException.php',
    'Phan\\Exception\\UnanalyzableException' => $baseDir . '/src/Phan/Exception/UnanalyzableException.php',
    'Phan\\ForkPool' => $baseDir . '/src/Phan/ForkPool.php',
    'Phan\\Issue' => $baseDir . '/src/Phan/Issue.php',
    'Phan\\IssueInstance' => $baseDir . '/src/Phan/IssueInstance.php',
    'Phan\\LanguageServer\\ClientHandler' => $baseDir . '/src/Phan/LanguageServer/ClientHandler.php',
    'Phan\\LanguageServer\\Client\\TextDocument' => $baseDir . '/src/Phan/LanguageServer/Client/TextDocument.php',
    'Phan\\LanguageServer\\FileMapping' => $baseDir . '/src/Phan/LanguageServer/FileMapping.php',
    'Phan\\LanguageServer\\IdGenerator' => $baseDir . '/src/Phan/LanguageServer/IdGenerator.php',
    'Phan\\LanguageServer\\LanguageClient' => $baseDir . '/src/Phan/LanguageServer/LanguageClient.php',
    'Phan\\LanguageServer\\LanguageServer' => $baseDir . '/src/Phan/LanguageServer/LanguageServer.php',
    'Phan\\LanguageServer\\Logger' => $baseDir . '/src/Phan/LanguageServer/Logger.php',
    'Phan\\LanguageServer\\ProtocolReader' => $baseDir . '/src/Phan/LanguageServer/ProtocolReader.php',
    'Phan\\LanguageServer\\ProtocolStreamReader' => $baseDir . '/src/Phan/LanguageServer/ProtocolStreamReader.php',
    'Phan\\LanguageServer\\ProtocolStreamWriter' => $baseDir . '/src/Phan/LanguageServer/ProtocolStreamWriter.php',
    'Phan\\LanguageServer\\ProtocolWriter' => $baseDir . '/src/Phan/LanguageServer/ProtocolWriter.php',
    'Phan\\LanguageServer\\Protocol\\ClientCapabilities' => $baseDir . '/src/Phan/LanguageServer/Protocol/ClientCapabilities.php',
    'Phan\\LanguageServer\\Protocol\\Diagnostic' => $baseDir . '/src/Phan/LanguageServer/Protocol/Diagnostic.php',
    'Phan\\LanguageServer\\Protocol\\DiagnosticSeverity' => $baseDir . '/src/Phan/LanguageServer/Protocol/DiagnosticSeverity.php',
    'Phan\\LanguageServer\\Protocol\\FileChangeType' => $baseDir . '/src/Phan/LanguageServer/Protocol/FileChangeType.php',
    'Phan\\LanguageServer\\Protocol\\FileEvent' => $baseDir . '/src/Phan/LanguageServer/Protocol/FileEvent.php',
    'Phan\\LanguageServer\\Protocol\\InitializeResult' => $baseDir . '/src/Phan/LanguageServer/Protocol/InitializeResult.php',
    'Phan\\LanguageServer\\Protocol\\Message' => $baseDir . '/src/Phan/LanguageServer/Protocol/Message.php',
    'Phan\\LanguageServer\\Protocol\\Position' => $baseDir . '/src/Phan/LanguageServer/Protocol/Position.php',
    'Phan\\LanguageServer\\Protocol\\Range' => $baseDir . '/src/Phan/LanguageServer/Protocol/Range.php',
    'Phan\\LanguageServer\\Protocol\\SaveOptions' => $baseDir . '/src/Phan/LanguageServer/Protocol/SaveOptions.php',
    'Phan\\LanguageServer\\Protocol\\ServerCapabilities' => $baseDir . '/src/Phan/LanguageServer/Protocol/ServerCapabilities.php',
    'Phan\\LanguageServer\\Protocol\\TextDocumentContentChangeEvent' => $baseDir . '/src/Phan/LanguageServer/Protocol/TextDocumentContentChangeEvent.php',
    'Phan\\LanguageServer\\Protocol\\TextDocumentIdentifier' => $baseDir . '/src/Phan/LanguageServer/Protocol/TextDocumentIdentifier.php',
    'Phan\\LanguageServer\\Protocol\\TextDocumentItem' => $baseDir . '/src/Phan/LanguageServer/Protocol/TextDocumentItem.php',
    'Phan\\LanguageServer\\Protocol\\TextDocumentSyncKind' => $baseDir . '/src/Phan/LanguageServer/Protocol/TextDocumentSyncKind.php',
    'Phan\\LanguageServer\\Protocol\\TextDocumentSyncOptions' => $baseDir . '/src/Phan/LanguageServer/Protocol/TextDocumentSyncOptions.php',
    'Phan\\LanguageServer\\Protocol\\VersionedTextDocumentIdentifier' => $baseDir . '/src/Phan/LanguageServer/Protocol/VersionedTextDocumentIdentifier.php',
    'Phan\\LanguageServer\\Server\\TextDocument' => $baseDir . '/src/Phan/LanguageServer/Server/TextDocument.php',
    'Phan\\LanguageServer\\Server\\Workspace' => $baseDir . '/src/Phan/LanguageServer/Server/Workspace.php',
    'Phan\\LanguageServer\\Utils' => $baseDir . '/src/Phan/LanguageServer/Utils.php',
    'Phan\\Language\\AnnotatedUnionType' => $baseDir . '/src/Phan/Language/AnnotatedUnionType.php',
    'Phan\\Language\\Context' => $baseDir . '/src/Phan/Language/Context.php',
    'Phan\\Language\\Element\\AddressableElement' => $baseDir . '/src/Phan/Language/Element/AddressableElement.php',
    'Phan\\Language\\Element\\AddressableElementInterface' => $baseDir . '/src/Phan/Language/Element/AddressableElementInterface.php',
    'Phan\\Language\\Element\\ClassAliasRecord' => $baseDir . '/src/Phan/Language/Element/ClassAliasRecord.php',
    'Phan\\Language\\Element\\ClassConstant' => $baseDir . '/src/Phan/Language/Element/ClassConstant.php',
    'Phan\\Language\\Element\\ClassElement' => $baseDir . '/src/Phan/Language/Element/ClassElement.php',
    'Phan\\Language\\Element\\Clazz' => $baseDir . '/src/Phan/Language/Element/Clazz.php',
    'Phan\\Language\\Element\\ClosedScopeElement' => $baseDir . '/src/Phan/Language/Element/ClosedScopeElement.php',
    'Phan\\Language\\Element\\Comment' => $baseDir . '/src/Phan/Language/Element/Comment.php',
    'Phan\\Language\\Element\\Comment\\Method' => $baseDir . '/src/Phan/Language/Element/Comment/Method.php',
    'Phan\\Language\\Element\\Comment\\Parameter' => $baseDir . '/src/Phan/Language/Element/Comment/Parameter.php',
    'Phan\\Language\\Element\\ConstantInterface' => $baseDir . '/src/Phan/Language/Element/ConstantInterface.php',
    'Phan\\Language\\Element\\ConstantTrait' => $baseDir . '/src/Phan/Language/Element/ConstantTrait.php',
    'Phan\\Language\\Element\\ElementFutureUnionType' => $baseDir . '/src/Phan/Language/Element/ElementFutureUnionType.php',
    'Phan\\Language\\Element\\Flags' => $baseDir . '/src/Phan/Language/Element/Flags.php',
    'Phan\\Language\\Element\\Func' => $baseDir . '/src/Phan/Language/Element/Func.php',
    'Phan\\Language\\Element\\FunctionFactory' => $baseDir . '/src/Phan/Language/Element/FunctionFactory.php',
    'Phan\\Language\\Element\\FunctionInterface' => $baseDir . '/src/Phan/Language/Element/FunctionInterface.php',
    'Phan\\Language\\Element\\FunctionTrait' => $baseDir . '/src/Phan/Language/Element/FunctionTrait.php',
    'Phan\\Language\\Element\\GlobalConstant' => $baseDir . '/src/Phan/Language/Element/GlobalConstant.php',
    'Phan\\Language\\Element\\Method' => $baseDir . '/src/Phan/Language/Element/Method.php',
    'Phan\\Language\\Element\\Parameter' => $baseDir . '/src/Phan/Language/Element/Parameter.php',
    'Phan\\Language\\Element\\PassByReferenceVariable' => $baseDir . '/src/Phan/Language/Element/PassByReferenceVariable.php',
    'Phan\\Language\\Element\\Property' => $baseDir . '/src/Phan/Language/Element/Property.php',
    'Phan\\Language\\Element\\TraitAdaptations' => $baseDir . '/src/Phan/Language/Element/TraitAdaptations.php',
    'Phan\\Language\\Element\\TraitAliasSource' => $baseDir . '/src/Phan/Language/Element/TraitAliasSource.php',
    'Phan\\Language\\Element\\TypedElement' => $baseDir . '/src/Phan/Language/Element/TypedElement.php',
    'Phan\\Language\\Element\\TypedElementInterface' => $baseDir . '/src/Phan/Language/Element/TypedElementInterface.php',
    'Phan\\Language\\Element\\UnaddressableTypedElement' => $baseDir . '/src/Phan/Language/Element/UnaddressableTypedElement.php',
    'Phan\\Language\\Element\\Variable' => $baseDir . '/src/Phan/Language/Element/Variable.php',
    'Phan\\Language\\Element\\VariadicParameter' => $baseDir . '/src/Phan/Language/Element/VariadicParameter.php',
    'Phan\\Language\\EmptyUnionType' => $baseDir . '/src/Phan/Language/EmptyUnionType.php',
    'Phan\\Language\\FQSEN' => $baseDir . '/src/Phan/Language/FQSEN.php',
    'Phan\\Language\\FQSEN\\AbstractFQSEN' => $baseDir . '/src/Phan/Language/FQSEN/AbstractFQSEN.php',
    'Phan\\Language\\FQSEN\\Alternatives' => $baseDir . '/src/Phan/Language/FQSEN/Alternatives.php',
    'Phan\\Language\\FQSEN\\FullyQualifiedClassConstantName' => $baseDir . '/src/Phan/Language/FQSEN/FullyQualifiedClassConstantName.php',
    'Phan\\Language\\FQSEN\\FullyQualifiedClassElement' => $baseDir . '/src/Phan/Language/FQSEN/FullyQualifiedClassElement.php',
    'Phan\\Language\\FQSEN\\FullyQualifiedClassName' => $baseDir . '/src/Phan/Language/FQSEN/FullyQualifiedClassName.php',
    'Phan\\Language\\FQSEN\\FullyQualifiedConstantName' => $baseDir . '/src/Phan/Language/FQSEN/FullyQualifiedConstantName.php',
    'Phan\\Language\\FQSEN\\FullyQualifiedFunctionLikeName' => $baseDir . '/src/Phan/Language/FQSEN/FullyQualifiedFunctionLikeName.php',
    'Phan\\Language\\FQSEN\\FullyQualifiedFunctionName' => $baseDir . '/src/Phan/Language/FQSEN/FullyQualifiedFunctionName.php',
    'Phan\\Language\\FQSEN\\FullyQualifiedGlobalConstantName' => $baseDir . '/src/Phan/Language/FQSEN/FullyQualifiedGlobalConstantName.php',
    'Phan\\Language\\FQSEN\\FullyQualifiedGlobalStructuralElement' => $baseDir . '/src/Phan/Language/FQSEN/FullyQualifiedGlobalStructuralElement.php',
    'Phan\\Language\\FQSEN\\FullyQualifiedMethodName' => $baseDir . '/src/Phan/Language/FQSEN/FullyQualifiedMethodName.php',
    'Phan\\Language\\FQSEN\\FullyQualifiedPropertyName' => $baseDir . '/src/Phan/Language/FQSEN/FullyQualifiedPropertyName.php',
    'Phan\\Language\\FileRef' => $baseDir . '/src/Phan/Language/FileRef.php',
    'Phan\\Language\\FutureUnionType' => $baseDir . '/src/Phan/Language/FutureUnionType.php',
    'Phan\\Language\\NamespaceMapEntry' => $baseDir . '/src/Phan/Language/NamespaceMapEntry.php',
    'Phan\\Language\\Scope' => $baseDir . '/src/Phan/Language/Scope.php',
    'Phan\\Language\\Scope\\BranchScope' => $baseDir . '/src/Phan/Language/Scope/BranchScope.php',
    'Phan\\Language\\Scope\\ClassScope' => $baseDir . '/src/Phan/Language/Scope/ClassScope.php',
    'Phan\\Language\\Scope\\ClosedScope' => $baseDir . '/src/Phan/Language/Scope/ClosedScope.php',
    'Phan\\Language\\Scope\\ClosureScope' => $baseDir . '/src/Phan/Language/Scope/ClosureScope.php',
    'Phan\\Language\\Scope\\FunctionLikeScope' => $baseDir . '/src/Phan/Language/Scope/FunctionLikeScope.php',
    'Phan\\Language\\Scope\\GlobalScope' => $baseDir . '/src/Phan/Language/Scope/GlobalScope.php',
    'Phan\\Language\\Scope\\PropertyScope' => $baseDir . '/src/Phan/Language/Scope/PropertyScope.php',
    'Phan\\Language\\Type' => $baseDir . '/src/Phan/Language/Type.php',
    'Phan\\Language\\Type\\ArrayShapeType' => $baseDir . '/src/Phan/Language/Type/ArrayShapeType.php',
    'Phan\\Language\\Type\\ArrayType' => $baseDir . '/src/Phan/Language/Type/ArrayType.php',
    'Phan\\Language\\Type\\BoolType' => $baseDir . '/src/Phan/Language/Type/BoolType.php',
    'Phan\\Language\\Type\\CallableDeclarationType' => $baseDir . '/src/Phan/Language/Type/CallableDeclarationType.php',
    'Phan\\Language\\Type\\CallableType' => $baseDir . '/src/Phan/Language/Type/CallableType.php',
    'Phan\\Language\\Type\\ClosureDeclarationParameter' => $baseDir . '/src/Phan/Language/Type/ClosureDeclarationParameter.php',
    'Phan\\Language\\Type\\ClosureDeclarationType' => $baseDir . '/src/Phan/Language/Type/ClosureDeclarationType.php',
    'Phan\\Language\\Type\\ClosureType' => $baseDir . '/src/Phan/Language/Type/ClosureType.php',
    'Phan\\Language\\Type\\FalseType' => $baseDir . '/src/Phan/Language/Type/FalseType.php',
    'Phan\\Language\\Type\\FloatType' => $baseDir . '/src/Phan/Language/Type/FloatType.php',
    'Phan\\Language\\Type\\FunctionLikeDeclarationType' => $baseDir . '/src/Phan/Language/Type/FunctionLikeDeclarationType.php',
    'Phan\\Language\\Type\\GenericArrayType' => $baseDir . '/src/Phan/Language/Type/GenericArrayType.php',
    'Phan\\Language\\Type\\GenericMultiArrayType' => $baseDir . '/src/Phan/Language/Type/GenericMultiArrayType.php',
    'Phan\\Language\\Type\\IntType' => $baseDir . '/src/Phan/Language/Type/IntType.php',
    'Phan\\Language\\Type\\IterableType' => $baseDir . '/src/Phan/Language/Type/IterableType.php',
    'Phan\\Language\\Type\\MixedType' => $baseDir . '/src/Phan/Language/Type/MixedType.php',
    'Phan\\Language\\Type\\MultiType' => $baseDir . '/src/Phan/Language/Type/MultiType.php',
    'Phan\\Language\\Type\\NativeType' => $baseDir . '/src/Phan/Language/Type/NativeType.php',
    'Phan\\Language\\Type\\NullType' => $baseDir . '/src/Phan/Language/Type/NullType.php',
    'Phan\\Language\\Type\\ObjectType' => $baseDir . '/src/Phan/Language/Type/ObjectType.php',
    'Phan\\Language\\Type\\ResourceType' => $baseDir . '/src/Phan/Language/Type/ResourceType.php',
    'Phan\\Language\\Type\\ScalarRawType' => $baseDir . '/src/Phan/Language/Type/ScalarRawType.php',
    'Phan\\Language\\Type\\ScalarType' => $baseDir . '/src/Phan/Language/Type/ScalarType.php',
    'Phan\\Language\\Type\\StaticType' => $baseDir . '/src/Phan/Language/Type/StaticType.php',
    'Phan\\Language\\Type\\StringType' => $baseDir . '/src/Phan/Language/Type/StringType.php',
    'Phan\\Language\\Type\\TemplateType' => $baseDir . '/src/Phan/Language/Type/TemplateType.php',
    'Phan\\Language\\Type\\TrueType' => $baseDir . '/src/Phan/Language/Type/TrueType.php',
    'Phan\\Language\\Type\\VoidType' => $baseDir . '/src/Phan/Language/Type/VoidType.php',
    'Phan\\Language\\UnionType' => $baseDir . '/src/Phan/Language/UnionType.php',
    'Phan\\Language\\UnionTypeBuilder' => $baseDir . '/src/Phan/Language/UnionTypeBuilder.php',
    'Phan\\Library\\FileCache' => $baseDir . '/src/Phan/Library/FileCache.php',
    'Phan\\Library\\FileCacheEntry' => $baseDir . '/src/Phan/Library/FileCacheEntry.php',
    'Phan\\Library\\Hasher' => $baseDir . '/src/Phan/Library/Hasher.php',
    'Phan\\Library\\Hasher\\Consistent' => $baseDir . '/src/Phan/Library/Hasher/Consistent.php',
    'Phan\\Library\\Hasher\\Sequential' => $baseDir . '/src/Phan/Library/Hasher/Sequential.php',
    'Phan\\Library\\Map' => $baseDir . '/src/Phan/Library/Map.php',
    'Phan\\Library\\None' => $baseDir . '/src/Phan/Library/None.php',
    'Phan\\Library\\Option' => $baseDir . '/src/Phan/Library/Option.php',
    'Phan\\Library\\Set' => $baseDir . '/src/Phan/Library/Set.php',
    'Phan\\Library\\Some' => $baseDir . '/src/Phan/Library/Some.php',
    'Phan\\Library\\StderrLogger' => $baseDir . '/src/Phan/Library/StderrLogger.php',
    'Phan\\Library\\Tuple' => $baseDir . '/src/Phan/Library/Tuple.php',
    'Phan\\Library\\Tuple1' => $baseDir . '/src/Phan/Library/Tuple1.php',
    'Phan\\Library\\Tuple2' => $baseDir . '/src/Phan/Library/Tuple2.php',
    'Phan\\Library\\Tuple3' => $baseDir . '/src/Phan/Library/Tuple3.php',
    'Phan\\Library\\Tuple4' => $baseDir . '/src/Phan/Library/Tuple4.php',
    'Phan\\Library\\Tuple5' => $baseDir . '/src/Phan/Library/Tuple5.php',
    'Phan\\Memoize' => $baseDir . '/src/Phan/Memoize.php',
    'Phan\\Ordering' => $baseDir . '/src/Phan/Ordering.php',
    'Phan\\Output\\BufferedPrinterInterface' => $baseDir . '/src/Phan/Output/BufferedPrinterInterface.php',
    'Phan\\Output\\Collector\\BufferingCollector' => $baseDir . '/src/Phan/Output/Collector/BufferingCollector.php',
    'Phan\\Output\\Collector\\ParallelChildCollector' => $baseDir . '/src/Phan/Output/Collector/ParallelChildCollector.php',
    'Phan\\Output\\Collector\\ParallelParentCollector' => $baseDir . '/src/Phan/Output/Collector/ParallelParentCollector.php',
    'Phan\\Output\\Colorizing' => $baseDir . '/src/Phan/Output/Colorizing.php',
    'Phan\\Output\\Filter\\AnyFilter' => $baseDir . '/src/Phan/Output/Filter/AnyFilter.php',
    'Phan\\Output\\Filter\\CategoryIssueFilter' => $baseDir . '/src/Phan/Output/Filter/CategoryIssueFilter.php',
    'Phan\\Output\\Filter\\ChainedIssueFilter' => $baseDir . '/src/Phan/Output/Filter/ChainedIssueFilter.php',
    'Phan\\Output\\Filter\\FileIssueFilter' => $baseDir . '/src/Phan/Output/Filter/FileIssueFilter.php',
    'Phan\\Output\\Filter\\MinimumSeverityFilter' => $baseDir . '/src/Phan/Output/Filter/MinimumSeverityFilter.php',
    'Phan\\Output\\IgnoredFilesFilterInterface' => $baseDir . '/src/Phan/Output/IgnoredFilesFilterInterface.php',
    'Phan\\Output\\IssueCollectorInterface' => $baseDir . '/src/Phan/Output/IssueCollectorInterface.php',
    'Phan\\Output\\IssueFilterInterface' => $baseDir . '/src/Phan/Output/IssueFilterInterface.php',
    'Phan\\Output\\IssuePrinterInterface' => $baseDir . '/src/Phan/Output/IssuePrinterInterface.php',
    'Phan\\Output\\PrinterFactory' => $baseDir . '/src/Phan/Output/PrinterFactory.php',
    'Phan\\Output\\Printer\\CSVPrinter' => $baseDir . '/src/Phan/Output/Printer/CSVPrinter.php',
    'Phan\\Output\\Printer\\CheckstylePrinter' => $baseDir . '/src/Phan/Output/Printer/CheckstylePrinter.php',
    'Phan\\Output\\Printer\\CodeClimatePrinter' => $baseDir . '/src/Phan/Output/Printer/CodeClimatePrinter.php',
    'Phan\\Output\\Printer\\JSONPrinter' => $baseDir . '/src/Phan/Output/Printer/JSONPrinter.php',
    'Phan\\Output\\Printer\\PlainTextPrinter' => $baseDir . '/src/Phan/Output/Printer/PlainTextPrinter.php',
    'Phan\\Output\\Printer\\PylintPrinter' => $baseDir . '/src/Phan/Output/Printer/PylintPrinter.php',
    'Phan\\Parse\\ParseVisitor' => $baseDir . '/src/Phan/Parse/ParseVisitor.php',
    'Phan\\Phan' => $baseDir . '/src/Phan/Phan.php',
    'Phan\\Plugin' => $baseDir . '/src/Phan/Plugin.php',
    'Phan\\PluginV2' => $baseDir . '/src/Phan/PluginV2.php',
    'Phan\\PluginV2\\AfterAnalyzeFileCapability' => $baseDir . '/src/Phan/PluginV2/AfterAnalyzeFileCapability.php',
    'Phan\\PluginV2\\AnalyzeClassCapability' => $baseDir . '/src/Phan/PluginV2/AnalyzeClassCapability.php',
    'Phan\\PluginV2\\AnalyzeFunctionCallCapability' => $baseDir . '/src/Phan/PluginV2/AnalyzeFunctionCallCapability.php',
    'Phan\\PluginV2\\AnalyzeFunctionCapability' => $baseDir . '/src/Phan/PluginV2/AnalyzeFunctionCapability.php',
    'Phan\\PluginV2\\AnalyzeMethodCapability' => $baseDir . '/src/Phan/PluginV2/AnalyzeMethodCapability.php',
    'Phan\\PluginV2\\AnalyzeNodeCapability' => $baseDir . '/src/Phan/PluginV2/AnalyzeNodeCapability.php',
    'Phan\\PluginV2\\AnalyzePropertyCapability' => $baseDir . '/src/Phan/PluginV2/AnalyzePropertyCapability.php',
    'Phan\\PluginV2\\BeforeAnalyzeFileCapability' => $baseDir . '/src/Phan/PluginV2/BeforeAnalyzeFileCapability.php',
    'Phan\\PluginV2\\FinalizeProcessCapability' => $baseDir . '/src/Phan/PluginV2/FinalizeProcessCapability.php',
    'Phan\\PluginV2\\IssueEmitter' => $baseDir . '/src/Phan/PluginV2/IssueEmitter.php',
    'Phan\\PluginV2\\LegacyAnalyzeNodeCapability' => $baseDir . '/src/Phan/PluginV2/LegacyAnalyzeNodeCapability.php',
    'Phan\\PluginV2\\LegacyPostAnalyzeNodeCapability' => $baseDir . '/src/Phan/PluginV2/LegacyPostAnalyzeNodeCapability.php',
    'Phan\\PluginV2\\LegacyPreAnalyzeNodeCapability' => $baseDir . '/src/Phan/PluginV2/LegacyPreAnalyzeNodeCapability.php',
    'Phan\\PluginV2\\PluginAwareAnalysisVisitor' => $baseDir . '/src/Phan/PluginV2/PluginAwareAnalysisVisitor.php',
    'Phan\\PluginV2\\PluginAwareBaseAnalysisVisitor' => $baseDir . '/src/Phan/PluginV2/PluginAwareBaseAnalysisVisitor.php',
    'Phan\\PluginV2\\PluginAwarePostAnalysisVisitor' => $baseDir . '/src/Phan/PluginV2/PluginAwarePostAnalysisVisitor.php',
    'Phan\\PluginV2\\PluginAwarePreAnalysisVisitor' => $baseDir . '/src/Phan/PluginV2/PluginAwarePreAnalysisVisitor.php',
    'Phan\\PluginV2\\PostAnalyzeNodeCapability' => $baseDir . '/src/Phan/PluginV2/PostAnalyzeNodeCapability.php',
    'Phan\\PluginV2\\PreAnalyzeNodeCapability' => $baseDir . '/src/Phan/PluginV2/PreAnalyzeNodeCapability.php',
    'Phan\\PluginV2\\ReturnTypeOverrideCapability' => $baseDir . '/src/Phan/PluginV2/ReturnTypeOverrideCapability.php',
    'Phan\\PluginV2\\StopParamAnalysisException' => $baseDir . '/src/Phan/PluginV2/StopParamAnalysisException.php',
    'Phan\\Plugin\\ClosuresForKind' => $baseDir . '/src/Phan/Plugin/ClosuresForKind.php',
    'Phan\\Plugin\\ConfigPluginSet' => $baseDir . '/src/Phan/Plugin/ConfigPluginSet.php',
    'Phan\\Plugin\\Internal\\ArrayReturnTypeOverridePlugin' => $baseDir . '/src/Phan/Plugin/Internal/ArrayReturnTypeOverridePlugin.php',
    'Phan\\Plugin\\Internal\\CallableParamPlugin' => $baseDir . '/src/Phan/Plugin/Internal/CallableParamPlugin.php',
    'Phan\\Plugin\\Internal\\ClosureReturnTypeOverridePlugin' => $baseDir . '/src/Phan/Plugin/Internal/ClosureReturnTypeOverridePlugin.php',
    'Phan\\Plugin\\Internal\\CompactPlugin' => $baseDir . '/src/Phan/Plugin/Internal/CompactPlugin.php',
    'Phan\\Plugin\\Internal\\DependentReturnTypeOverridePlugin' => $baseDir . '/src/Phan/Plugin/Internal/DependentReturnTypeOverridePlugin.php',
    'Phan\\Plugin\\Internal\\MiscParamPlugin' => $baseDir . '/src/Phan/Plugin/Internal/MiscParamPlugin.php',
    'Phan\\Plugin\\Internal\\StringFunctionPlugin' => $baseDir . '/src/Phan/Plugin/Internal/StringFunctionPlugin.php',
    'Phan\\Plugin\\PluginImplementation' => $baseDir . '/src/Phan/Plugin/PluginImplementation.php',
    'Phan\\Prep' => $baseDir . '/src/Phan/Prep.php',
    'Phan\\Profile' => $baseDir . '/src/Phan/Profile.php',
    'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php',
    'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php',
    'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php',
    'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php',
    'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php',
    'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php',
    'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php',
    'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php',
    'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
    'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
    'Sabre\\Event\\Emitter' => $vendorDir . '/sabre/event/lib/Emitter.php',
    'Sabre\\Event\\EmitterInterface' => $vendorDir . '/sabre/event/lib/EmitterInterface.php',
    'Sabre\\Event\\EmitterTrait' => $vendorDir . '/sabre/event/lib/EmitterTrait.php',
    'Sabre\\Event\\EventEmitter' => $vendorDir . '/sabre/event/lib/EventEmitter.php',
    'Sabre\\Event\\Loop\\Loop' => $vendorDir . '/sabre/event/lib/Loop/Loop.php',
    'Sabre\\Event\\Promise' => $vendorDir . '/sabre/event/lib/Promise.php',
    'Sabre\\Event\\PromiseAlreadyResolvedException' => $vendorDir . '/sabre/event/lib/PromiseAlreadyResolvedException.php',
    'Sabre\\Event\\Version' => $vendorDir . '/sabre/event/lib/Version.php',
    'Sabre\\Event\\WildcardEmitter' => $vendorDir . '/sabre/event/lib/WildcardEmitter.php',
    'Sabre\\Event\\WildcardEmitterTrait' => $vendorDir . '/sabre/event/lib/WildcardEmitterTrait.php',
    'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php',
    'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
    'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/ContainerCommandLoader.php',
    'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/FactoryCommandLoader.php',
    'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php',
    'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php',
    'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php',
    'Symfony\\Component\\Console\\Command\\LockableTrait' => $vendorDir . '/symfony/console/Command/LockableTrait.php',
    'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php',
    'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => $vendorDir . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php',
    'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php',
    'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php',
    'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php',
    'Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php',
    'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => $vendorDir . '/symfony/console/Event/ConsoleErrorEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => $vendorDir . '/symfony/console/Event/ConsoleExceptionEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php',
    'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php',
    'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php',
    'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php',
    'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php',
    'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php',
    'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php',
    'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php',
    'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php',
    'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php',
    'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php',
    'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php',
    'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php',
    'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php',
    'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php',
    'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php',
    'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php',
    'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php',
    'Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php',
    'Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php',
    'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php',
    'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php',
    'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php',
    'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php',
    'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php',
    'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php',
    'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php',
    'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php',
    'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php',
    'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php',
    'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php',
    'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php',
    'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php',
    'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php',
    'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php',
    'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php',
    'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php',
    'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php',
    'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php',
    'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php',
    'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php',
    'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php',
    'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php',
    'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php',
    'Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php',
    'Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php',
    'Symfony\\Component\\Console\\Terminal' => $vendorDir . '/symfony/console/Terminal.php',
    'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php',
    'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php',
    'Symfony\\Component\\Debug\\BufferingLogger' => $vendorDir . '/symfony/debug/BufferingLogger.php',
    'Symfony\\Component\\Debug\\Debug' => $vendorDir . '/symfony/debug/Debug.php',
    'Symfony\\Component\\Debug\\DebugClassLoader' => $vendorDir . '/symfony/debug/DebugClassLoader.php',
    'Symfony\\Component\\Debug\\ErrorHandler' => $vendorDir . '/symfony/debug/ErrorHandler.php',
    'Symfony\\Component\\Debug\\ExceptionHandler' => $vendorDir . '/symfony/debug/ExceptionHandler.php',
    'Symfony\\Component\\Debug\\Exception\\ClassNotFoundException' => $vendorDir . '/symfony/debug/Exception/ClassNotFoundException.php',
    'Symfony\\Component\\Debug\\Exception\\ContextErrorException' => $vendorDir . '/symfony/debug/Exception/ContextErrorException.php',
    'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => $vendorDir . '/symfony/debug/Exception/FatalErrorException.php',
    'Symfony\\Component\\Debug\\Exception\\FatalThrowableError' => $vendorDir . '/symfony/debug/Exception/FatalThrowableError.php',
    'Symfony\\Component\\Debug\\Exception\\FlattenException' => $vendorDir . '/symfony/debug/Exception/FlattenException.php',
    'Symfony\\Component\\Debug\\Exception\\OutOfMemoryException' => $vendorDir . '/symfony/debug/Exception/OutOfMemoryException.php',
    'Symfony\\Component\\Debug\\Exception\\SilencedErrorContext' => $vendorDir . '/symfony/debug/Exception/SilencedErrorContext.php',
    'Symfony\\Component\\Debug\\Exception\\UndefinedFunctionException' => $vendorDir . '/symfony/debug/Exception/UndefinedFunctionException.php',
    'Symfony\\Component\\Debug\\Exception\\UndefinedMethodException' => $vendorDir . '/symfony/debug/Exception/UndefinedMethodException.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\ClassNotFoundFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\FatalErrorHandlerInterface' => $vendorDir . '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedFunctionFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedMethodFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php',
    'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php',
    'Webmozart\\Assert\\Assert' => $vendorDir . '/webmozart/assert/src/Assert.php',
    'phpDocumentor\\Reflection\\DocBlock' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock.php',
    'phpDocumentor\\Reflection\\DocBlockFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlockFactory.php',
    'phpDocumentor\\Reflection\\DocBlockFactoryInterface' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php',
    'phpDocumentor\\Reflection\\DocBlock\\Description' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Description.php',
    'phpDocumentor\\Reflection\\DocBlock\\DescriptionFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php',
    'phpDocumentor\\Reflection\\DocBlock\\ExampleFinder' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php',
    'phpDocumentor\\Reflection\\DocBlock\\Serializer' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php',
    'phpDocumentor\\Reflection\\DocBlock\\StandardTagFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tag' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php',
    'phpDocumentor\\Reflection\\DocBlock\\TagFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Author' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\BaseTag' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Covers' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Deprecated' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Example' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\StaticMethod' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\Strategy' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\AlignFormatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\PassthroughFormatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Generic' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Link' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Method' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Param' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Property' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyRead' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyWrite' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Fqsen' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Reference' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Url' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Return_' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\See' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Since' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Source' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Throws' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Uses' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Var_' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Version' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php',
    'phpDocumentor\\Reflection\\Element' => $vendorDir . '/phpdocumentor/reflection-common/src/Element.php',
    'phpDocumentor\\Reflection\\File' => $vendorDir . '/phpdocumentor/reflection-common/src/File.php',
    'phpDocumentor\\Reflection\\Fqsen' => $vendorDir . '/phpdocumentor/reflection-common/src/Fqsen.php',
    'phpDocumentor\\Reflection\\FqsenResolver' => $vendorDir . '/phpdocumentor/type-resolver/src/FqsenResolver.php',
    'phpDocumentor\\Reflection\\Location' => $vendorDir . '/phpdocumentor/reflection-common/src/Location.php',
    'phpDocumentor\\Reflection\\Project' => $vendorDir . '/phpdocumentor/reflection-common/src/Project.php',
    'phpDocumentor\\Reflection\\ProjectFactory' => $vendorDir . '/phpdocumentor/reflection-common/src/ProjectFactory.php',
    'phpDocumentor\\Reflection\\Type' => $vendorDir . '/phpdocumentor/type-resolver/src/Type.php',
    'phpDocumentor\\Reflection\\TypeResolver' => $vendorDir . '/phpdocumentor/type-resolver/src/TypeResolver.php',
    'phpDocumentor\\Reflection\\Types\\Array_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Array_.php',
    'phpDocumentor\\Reflection\\Types\\Boolean' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Boolean.php',
    'phpDocumentor\\Reflection\\Types\\Callable_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Callable_.php',
    'phpDocumentor\\Reflection\\Types\\Compound' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Compound.php',
    'phpDocumentor\\Reflection\\Types\\Context' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Context.php',
    'phpDocumentor\\Reflection\\Types\\ContextFactory' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/ContextFactory.php',
    'phpDocumentor\\Reflection\\Types\\Float_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Float_.php',
    'phpDocumentor\\Reflection\\Types\\Integer' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Integer.php',
    'phpDocumentor\\Reflection\\Types\\Iterable_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Iterable_.php',
    'phpDocumentor\\Reflection\\Types\\Mixed_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Mixed_.php',
    'phpDocumentor\\Reflection\\Types\\Null_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Null_.php',
    'phpDocumentor\\Reflection\\Types\\Nullable' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Nullable.php',
    'phpDocumentor\\Reflection\\Types\\Object_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Object_.php',
    'phpDocumentor\\Reflection\\Types\\Parent_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Parent_.php',
    'phpDocumentor\\Reflection\\Types\\Resource_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Resource_.php',
    'phpDocumentor\\Reflection\\Types\\Scalar' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Scalar.php',
    'phpDocumentor\\Reflection\\Types\\Self_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Self_.php',
    'phpDocumentor\\Reflection\\Types\\Static_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Static_.php',
    'phpDocumentor\\Reflection\\Types\\String_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/String_.php',
    'phpDocumentor\\Reflection\\Types\\This' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/This.php',
    'phpDocumentor\\Reflection\\Types\\Void_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Void_.php',
);
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInitc2bcc8b87b5bd6e082150780bb130a51
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInitc2bcc8b87b5bd6e082150780bb130a51', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInitc2bcc8b87b5bd6e082150780bb130a51', 'loadClassLoader'));

        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInitc2bcc8b87b5bd6e082150780bb130a51::getInitializer($loader));
        } else {
            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
        }

        $loader->setClassMapAuthoritative(true);
        $loader->register(true);

        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInitc2bcc8b87b5bd6e082150780bb130a51::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequirec2bcc8b87b5bd6e082150780bb130a51($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequirec2bcc8b87b5bd6e082150780bb130a51($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInitc2bcc8b87b5bd6e082150780bb130a51
{
    public static $files = array (
        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
        '2b9d0f43f9552984cfa82fee95491826' => __DIR__ . '/..' . '/sabre/event/lib/coroutine.php',
        'd81bab31d3feb45bfe2f283ea3c8fdf7' => __DIR__ . '/..' . '/sabre/event/lib/Loop/functions.php',
        'a1cce3d26cc15c00fcd0b3354bd72c88' => __DIR__ . '/..' . '/sabre/event/lib/Promise/functions.php',
    );

    public static $prefixLengthsPsr4 = array (
        'p' => 
        array (
            'phpDocumentor\\Reflection\\' => 25,
        ),
        'W' => 
        array (
            'Webmozart\\Assert\\' => 17,
        ),
        'S' => 
        array (
            'Symfony\\Polyfill\\Mbstring\\' => 26,
            'Symfony\\Component\\Debug\\' => 24,
            'Symfony\\Component\\Console\\' => 26,
            'Sabre\\Event\\' => 12,
        ),
        'P' => 
        array (
            'Psr\\Log\\' => 8,
            'Phan\\' => 5,
        ),
        'M' => 
        array (
            'Microsoft\\PhpParser\\' => 20,
        ),
        'C' => 
        array (
            'Composer\\XdebugHandler\\' => 23,
            'Composer\\Semver\\' => 16,
        ),
        'A' => 
        array (
            'AdvancedJsonRpc\\' => 16,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'phpDocumentor\\Reflection\\' => 
        array (
            0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
            1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
            2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
        ),
        'Webmozart\\Assert\\' => 
        array (
            0 => __DIR__ . '/..' . '/webmozart/assert/src',
        ),
        'Symfony\\Polyfill\\Mbstring\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
        ),
        'Symfony\\Component\\Debug\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/debug',
        ),
        'Symfony\\Component\\Console\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/console',
        ),
        'Sabre\\Event\\' => 
        array (
            0 => __DIR__ . '/..' . '/sabre/event/lib',
        ),
        'Psr\\Log\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
        ),
        'Phan\\' => 
        array (
            0 => __DIR__ . '/../..' . '/src/Phan',
        ),
        'Microsoft\\PhpParser\\' => 
        array (
            0 => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src',
        ),
        'Composer\\XdebugHandler\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/xdebug-handler/src',
        ),
        'Composer\\Semver\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/semver/src',
        ),
        'AdvancedJsonRpc\\' => 
        array (
            0 => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib',
        ),
    );

    public static $prefixesPsr0 = array (
        'J' => 
        array (
            'JsonMapper' => 
            array (
                0 => __DIR__ . '/..' . '/netresearch/jsonmapper/src',
            ),
        ),
    );

    public static $classMap = array (
        'AdvancedJsonRpc\\Dispatcher' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Dispatcher.php',
        'AdvancedJsonRpc\\Error' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Error.php',
        'AdvancedJsonRpc\\ErrorCode' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/ErrorCode.php',
        'AdvancedJsonRpc\\ErrorResponse' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/ErrorResponse.php',
        'AdvancedJsonRpc\\Message' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Message.php',
        'AdvancedJsonRpc\\Notification' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Notification.php',
        'AdvancedJsonRpc\\Request' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Request.php',
        'AdvancedJsonRpc\\Response' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/Response.php',
        'AdvancedJsonRpc\\SuccessResponse' => __DIR__ . '/..' . '/felixfbecker/advanced-json-rpc/lib/SuccessResponse.php',
        'Composer\\Semver\\Comparator' => __DIR__ . '/..' . '/composer/semver/src/Comparator.php',
        'Composer\\Semver\\Constraint\\AbstractConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/AbstractConstraint.php',
        'Composer\\Semver\\Constraint\\Constraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Constraint.php',
        'Composer\\Semver\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/composer/semver/src/Constraint/ConstraintInterface.php',
        'Composer\\Semver\\Constraint\\EmptyConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/EmptyConstraint.php',
        'Composer\\Semver\\Constraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MultiConstraint.php',
        'Composer\\Semver\\Semver' => __DIR__ . '/..' . '/composer/semver/src/Semver.php',
        'Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php',
        'Composer\\XdebugHandler\\Process' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Process.php',
        'Composer\\XdebugHandler\\Status' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Status.php',
        'Composer\\XdebugHandler\\XdebugHandler' => __DIR__ . '/..' . '/composer/xdebug-handler/src/XdebugHandler.php',
        'JsonMapper' => __DIR__ . '/..' . '/netresearch/jsonmapper/src/JsonMapper.php',
        'JsonMapper_Exception' => __DIR__ . '/..' . '/netresearch/jsonmapper/src/JsonMapper/Exception.php',
        'Microsoft\\PhpParser\\Associativity' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Parser.php',
        'Microsoft\\PhpParser\\CharacterCodes' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/CharacterCodes.php',
        'Microsoft\\PhpParser\\ClassLike' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/ClassLike.php',
        'Microsoft\\PhpParser\\Diagnostic' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Diagnostic.php',
        'Microsoft\\PhpParser\\DiagnosticKind' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/DiagnosticKind.php',
        'Microsoft\\PhpParser\\DiagnosticsProvider' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/DiagnosticsProvider.php',
        'Microsoft\\PhpParser\\FilePositionMap' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/FilePositionMap.php',
        'Microsoft\\PhpParser\\FunctionLike' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/FunctionLike.php',
        'Microsoft\\PhpParser\\LineCharacterPosition' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/LineCharacterPosition.php',
        'Microsoft\\PhpParser\\MissingToken' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/MissingToken.php',
        'Microsoft\\PhpParser\\NamespacedNameInterface' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/NamespacedNameInterface.php',
        'Microsoft\\PhpParser\\NamespacedNameTrait' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/NamespacedNameTrait.php',
        'Microsoft\\PhpParser\\Node' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node.php',
        'Microsoft\\PhpParser\\Node\\AnonymousFunctionUseClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/AnonymousFunctionUseClause.php',
        'Microsoft\\PhpParser\\Node\\ArrayElement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ArrayElement.php',
        'Microsoft\\PhpParser\\Node\\CaseStatementNode' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/CaseStatementNode.php',
        'Microsoft\\PhpParser\\Node\\CatchClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/CatchClause.php',
        'Microsoft\\PhpParser\\Node\\ClassBaseClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ClassBaseClause.php',
        'Microsoft\\PhpParser\\Node\\ClassConstDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ClassConstDeclaration.php',
        'Microsoft\\PhpParser\\Node\\ClassInterfaceClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ClassInterfaceClause.php',
        'Microsoft\\PhpParser\\Node\\ClassMembersNode' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ClassMembersNode.php',
        'Microsoft\\PhpParser\\Node\\ConstElement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ConstElement.php',
        'Microsoft\\PhpParser\\Node\\DeclareDirective' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DeclareDirective.php',
        'Microsoft\\PhpParser\\Node\\DefaultStatementNode' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DefaultStatementNode.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\ArgumentExpressionList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ArgumentExpressionList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\ArrayElementList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ArrayElementList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\ConstElementList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ConstElementList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\ExpressionList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ExpressionList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\ListExpressionList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ListExpressionList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\NamespaceUseClauseList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/NamespaceUseClauseList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\NamespaceUseGroupClauseList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/NamespaceUseGroupClauseList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\ParameterDeclarationList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/ParameterDeclarationList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\QualifiedNameList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/QualifiedNameList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\QualifiedNameParts' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/QualifiedNameParts.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\StaticVariableNameList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/StaticVariableNameList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\TraitSelectOrAliasClauseList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/TraitSelectOrAliasClauseList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\UseVariableNameList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/UseVariableNameList.php',
        'Microsoft\\PhpParser\\Node\\DelimitedList\\VariableNameList' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/DelimitedList/VariableNameList.php',
        'Microsoft\\PhpParser\\Node\\ElseClauseNode' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ElseClauseNode.php',
        'Microsoft\\PhpParser\\Node\\ElseIfClauseNode' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ElseIfClauseNode.php',
        'Microsoft\\PhpParser\\Node\\Expression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\AnonymousFunctionCreationExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/AnonymousFunctionCreationExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\ArgumentExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/ArgumentExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\ArrayCreationExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/ArrayCreationExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\AssignmentExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/AssignmentExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\BinaryExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/BinaryExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\BracedExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/BracedExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\CallExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/CallExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\CastExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/CastExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\CloneExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/CloneExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\EchoExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/EchoExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\EmptyIntrinsicExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/EmptyIntrinsicExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\ErrorControlExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/ErrorControlExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\EvalIntrinsicExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/EvalIntrinsicExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\ExitIntrinsicExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/ExitIntrinsicExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\IssetIntrinsicExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/IssetIntrinsicExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\ListIntrinsicExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/ListIntrinsicExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\MemberAccessExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/MemberAccessExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\ObjectCreationExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/ObjectCreationExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\ParenthesizedExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/ParenthesizedExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\PostfixUpdateExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/PostfixUpdateExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\PrefixUpdateExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/PrefixUpdateExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\PrintIntrinsicExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/PrintIntrinsicExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\ScopedPropertyAccessExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/ScopedPropertyAccessExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\ScriptInclusionExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/ScriptInclusionExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\SubscriptExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/SubscriptExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\TernaryExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/TernaryExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\UnaryExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/UnaryExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\UnaryOpExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/UnaryOpExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\UnsetIntrinsicExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/UnsetIntrinsicExpression.php',
        'Microsoft\\PhpParser\\Node\\Expression\\Variable' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/Variable.php',
        'Microsoft\\PhpParser\\Node\\Expression\\YieldExpression' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Expression/YieldExpression.php',
        'Microsoft\\PhpParser\\Node\\FinallyClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/FinallyClause.php',
        'Microsoft\\PhpParser\\Node\\ForeachKey' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ForeachKey.php',
        'Microsoft\\PhpParser\\Node\\ForeachValue' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ForeachValue.php',
        'Microsoft\\PhpParser\\Node\\FunctionBody' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/FunctionBody.php',
        'Microsoft\\PhpParser\\Node\\FunctionHeader' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/FunctionHeader.php',
        'Microsoft\\PhpParser\\Node\\FunctionReturnType' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/FunctionReturnType.php',
        'Microsoft\\PhpParser\\Node\\FunctionUseClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/FunctionUseClause.php',
        'Microsoft\\PhpParser\\Node\\InterfaceBaseClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/InterfaceBaseClause.php',
        'Microsoft\\PhpParser\\Node\\InterfaceMembers' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/InterfaceMembers.php',
        'Microsoft\\PhpParser\\Node\\MethodDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/MethodDeclaration.php',
        'Microsoft\\PhpParser\\Node\\MissingMemberDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/MissingMemberDeclaration.php',
        'Microsoft\\PhpParser\\Node\\NamespaceAliasingClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/NamespaceAliasingClause.php',
        'Microsoft\\PhpParser\\Node\\NamespaceUseClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/NamespaceUseClause.php',
        'Microsoft\\PhpParser\\Node\\NamespaceUseGroupClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/NamespaceUseGroupClause.php',
        'Microsoft\\PhpParser\\Node\\NumericLiteral' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/NumericLiteral.php',
        'Microsoft\\PhpParser\\Node\\Parameter' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Parameter.php',
        'Microsoft\\PhpParser\\Node\\PropertyDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/PropertyDeclaration.php',
        'Microsoft\\PhpParser\\Node\\QualifiedName' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/QualifiedName.php',
        'Microsoft\\PhpParser\\Node\\RelativeSpecifier' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/RelativeSpecifier.php',
        'Microsoft\\PhpParser\\Node\\ReservedWord' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/ReservedWord.php',
        'Microsoft\\PhpParser\\Node\\SourceFileNode' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/SourceFileNode.php',
        'Microsoft\\PhpParser\\Node\\StatementNode' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/StatementNode.php',
        'Microsoft\\PhpParser\\Node\\Statement\\BreakOrContinueStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/BreakOrContinueStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\ClassDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/ClassDeclaration.php',
        'Microsoft\\PhpParser\\Node\\Statement\\CompoundStatementNode' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/CompoundStatementNode.php',
        'Microsoft\\PhpParser\\Node\\Statement\\ConstDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/ConstDeclaration.php',
        'Microsoft\\PhpParser\\Node\\Statement\\DeclareStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/DeclareStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\DoStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/DoStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\EmptyStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/EmptyStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\ExpressionStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/ExpressionStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\ForStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/ForStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\ForeachStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/ForeachStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\FunctionDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/FunctionDeclaration.php',
        'Microsoft\\PhpParser\\Node\\Statement\\FunctionStaticDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/FunctionStaticDeclaration.php',
        'Microsoft\\PhpParser\\Node\\Statement\\GlobalDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/GlobalDeclaration.php',
        'Microsoft\\PhpParser\\Node\\Statement\\GotoStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/GotoStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\IfStatementNode' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/IfStatementNode.php',
        'Microsoft\\PhpParser\\Node\\Statement\\InlineHtml' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/InlineHtml.php',
        'Microsoft\\PhpParser\\Node\\Statement\\InterfaceDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/InterfaceDeclaration.php',
        'Microsoft\\PhpParser\\Node\\Statement\\NamedLabelStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/NamedLabelStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\NamespaceDefinition' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/NamespaceDefinition.php',
        'Microsoft\\PhpParser\\Node\\Statement\\NamespaceUseDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/NamespaceUseDeclaration.php',
        'Microsoft\\PhpParser\\Node\\Statement\\ReturnStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/ReturnStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\SwitchStatementNode' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/SwitchStatementNode.php',
        'Microsoft\\PhpParser\\Node\\Statement\\ThrowStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/ThrowStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\TraitDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/TraitDeclaration.php',
        'Microsoft\\PhpParser\\Node\\Statement\\TryStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/TryStatement.php',
        'Microsoft\\PhpParser\\Node\\Statement\\WhileStatement' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/Statement/WhileStatement.php',
        'Microsoft\\PhpParser\\Node\\StaticVariableDeclaration' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/StaticVariableDeclaration.php',
        'Microsoft\\PhpParser\\Node\\StringLiteral' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/StringLiteral.php',
        'Microsoft\\PhpParser\\Node\\TraitMembers' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/TraitMembers.php',
        'Microsoft\\PhpParser\\Node\\TraitSelectOrAliasClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/TraitSelectOrAliasClause.php',
        'Microsoft\\PhpParser\\Node\\TraitUseClause' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/TraitUseClause.php',
        'Microsoft\\PhpParser\\Node\\UseVariableName' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Node/UseVariableName.php',
        'Microsoft\\PhpParser\\ParseContext' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/ParseContext.php',
        'Microsoft\\PhpParser\\Parser' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Parser.php',
        'Microsoft\\PhpParser\\PhpTokenizer' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/PhpTokenizer.php',
        'Microsoft\\PhpParser\\PositionUtilities' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/PositionUtilities.php',
        'Microsoft\\PhpParser\\Range' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Range.php',
        'Microsoft\\PhpParser\\ResolvedName' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/ResolvedName.php',
        'Microsoft\\PhpParser\\SkippedToken' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/SkippedToken.php',
        'Microsoft\\PhpParser\\TextEdit' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/TextEdit.php',
        'Microsoft\\PhpParser\\Token' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/Token.php',
        'Microsoft\\PhpParser\\TokenKind' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/TokenKind.php',
        'Microsoft\\PhpParser\\TokenStreamProviderFactory' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/TokenStreamProviderFactory.php',
        'Microsoft\\PhpParser\\TokenStreamProviderInterface' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/TokenStreamProviderInterface.php',
        'Microsoft\\PhpParser\\TokenStringMaps' => __DIR__ . '/..' . '/microsoft/tolerant-php-parser/src/TokenStringMaps.php',
        'Phan\\AST\\ASTSimplifier' => __DIR__ . '/../..' . '/src/Phan/AST/ASTSimplifier.php',
        'Phan\\AST\\AnalysisVisitor' => __DIR__ . '/../..' . '/src/Phan/AST/AnalysisVisitor.php',
        'Phan\\AST\\ContextNode' => __DIR__ . '/../..' . '/src/Phan/AST/ContextNode.php',
        'Phan\\AST\\Parser' => __DIR__ . '/../..' . '/src/Phan/AST/Parser.php',
        'Phan\\AST\\PhanAnnotationAdder' => __DIR__ . '/../..' . '/src/Phan/AST/PhanAnnotationAdder.php',
        'Phan\\AST\\TolerantASTConverter\\InvalidNodeException' => __DIR__ . '/../..' . '/src/Phan/AST/TolerantASTConverter/InvalidNodeException.php',
        'Phan\\AST\\TolerantASTConverter\\NodeDumper' => __DIR__ . '/../..' . '/src/Phan/AST/TolerantASTConverter/NodeDumper.php',
        'Phan\\AST\\TolerantASTConverter\\ParseException' => __DIR__ . '/../..' . '/src/Phan/AST/TolerantASTConverter/ParseException.php',
        'Phan\\AST\\TolerantASTConverter\\String_' => __DIR__ . '/../..' . '/src/Phan/AST/TolerantASTConverter/String_.php',
        'Phan\\AST\\TolerantASTConverter\\TolerantASTConverter' => __DIR__ . '/../..' . '/src/Phan/AST/TolerantASTConverter/TolerantASTConverter.php',
        'Phan\\AST\\UnionTypeVisitor' => __DIR__ . '/../..' . '/src/Phan/AST/UnionTypeVisitor.php',
        'Phan\\AST\\Visitor\\Element' => __DIR__ . '/../..' . '/src/Phan/AST/Visitor/Element.php',
        'Phan\\AST\\Visitor\\FlagVisitor' => __DIR__ . '/../..' . '/src/Phan/AST/Visitor/FlagVisitor.php',
        'Phan\\AST\\Visitor\\FlagVisitorImplementation' => __DIR__ . '/../..' . '/src/Phan/AST/Visitor/FlagVisitorImplementation.php',
        'Phan\\AST\\Visitor\\KindVisitor' => __DIR__ . '/../..' . '/src/Phan/AST/Visitor/KindVisitor.php',
        'Phan\\AST\\Visitor\\KindVisitorImplementation' => __DIR__ . '/../..' . '/src/Phan/AST/Visitor/KindVisitorImplementation.php',
        'Phan\\Analysis' => __DIR__ . '/../..' . '/src/Phan/Analysis.php',
        'Phan\\Analysis\\AbstractMethodAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/AbstractMethodAnalyzer.php',
        'Phan\\Analysis\\Analyzable' => __DIR__ . '/../..' . '/src/Phan/Analysis/Analyzable.php',
        'Phan\\Analysis\\ArgumentType' => __DIR__ . '/../..' . '/src/Phan/Analysis/ArgumentType.php',
        'Phan\\Analysis\\AssignOperatorFlagVisitor' => __DIR__ . '/../..' . '/src/Phan/Analysis/AssignOperatorFlagVisitor.php',
        'Phan\\Analysis\\AssignmentVisitor' => __DIR__ . '/../..' . '/src/Phan/Analysis/AssignmentVisitor.php',
        'Phan\\Analysis\\BinaryOperatorFlagVisitor' => __DIR__ . '/../..' . '/src/Phan/Analysis/BinaryOperatorFlagVisitor.php',
        'Phan\\Analysis\\BlockExitStatusChecker' => __DIR__ . '/../..' . '/src/Phan/Analysis/BlockExitStatusChecker.php',
        'Phan\\Analysis\\ClassInheritanceAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/ClassInheritanceAnalyzer.php',
        'Phan\\Analysis\\CompositionAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/CompositionAnalyzer.php',
        'Phan\\Analysis\\ConditionVisitor' => __DIR__ . '/../..' . '/src/Phan/Analysis/ConditionVisitor.php',
        'Phan\\Analysis\\ConditionVisitorUtil' => __DIR__ . '/../..' . '/src/Phan/Analysis/ConditionVisitorUtil.php',
        'Phan\\Analysis\\ContextMergeVisitor' => __DIR__ . '/../..' . '/src/Phan/Analysis/ContextMergeVisitor.php',
        'Phan\\Analysis\\DuplicateClassAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/DuplicateClassAnalyzer.php',
        'Phan\\Analysis\\DuplicateFunctionAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/DuplicateFunctionAnalyzer.php',
        'Phan\\Analysis\\NegatedConditionVisitor' => __DIR__ . '/../..' . '/src/Phan/Analysis/NegatedConditionVisitor.php',
        'Phan\\Analysis\\ParameterTypesAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/ParameterTypesAnalyzer.php',
        'Phan\\Analysis\\ParentConstructorCalledAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/ParentConstructorCalledAnalyzer.php',
        'Phan\\Analysis\\PostOrderAnalysisVisitor' => __DIR__ . '/../..' . '/src/Phan/Analysis/PostOrderAnalysisVisitor.php',
        'Phan\\Analysis\\PreOrderAnalysisVisitor' => __DIR__ . '/../..' . '/src/Phan/Analysis/PreOrderAnalysisVisitor.php',
        'Phan\\Analysis\\PropertyTypesAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/PropertyTypesAnalyzer.php',
        'Phan\\Analysis\\ReferenceCountsAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/ReferenceCountsAnalyzer.php',
        'Phan\\Analysis\\ReturnTypesAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/ReturnTypesAnalyzer.php',
        'Phan\\Analysis\\ScopeVisitor' => __DIR__ . '/../..' . '/src/Phan/Analysis/ScopeVisitor.php',
        'Phan\\Analysis\\ThrowsTypesAnalyzer' => __DIR__ . '/../..' . '/src/Phan/Analysis/ThrowsTypesAnalyzer.php',
        'Phan\\BlockAnalysisVisitor' => __DIR__ . '/../..' . '/src/Phan/BlockAnalysisVisitor.php',
        'Phan\\CLI' => __DIR__ . '/../..' . '/src/Phan/CLI.php',
        'Phan\\CodeBase' => __DIR__ . '/../..' . '/src/Phan/CodeBase.php',
        'Phan\\CodeBase\\ClassMap' => __DIR__ . '/../..' . '/src/Phan/CodeBase/ClassMap.php',
        'Phan\\CodeBase\\UndoTracker' => __DIR__ . '/../..' . '/src/Phan/CodeBase/UndoTracker.php',
        'Phan\\Config' => __DIR__ . '/../..' . '/src/Phan/Config.php',
        'Phan\\Config\\InitializedSettings' => __DIR__ . '/../..' . '/src/Phan/Config/InitializedSettings.php',
        'Phan\\Config\\Initializer' => __DIR__ . '/../..' . '/src/Phan/Config/Initializer.php',
        'Phan\\Daemon' => __DIR__ . '/../..' . '/src/Phan/Daemon.php',
        'Phan\\Daemon\\ExitException' => __DIR__ . '/../..' . '/src/Phan/Daemon/ExitException.php',
        'Phan\\Daemon\\Request' => __DIR__ . '/../..' . '/src/Phan/Daemon/Request.php',
        'Phan\\Daemon\\Transport\\CapturerResponder' => __DIR__ . '/../..' . '/src/Phan/Daemon/Transport/CapturerResponder.php',
        'Phan\\Daemon\\Transport\\Responder' => __DIR__ . '/../..' . '/src/Phan/Daemon/Transport/Responder.php',
        'Phan\\Daemon\\Transport\\StreamResponder' => __DIR__ . '/../..' . '/src/Phan/Daemon/Transport/StreamResponder.php',
        'Phan\\Debug' => __DIR__ . '/../..' . '/src/Phan/Debug.php',
        'Phan\\Debug\\DebugUnionType' => __DIR__ . '/../..' . '/src/Phan/Debug/DebugUnionType.php',
        'Phan\\Exception\\CodeBaseException' => __DIR__ . '/../..' . '/src/Phan/Exception/CodeBaseException.php',
        'Phan\\Exception\\EmptyFQSENException' => __DIR__ . '/../..' . '/src/Phan/Exception/EmptyFQSENException.php',
        'Phan\\Exception\\IssueException' => __DIR__ . '/../..' . '/src/Phan/Exception/IssueException.php',
        'Phan\\Exception\\NodeException' => __DIR__ . '/../..' . '/src/Phan/Exception/NodeException.php',
        'Phan\\Exception\\TypeException' => __DIR__ . '/../..' . '/src/Phan/Exception/TypeException.php',
        'Phan\\Exception\\UnanalyzableException' => __DIR__ . '/../..' . '/src/Phan/Exception/UnanalyzableException.php',
        'Phan\\ForkPool' => __DIR__ . '/../..' . '/src/Phan/ForkPool.php',
        'Phan\\Issue' => __DIR__ . '/../..' . '/src/Phan/Issue.php',
        'Phan\\IssueInstance' => __DIR__ . '/../..' . '/src/Phan/IssueInstance.php',
        'Phan\\LanguageServer\\ClientHandler' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/ClientHandler.php',
        'Phan\\LanguageServer\\Client\\TextDocument' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Client/TextDocument.php',
        'Phan\\LanguageServer\\FileMapping' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/FileMapping.php',
        'Phan\\LanguageServer\\IdGenerator' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/IdGenerator.php',
        'Phan\\LanguageServer\\LanguageClient' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/LanguageClient.php',
        'Phan\\LanguageServer\\LanguageServer' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/LanguageServer.php',
        'Phan\\LanguageServer\\Logger' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Logger.php',
        'Phan\\LanguageServer\\ProtocolReader' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/ProtocolReader.php',
        'Phan\\LanguageServer\\ProtocolStreamReader' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/ProtocolStreamReader.php',
        'Phan\\LanguageServer\\ProtocolStreamWriter' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/ProtocolStreamWriter.php',
        'Phan\\LanguageServer\\ProtocolWriter' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/ProtocolWriter.php',
        'Phan\\LanguageServer\\Protocol\\ClientCapabilities' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/ClientCapabilities.php',
        'Phan\\LanguageServer\\Protocol\\Diagnostic' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/Diagnostic.php',
        'Phan\\LanguageServer\\Protocol\\DiagnosticSeverity' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/DiagnosticSeverity.php',
        'Phan\\LanguageServer\\Protocol\\FileChangeType' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/FileChangeType.php',
        'Phan\\LanguageServer\\Protocol\\FileEvent' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/FileEvent.php',
        'Phan\\LanguageServer\\Protocol\\InitializeResult' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/InitializeResult.php',
        'Phan\\LanguageServer\\Protocol\\Message' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/Message.php',
        'Phan\\LanguageServer\\Protocol\\Position' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/Position.php',
        'Phan\\LanguageServer\\Protocol\\Range' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/Range.php',
        'Phan\\LanguageServer\\Protocol\\SaveOptions' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/SaveOptions.php',
        'Phan\\LanguageServer\\Protocol\\ServerCapabilities' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/ServerCapabilities.php',
        'Phan\\LanguageServer\\Protocol\\TextDocumentContentChangeEvent' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/TextDocumentContentChangeEvent.php',
        'Phan\\LanguageServer\\Protocol\\TextDocumentIdentifier' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/TextDocumentIdentifier.php',
        'Phan\\LanguageServer\\Protocol\\TextDocumentItem' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/TextDocumentItem.php',
        'Phan\\LanguageServer\\Protocol\\TextDocumentSyncKind' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/TextDocumentSyncKind.php',
        'Phan\\LanguageServer\\Protocol\\TextDocumentSyncOptions' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/TextDocumentSyncOptions.php',
        'Phan\\LanguageServer\\Protocol\\VersionedTextDocumentIdentifier' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Protocol/VersionedTextDocumentIdentifier.php',
        'Phan\\LanguageServer\\Server\\TextDocument' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Server/TextDocument.php',
        'Phan\\LanguageServer\\Server\\Workspace' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Server/Workspace.php',
        'Phan\\LanguageServer\\Utils' => __DIR__ . '/../..' . '/src/Phan/LanguageServer/Utils.php',
        'Phan\\Language\\AnnotatedUnionType' => __DIR__ . '/../..' . '/src/Phan/Language/AnnotatedUnionType.php',
        'Phan\\Language\\Context' => __DIR__ . '/../..' . '/src/Phan/Language/Context.php',
        'Phan\\Language\\Element\\AddressableElement' => __DIR__ . '/../..' . '/src/Phan/Language/Element/AddressableElement.php',
        'Phan\\Language\\Element\\AddressableElementInterface' => __DIR__ . '/../..' . '/src/Phan/Language/Element/AddressableElementInterface.php',
        'Phan\\Language\\Element\\ClassAliasRecord' => __DIR__ . '/../..' . '/src/Phan/Language/Element/ClassAliasRecord.php',
        'Phan\\Language\\Element\\ClassConstant' => __DIR__ . '/../..' . '/src/Phan/Language/Element/ClassConstant.php',
        'Phan\\Language\\Element\\ClassElement' => __DIR__ . '/../..' . '/src/Phan/Language/Element/ClassElement.php',
        'Phan\\Language\\Element\\Clazz' => __DIR__ . '/../..' . '/src/Phan/Language/Element/Clazz.php',
        'Phan\\Language\\Element\\ClosedScopeElement' => __DIR__ . '/../..' . '/src/Phan/Language/Element/ClosedScopeElement.php',
        'Phan\\Language\\Element\\Comment' => __DIR__ . '/../..' . '/src/Phan/Language/Element/Comment.php',
        'Phan\\Language\\Element\\Comment\\Method' => __DIR__ . '/../..' . '/src/Phan/Language/Element/Comment/Method.php',
        'Phan\\Language\\Element\\Comment\\Parameter' => __DIR__ . '/../..' . '/src/Phan/Language/Element/Comment/Parameter.php',
        'Phan\\Language\\Element\\ConstantInterface' => __DIR__ . '/../..' . '/src/Phan/Language/Element/ConstantInterface.php',
        'Phan\\Language\\Element\\ConstantTrait' => __DIR__ . '/../..' . '/src/Phan/Language/Element/ConstantTrait.php',
        'Phan\\Language\\Element\\ElementFutureUnionType' => __DIR__ . '/../..' . '/src/Phan/Language/Element/ElementFutureUnionType.php',
        'Phan\\Language\\Element\\Flags' => __DIR__ . '/../..' . '/src/Phan/Language/Element/Flags.php',
        'Phan\\Language\\Element\\Func' => __DIR__ . '/../..' . '/src/Phan/Language/Element/Func.php',
        'Phan\\Language\\Element\\FunctionFactory' => __DIR__ . '/../..' . '/src/Phan/Language/Element/FunctionFactory.php',
        'Phan\\Language\\Element\\FunctionInterface' => __DIR__ . '/../..' . '/src/Phan/Language/Element/FunctionInterface.php',
        'Phan\\Language\\Element\\FunctionTrait' => __DIR__ . '/../..' . '/src/Phan/Language/Element/FunctionTrait.php',
        'Phan\\Language\\Element\\GlobalConstant' => __DIR__ . '/../..' . '/src/Phan/Language/Element/GlobalConstant.php',
        'Phan\\Language\\Element\\Method' => __DIR__ . '/../..' . '/src/Phan/Language/Element/Method.php',
        'Phan\\Language\\Element\\Parameter' => __DIR__ . '/../..' . '/src/Phan/Language/Element/Parameter.php',
        'Phan\\Language\\Element\\PassByReferenceVariable' => __DIR__ . '/../..' . '/src/Phan/Language/Element/PassByReferenceVariable.php',
        'Phan\\Language\\Element\\Property' => __DIR__ . '/../..' . '/src/Phan/Language/Element/Property.php',
        'Phan\\Language\\Element\\TraitAdaptations' => __DIR__ . '/../..' . '/src/Phan/Language/Element/TraitAdaptations.php',
        'Phan\\Language\\Element\\TraitAliasSource' => __DIR__ . '/../..' . '/src/Phan/Language/Element/TraitAliasSource.php',
        'Phan\\Language\\Element\\TypedElement' => __DIR__ . '/../..' . '/src/Phan/Language/Element/TypedElement.php',
        'Phan\\Language\\Element\\TypedElementInterface' => __DIR__ . '/../..' . '/src/Phan/Language/Element/TypedElementInterface.php',
        'Phan\\Language\\Element\\UnaddressableTypedElement' => __DIR__ . '/../..' . '/src/Phan/Language/Element/UnaddressableTypedElement.php',
        'Phan\\Language\\Element\\Variable' => __DIR__ . '/../..' . '/src/Phan/Language/Element/Variable.php',
        'Phan\\Language\\Element\\VariadicParameter' => __DIR__ . '/../..' . '/src/Phan/Language/Element/VariadicParameter.php',
        'Phan\\Language\\EmptyUnionType' => __DIR__ . '/../..' . '/src/Phan/Language/EmptyUnionType.php',
        'Phan\\Language\\FQSEN' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN.php',
        'Phan\\Language\\FQSEN\\AbstractFQSEN' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/AbstractFQSEN.php',
        'Phan\\Language\\FQSEN\\Alternatives' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/Alternatives.php',
        'Phan\\Language\\FQSEN\\FullyQualifiedClassConstantName' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/FullyQualifiedClassConstantName.php',
        'Phan\\Language\\FQSEN\\FullyQualifiedClassElement' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/FullyQualifiedClassElement.php',
        'Phan\\Language\\FQSEN\\FullyQualifiedClassName' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/FullyQualifiedClassName.php',
        'Phan\\Language\\FQSEN\\FullyQualifiedConstantName' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/FullyQualifiedConstantName.php',
        'Phan\\Language\\FQSEN\\FullyQualifiedFunctionLikeName' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/FullyQualifiedFunctionLikeName.php',
        'Phan\\Language\\FQSEN\\FullyQualifiedFunctionName' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/FullyQualifiedFunctionName.php',
        'Phan\\Language\\FQSEN\\FullyQualifiedGlobalConstantName' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/FullyQualifiedGlobalConstantName.php',
        'Phan\\Language\\FQSEN\\FullyQualifiedGlobalStructuralElement' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/FullyQualifiedGlobalStructuralElement.php',
        'Phan\\Language\\FQSEN\\FullyQualifiedMethodName' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/FullyQualifiedMethodName.php',
        'Phan\\Language\\FQSEN\\FullyQualifiedPropertyName' => __DIR__ . '/../..' . '/src/Phan/Language/FQSEN/FullyQualifiedPropertyName.php',
        'Phan\\Language\\FileRef' => __DIR__ . '/../..' . '/src/Phan/Language/FileRef.php',
        'Phan\\Language\\FutureUnionType' => __DIR__ . '/../..' . '/src/Phan/Language/FutureUnionType.php',
        'Phan\\Language\\NamespaceMapEntry' => __DIR__ . '/../..' . '/src/Phan/Language/NamespaceMapEntry.php',
        'Phan\\Language\\Scope' => __DIR__ . '/../..' . '/src/Phan/Language/Scope.php',
        'Phan\\Language\\Scope\\BranchScope' => __DIR__ . '/../..' . '/src/Phan/Language/Scope/BranchScope.php',
        'Phan\\Language\\Scope\\ClassScope' => __DIR__ . '/../..' . '/src/Phan/Language/Scope/ClassScope.php',
        'Phan\\Language\\Scope\\ClosedScope' => __DIR__ . '/../..' . '/src/Phan/Language/Scope/ClosedScope.php',
        'Phan\\Language\\Scope\\ClosureScope' => __DIR__ . '/../..' . '/src/Phan/Language/Scope/ClosureScope.php',
        'Phan\\Language\\Scope\\FunctionLikeScope' => __DIR__ . '/../..' . '/src/Phan/Language/Scope/FunctionLikeScope.php',
        'Phan\\Language\\Scope\\GlobalScope' => __DIR__ . '/../..' . '/src/Phan/Language/Scope/GlobalScope.php',
        'Phan\\Language\\Scope\\PropertyScope' => __DIR__ . '/../..' . '/src/Phan/Language/Scope/PropertyScope.php',
        'Phan\\Language\\Type' => __DIR__ . '/../..' . '/src/Phan/Language/Type.php',
        'Phan\\Language\\Type\\ArrayShapeType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/ArrayShapeType.php',
        'Phan\\Language\\Type\\ArrayType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/ArrayType.php',
        'Phan\\Language\\Type\\BoolType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/BoolType.php',
        'Phan\\Language\\Type\\CallableDeclarationType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/CallableDeclarationType.php',
        'Phan\\Language\\Type\\CallableType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/CallableType.php',
        'Phan\\Language\\Type\\ClosureDeclarationParameter' => __DIR__ . '/../..' . '/src/Phan/Language/Type/ClosureDeclarationParameter.php',
        'Phan\\Language\\Type\\ClosureDeclarationType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/ClosureDeclarationType.php',
        'Phan\\Language\\Type\\ClosureType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/ClosureType.php',
        'Phan\\Language\\Type\\FalseType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/FalseType.php',
        'Phan\\Language\\Type\\FloatType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/FloatType.php',
        'Phan\\Language\\Type\\FunctionLikeDeclarationType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/FunctionLikeDeclarationType.php',
        'Phan\\Language\\Type\\GenericArrayType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/GenericArrayType.php',
        'Phan\\Language\\Type\\GenericMultiArrayType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/GenericMultiArrayType.php',
        'Phan\\Language\\Type\\IntType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/IntType.php',
        'Phan\\Language\\Type\\IterableType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/IterableType.php',
        'Phan\\Language\\Type\\MixedType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/MixedType.php',
        'Phan\\Language\\Type\\MultiType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/MultiType.php',
        'Phan\\Language\\Type\\NativeType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/NativeType.php',
        'Phan\\Language\\Type\\NullType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/NullType.php',
        'Phan\\Language\\Type\\ObjectType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/ObjectType.php',
        'Phan\\Language\\Type\\ResourceType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/ResourceType.php',
        'Phan\\Language\\Type\\ScalarRawType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/ScalarRawType.php',
        'Phan\\Language\\Type\\ScalarType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/ScalarType.php',
        'Phan\\Language\\Type\\StaticType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/StaticType.php',
        'Phan\\Language\\Type\\StringType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/StringType.php',
        'Phan\\Language\\Type\\TemplateType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/TemplateType.php',
        'Phan\\Language\\Type\\TrueType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/TrueType.php',
        'Phan\\Language\\Type\\VoidType' => __DIR__ . '/../..' . '/src/Phan/Language/Type/VoidType.php',
        'Phan\\Language\\UnionType' => __DIR__ . '/../..' . '/src/Phan/Language/UnionType.php',
        'Phan\\Language\\UnionTypeBuilder' => __DIR__ . '/../..' . '/src/Phan/Language/UnionTypeBuilder.php',
        'Phan\\Library\\FileCache' => __DIR__ . '/../..' . '/src/Phan/Library/FileCache.php',
        'Phan\\Library\\FileCacheEntry' => __DIR__ . '/../..' . '/src/Phan/Library/FileCacheEntry.php',
        'Phan\\Library\\Hasher' => __DIR__ . '/../..' . '/src/Phan/Library/Hasher.php',
        'Phan\\Library\\Hasher\\Consistent' => __DIR__ . '/../..' . '/src/Phan/Library/Hasher/Consistent.php',
        'Phan\\Library\\Hasher\\Sequential' => __DIR__ . '/../..' . '/src/Phan/Library/Hasher/Sequential.php',
        'Phan\\Library\\Map' => __DIR__ . '/../..' . '/src/Phan/Library/Map.php',
        'Phan\\Library\\None' => __DIR__ . '/../..' . '/src/Phan/Library/None.php',
        'Phan\\Library\\Option' => __DIR__ . '/../..' . '/src/Phan/Library/Option.php',
        'Phan\\Library\\Set' => __DIR__ . '/../..' . '/src/Phan/Library/Set.php',
        'Phan\\Library\\Some' => __DIR__ . '/../..' . '/src/Phan/Library/Some.php',
        'Phan\\Library\\StderrLogger' => __DIR__ . '/../..' . '/src/Phan/Library/StderrLogger.php',
        'Phan\\Library\\Tuple' => __DIR__ . '/../..' . '/src/Phan/Library/Tuple.php',
        'Phan\\Library\\Tuple1' => __DIR__ . '/../..' . '/src/Phan/Library/Tuple1.php',
        'Phan\\Library\\Tuple2' => __DIR__ . '/../..' . '/src/Phan/Library/Tuple2.php',
        'Phan\\Library\\Tuple3' => __DIR__ . '/../..' . '/src/Phan/Library/Tuple3.php',
        'Phan\\Library\\Tuple4' => __DIR__ . '/../..' . '/src/Phan/Library/Tuple4.php',
        'Phan\\Library\\Tuple5' => __DIR__ . '/../..' . '/src/Phan/Library/Tuple5.php',
        'Phan\\Memoize' => __DIR__ . '/../..' . '/src/Phan/Memoize.php',
        'Phan\\Ordering' => __DIR__ . '/../..' . '/src/Phan/Ordering.php',
        'Phan\\Output\\BufferedPrinterInterface' => __DIR__ . '/../..' . '/src/Phan/Output/BufferedPrinterInterface.php',
        'Phan\\Output\\Collector\\BufferingCollector' => __DIR__ . '/../..' . '/src/Phan/Output/Collector/BufferingCollector.php',
        'Phan\\Output\\Collector\\ParallelChildCollector' => __DIR__ . '/../..' . '/src/Phan/Output/Collector/ParallelChildCollector.php',
        'Phan\\Output\\Collector\\ParallelParentCollector' => __DIR__ . '/../..' . '/src/Phan/Output/Collector/ParallelParentCollector.php',
        'Phan\\Output\\Colorizing' => __DIR__ . '/../..' . '/src/Phan/Output/Colorizing.php',
        'Phan\\Output\\Filter\\AnyFilter' => __DIR__ . '/../..' . '/src/Phan/Output/Filter/AnyFilter.php',
        'Phan\\Output\\Filter\\CategoryIssueFilter' => __DIR__ . '/../..' . '/src/Phan/Output/Filter/CategoryIssueFilter.php',
        'Phan\\Output\\Filter\\ChainedIssueFilter' => __DIR__ . '/../..' . '/src/Phan/Output/Filter/ChainedIssueFilter.php',
        'Phan\\Output\\Filter\\FileIssueFilter' => __DIR__ . '/../..' . '/src/Phan/Output/Filter/FileIssueFilter.php',
        'Phan\\Output\\Filter\\MinimumSeverityFilter' => __DIR__ . '/../..' . '/src/Phan/Output/Filter/MinimumSeverityFilter.php',
        'Phan\\Output\\IgnoredFilesFilterInterface' => __DIR__ . '/../..' . '/src/Phan/Output/IgnoredFilesFilterInterface.php',
        'Phan\\Output\\IssueCollectorInterface' => __DIR__ . '/../..' . '/src/Phan/Output/IssueCollectorInterface.php',
        'Phan\\Output\\IssueFilterInterface' => __DIR__ . '/../..' . '/src/Phan/Output/IssueFilterInterface.php',
        'Phan\\Output\\IssuePrinterInterface' => __DIR__ . '/../..' . '/src/Phan/Output/IssuePrinterInterface.php',
        'Phan\\Output\\PrinterFactory' => __DIR__ . '/../..' . '/src/Phan/Output/PrinterFactory.php',
        'Phan\\Output\\Printer\\CSVPrinter' => __DIR__ . '/../..' . '/src/Phan/Output/Printer/CSVPrinter.php',
        'Phan\\Output\\Printer\\CheckstylePrinter' => __DIR__ . '/../..' . '/src/Phan/Output/Printer/CheckstylePrinter.php',
        'Phan\\Output\\Printer\\CodeClimatePrinter' => __DIR__ . '/../..' . '/src/Phan/Output/Printer/CodeClimatePrinter.php',
        'Phan\\Output\\Printer\\JSONPrinter' => __DIR__ . '/../..' . '/src/Phan/Output/Printer/JSONPrinter.php',
        'Phan\\Output\\Printer\\PlainTextPrinter' => __DIR__ . '/../..' . '/src/Phan/Output/Printer/PlainTextPrinter.php',
        'Phan\\Output\\Printer\\PylintPrinter' => __DIR__ . '/../..' . '/src/Phan/Output/Printer/PylintPrinter.php',
        'Phan\\Parse\\ParseVisitor' => __DIR__ . '/../..' . '/src/Phan/Parse/ParseVisitor.php',
        'Phan\\Phan' => __DIR__ . '/../..' . '/src/Phan/Phan.php',
        'Phan\\Plugin' => __DIR__ . '/../..' . '/src/Phan/Plugin.php',
        'Phan\\PluginV2' => __DIR__ . '/../..' . '/src/Phan/PluginV2.php',
        'Phan\\PluginV2\\AfterAnalyzeFileCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/AfterAnalyzeFileCapability.php',
        'Phan\\PluginV2\\AnalyzeClassCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/AnalyzeClassCapability.php',
        'Phan\\PluginV2\\AnalyzeFunctionCallCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/AnalyzeFunctionCallCapability.php',
        'Phan\\PluginV2\\AnalyzeFunctionCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/AnalyzeFunctionCapability.php',
        'Phan\\PluginV2\\AnalyzeMethodCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/AnalyzeMethodCapability.php',
        'Phan\\PluginV2\\AnalyzeNodeCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/AnalyzeNodeCapability.php',
        'Phan\\PluginV2\\AnalyzePropertyCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/AnalyzePropertyCapability.php',
        'Phan\\PluginV2\\BeforeAnalyzeFileCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/BeforeAnalyzeFileCapability.php',
        'Phan\\PluginV2\\FinalizeProcessCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/FinalizeProcessCapability.php',
        'Phan\\PluginV2\\IssueEmitter' => __DIR__ . '/../..' . '/src/Phan/PluginV2/IssueEmitter.php',
        'Phan\\PluginV2\\LegacyAnalyzeNodeCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/LegacyAnalyzeNodeCapability.php',
        'Phan\\PluginV2\\LegacyPostAnalyzeNodeCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/LegacyPostAnalyzeNodeCapability.php',
        'Phan\\PluginV2\\LegacyPreAnalyzeNodeCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/LegacyPreAnalyzeNodeCapability.php',
        'Phan\\PluginV2\\PluginAwareAnalysisVisitor' => __DIR__ . '/../..' . '/src/Phan/PluginV2/PluginAwareAnalysisVisitor.php',
        'Phan\\PluginV2\\PluginAwareBaseAnalysisVisitor' => __DIR__ . '/../..' . '/src/Phan/PluginV2/PluginAwareBaseAnalysisVisitor.php',
        'Phan\\PluginV2\\PluginAwarePostAnalysisVisitor' => __DIR__ . '/../..' . '/src/Phan/PluginV2/PluginAwarePostAnalysisVisitor.php',
        'Phan\\PluginV2\\PluginAwarePreAnalysisVisitor' => __DIR__ . '/../..' . '/src/Phan/PluginV2/PluginAwarePreAnalysisVisitor.php',
        'Phan\\PluginV2\\PostAnalyzeNodeCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/PostAnalyzeNodeCapability.php',
        'Phan\\PluginV2\\PreAnalyzeNodeCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/PreAnalyzeNodeCapability.php',
        'Phan\\PluginV2\\ReturnTypeOverrideCapability' => __DIR__ . '/../..' . '/src/Phan/PluginV2/ReturnTypeOverrideCapability.php',
        'Phan\\PluginV2\\StopParamAnalysisException' => __DIR__ . '/../..' . '/src/Phan/PluginV2/StopParamAnalysisException.php',
        'Phan\\Plugin\\ClosuresForKind' => __DIR__ . '/../..' . '/src/Phan/Plugin/ClosuresForKind.php',
        'Phan\\Plugin\\ConfigPluginSet' => __DIR__ . '/../..' . '/src/Phan/Plugin/ConfigPluginSet.php',
        'Phan\\Plugin\\Internal\\ArrayReturnTypeOverridePlugin' => __DIR__ . '/../..' . '/src/Phan/Plugin/Internal/ArrayReturnTypeOverridePlugin.php',
        'Phan\\Plugin\\Internal\\CallableParamPlugin' => __DIR__ . '/../..' . '/src/Phan/Plugin/Internal/CallableParamPlugin.php',
        'Phan\\Plugin\\Internal\\ClosureReturnTypeOverridePlugin' => __DIR__ . '/../..' . '/src/Phan/Plugin/Internal/ClosureReturnTypeOverridePlugin.php',
        'Phan\\Plugin\\Internal\\CompactPlugin' => __DIR__ . '/../..' . '/src/Phan/Plugin/Internal/CompactPlugin.php',
        'Phan\\Plugin\\Internal\\DependentReturnTypeOverridePlugin' => __DIR__ . '/../..' . '/src/Phan/Plugin/Internal/DependentReturnTypeOverridePlugin.php',
        'Phan\\Plugin\\Internal\\MiscParamPlugin' => __DIR__ . '/../..' . '/src/Phan/Plugin/Internal/MiscParamPlugin.php',
        'Phan\\Plugin\\Internal\\StringFunctionPlugin' => __DIR__ . '/../..' . '/src/Phan/Plugin/Internal/StringFunctionPlugin.php',
        'Phan\\Plugin\\PluginImplementation' => __DIR__ . '/../..' . '/src/Phan/Plugin/PluginImplementation.php',
        'Phan\\Prep' => __DIR__ . '/../..' . '/src/Phan/Prep.php',
        'Phan\\Profile' => __DIR__ . '/../..' . '/src/Phan/Profile.php',
        'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php',
        'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php',
        'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php',
        'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php',
        'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php',
        'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php',
        'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php',
        'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php',
        'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
        'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
        'Sabre\\Event\\Emitter' => __DIR__ . '/..' . '/sabre/event/lib/Emitter.php',
        'Sabre\\Event\\EmitterInterface' => __DIR__ . '/..' . '/sabre/event/lib/EmitterInterface.php',
        'Sabre\\Event\\EmitterTrait' => __DIR__ . '/..' . '/sabre/event/lib/EmitterTrait.php',
        'Sabre\\Event\\EventEmitter' => __DIR__ . '/..' . '/sabre/event/lib/EventEmitter.php',
        'Sabre\\Event\\Loop\\Loop' => __DIR__ . '/..' . '/sabre/event/lib/Loop/Loop.php',
        'Sabre\\Event\\Promise' => __DIR__ . '/..' . '/sabre/event/lib/Promise.php',
        'Sabre\\Event\\PromiseAlreadyResolvedException' => __DIR__ . '/..' . '/sabre/event/lib/PromiseAlreadyResolvedException.php',
        'Sabre\\Event\\Version' => __DIR__ . '/..' . '/sabre/event/lib/Version.php',
        'Sabre\\Event\\WildcardEmitter' => __DIR__ . '/..' . '/sabre/event/lib/WildcardEmitter.php',
        'Sabre\\Event\\WildcardEmitterTrait' => __DIR__ . '/..' . '/sabre/event/lib/WildcardEmitterTrait.php',
        'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php',
        'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
        'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/ContainerCommandLoader.php',
        'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/FactoryCommandLoader.php',
        'Symfony\\Component\\Console\\Command\\Command' => __DIR__ . '/..' . '/symfony/console/Command/Command.php',
        'Symfony\\Component\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/symfony/console/Command/HelpCommand.php',
        'Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php',
        'Symfony\\Component\\Console\\Command\\LockableTrait' => __DIR__ . '/..' . '/symfony/console/Command/LockableTrait.php',
        'Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php',
        'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php',
        'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php',
        'Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php',
        'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php',
        'Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php',
        'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleErrorEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleExceptionEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleTerminateEvent.php',
        'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/CommandNotFoundException.php',
        'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/console/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidArgumentException.php',
        'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidOptionException.php',
        'Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php',
        'Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatter.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterInterface.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyle.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleInterface.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleStack.php',
        'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php',
        'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php',
        'Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php',
        'Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php',
        'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php',
        'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php',
        'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php',
        'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php',
        'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php',
        'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php',
        'Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php',
        'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/SymfonyQuestionHelper.php',
        'Symfony\\Component\\Console\\Helper\\Table' => __DIR__ . '/..' . '/symfony/console/Helper/Table.php',
        'Symfony\\Component\\Console\\Helper\\TableCell' => __DIR__ . '/..' . '/symfony/console/Helper/TableCell.php',
        'Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php',
        'Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php',
        'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php',
        'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php',
        'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php',
        'Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php',
        'Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php',
        'Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php',
        'Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php',
        'Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php',
        'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php',
        'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php',
        'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php',
        'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php',
        'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php',
        'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php',
        'Symfony\\Component\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/symfony/console/Output/NullOutput.php',
        'Symfony\\Component\\Console\\Output\\Output' => __DIR__ . '/..' . '/symfony/console/Output/Output.php',
        'Symfony\\Component\\Console\\Output\\OutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/OutputInterface.php',
        'Symfony\\Component\\Console\\Output\\StreamOutput' => __DIR__ . '/..' . '/symfony/console/Output/StreamOutput.php',
        'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php',
        'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php',
        'Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php',
        'Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php',
        'Symfony\\Component\\Console\\Style\\StyleInterface' => __DIR__ . '/..' . '/symfony/console/Style/StyleInterface.php',
        'Symfony\\Component\\Console\\Style\\SymfonyStyle' => __DIR__ . '/..' . '/symfony/console/Style/SymfonyStyle.php',
        'Symfony\\Component\\Console\\Terminal' => __DIR__ . '/..' . '/symfony/console/Terminal.php',
        'Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php',
        'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php',
        'Symfony\\Component\\Debug\\BufferingLogger' => __DIR__ . '/..' . '/symfony/debug/BufferingLogger.php',
        'Symfony\\Component\\Debug\\Debug' => __DIR__ . '/..' . '/symfony/debug/Debug.php',
        'Symfony\\Component\\Debug\\DebugClassLoader' => __DIR__ . '/..' . '/symfony/debug/DebugClassLoader.php',
        'Symfony\\Component\\Debug\\ErrorHandler' => __DIR__ . '/..' . '/symfony/debug/ErrorHandler.php',
        'Symfony\\Component\\Debug\\ExceptionHandler' => __DIR__ . '/..' . '/symfony/debug/ExceptionHandler.php',
        'Symfony\\Component\\Debug\\Exception\\ClassNotFoundException' => __DIR__ . '/..' . '/symfony/debug/Exception/ClassNotFoundException.php',
        'Symfony\\Component\\Debug\\Exception\\ContextErrorException' => __DIR__ . '/..' . '/symfony/debug/Exception/ContextErrorException.php',
        'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => __DIR__ . '/..' . '/symfony/debug/Exception/FatalErrorException.php',
        'Symfony\\Component\\Debug\\Exception\\FatalThrowableError' => __DIR__ . '/..' . '/symfony/debug/Exception/FatalThrowableError.php',
        'Symfony\\Component\\Debug\\Exception\\FlattenException' => __DIR__ . '/..' . '/symfony/debug/Exception/FlattenException.php',
        'Symfony\\Component\\Debug\\Exception\\OutOfMemoryException' => __DIR__ . '/..' . '/symfony/debug/Exception/OutOfMemoryException.php',
        'Symfony\\Component\\Debug\\Exception\\SilencedErrorContext' => __DIR__ . '/..' . '/symfony/debug/Exception/SilencedErrorContext.php',
        'Symfony\\Component\\Debug\\Exception\\UndefinedFunctionException' => __DIR__ . '/..' . '/symfony/debug/Exception/UndefinedFunctionException.php',
        'Symfony\\Component\\Debug\\Exception\\UndefinedMethodException' => __DIR__ . '/..' . '/symfony/debug/Exception/UndefinedMethodException.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\ClassNotFoundFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\FatalErrorHandlerInterface' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedFunctionFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedMethodFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php',
        'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php',
        'Webmozart\\Assert\\Assert' => __DIR__ . '/..' . '/webmozart/assert/src/Assert.php',
        'phpDocumentor\\Reflection\\DocBlock' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock.php',
        'phpDocumentor\\Reflection\\DocBlockFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlockFactory.php',
        'phpDocumentor\\Reflection\\DocBlockFactoryInterface' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php',
        'phpDocumentor\\Reflection\\DocBlock\\Description' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Description.php',
        'phpDocumentor\\Reflection\\DocBlock\\DescriptionFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php',
        'phpDocumentor\\Reflection\\DocBlock\\ExampleFinder' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php',
        'phpDocumentor\\Reflection\\DocBlock\\Serializer' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php',
        'phpDocumentor\\Reflection\\DocBlock\\StandardTagFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tag' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php',
        'phpDocumentor\\Reflection\\DocBlock\\TagFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Author' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\BaseTag' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Covers' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Deprecated' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Example' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\StaticMethod' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\Strategy' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\AlignFormatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\PassthroughFormatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Generic' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Link' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Method' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Param' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Property' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyRead' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyWrite' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Fqsen' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Reference' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Url' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Return_' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\See' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Since' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Source' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Throws' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Uses' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Var_' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Version' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php',
        'phpDocumentor\\Reflection\\Element' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Element.php',
        'phpDocumentor\\Reflection\\File' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/File.php',
        'phpDocumentor\\Reflection\\Fqsen' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Fqsen.php',
        'phpDocumentor\\Reflection\\FqsenResolver' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/FqsenResolver.php',
        'phpDocumentor\\Reflection\\Location' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Location.php',
        'phpDocumentor\\Reflection\\Project' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Project.php',
        'phpDocumentor\\Reflection\\ProjectFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/ProjectFactory.php',
        'phpDocumentor\\Reflection\\Type' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Type.php',
        'phpDocumentor\\Reflection\\TypeResolver' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/TypeResolver.php',
        'phpDocumentor\\Reflection\\Types\\Array_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Array_.php',
        'phpDocumentor\\Reflection\\Types\\Boolean' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Boolean.php',
        'phpDocumentor\\Reflection\\Types\\Callable_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Callable_.php',
        'phpDocumentor\\Reflection\\Types\\Compound' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Compound.php',
        'phpDocumentor\\Reflection\\Types\\Context' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Context.php',
        'phpDocumentor\\Reflection\\Types\\ContextFactory' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/ContextFactory.php',
        'phpDocumentor\\Reflection\\Types\\Float_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Float_.php',
        'phpDocumentor\\Reflection\\Types\\Integer' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Integer.php',
        'phpDocumentor\\Reflection\\Types\\Iterable_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Iterable_.php',
        'phpDocumentor\\Reflection\\Types\\Mixed_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Mixed_.php',
        'phpDocumentor\\Reflection\\Types\\Null_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Null_.php',
        'phpDocumentor\\Reflection\\Types\\Nullable' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Nullable.php',
        'phpDocumentor\\Reflection\\Types\\Object_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Object_.php',
        'phpDocumentor\\Reflection\\Types\\Parent_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Parent_.php',
        'phpDocumentor\\Reflection\\Types\\Resource_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Resource_.php',
        'phpDocumentor\\Reflection\\Types\\Scalar' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Scalar.php',
        'phpDocumentor\\Reflection\\Types\\Self_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Self_.php',
        'phpDocumentor\\Reflection\\Types\\Static_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Static_.php',
        'phpDocumentor\\Reflection\\Types\\String_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/String_.php',
        'phpDocumentor\\Reflection\\Types\\This' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/This.php',
        'phpDocumentor\\Reflection\\Types\\Void_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Void_.php',
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInitc2bcc8b87b5bd6e082150780bb130a51::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInitc2bcc8b87b5bd6e082150780bb130a51::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInitc2bcc8b87b5bd6e082150780bb130a51::$prefixesPsr0;
            $loader->classMap = ComposerStaticInitc2bcc8b87b5bd6e082150780bb130a51::$classMap;

        }, null, ClassLoader::class);
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\XdebugHandler;

use Psr\Log\LoggerInterface;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class XdebugHandler
{
    const SUFFIX_ALLOW = '_ALLOW_XDEBUG';
    const SUFFIX_INIS = '_ORIGINAL_INIS';
    const RESTART_ID = 'internal';

    private static $name;
    private static $skipped;

    private $cli;
    private $colorOption;
    private $envAllowXdebug;
    private $envOriginalInis;
    private $loaded;
    private $statusWriter;
    private $tmpIni;

    /**
     * Constructor
     *
     * The $envPrefix is used to create distinct environment variables. It is
     * uppercased and prepended to the default base values. For example 'myapp'
     * would result in MYAPP_ALLOW_XDEBUG and MYAPP_ORIGINAL_INIS.
     *
     * @param string $envPrefix Value used in environment variables
     * @param string $colorOption Command-line long option to force color output
     * @throws RuntimeException If a parameter is invalid
     */
    public function __construct($envPrefix, $colorOption = '')
    {
        if (!is_string($envPrefix) || empty($envPrefix) || !is_string($colorOption)) {
            throw new \RuntimeException('Invalid constructor parameter');
        }

        self::$name = strtoupper($envPrefix);
        $this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW;
        $this->envOriginalInis = self::$name.self::SUFFIX_INIS;

        $this->colorOption = $colorOption;
        $this->cli = PHP_SAPI === 'cli';

        if (extension_loaded('xdebug')) {
            $ext = new \ReflectionExtension('xdebug');
            $this->loaded = $ext->getVersion() ?: 'unknown';
        }
    }

    /**
     * Activates status message output to a PSR3 logger
     *
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->statusWriter = new Status($logger, $this->loaded, $this->envAllowXdebug);
    }

    /**
     * Checks if xdebug is loaded and the process needs to be restarted
     *
     * If so, then a tmp ini is created with the xdebug ini entry commented out.
     * If scanned inis have been loaded, these are combined into the tmp ini
     * and PHP_INI_SCAN_DIR is set to an empty value. Current ini locations are
     * are stored in MYAPP_ORIGINAL_INIS (where 'MYAPP' is the prefix passed in the
     * constructor) for use in the restarted process.
     *
     * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG
     * environment variable to 1. This variable is used internally so that the
     * restarted process is created only once and PHP_INI_SCAN_DIR can be
     * restored to its original value.
     */
    public function check()
    {
        $this->notify(Status::CHECK);
        $envArgs = explode('|', strval(getenv($this->envAllowXdebug)), 4);

        if ($this->loaded && empty($envArgs[0])) {
            // Restart required
            $this->notify(Status::RESTART);

            if ($this->prepareRestart()) {
                $command = $this->getCommand($_SERVER['argv']);
                $this->notify(Status::RESTARTING);
                $this->restart($command);
            }
            return;
        }

        if (self::RESTART_ID === $envArgs[0] && count($envArgs) >= 3) {
            // Restarting, so unset environment variable and extract saved values
            $this->notify(Status::RESTARTED);

            putenv($this->envAllowXdebug);
            $version = $envArgs[1];
            $scannedInis = $envArgs[2];

            if (!$this->loaded) {
                // Version is only set if restart is successful
                self::$skipped = $version;
            }

            if ($scannedInis) {
                // Scan dir will have been changed, so restore it
                if (isset($envArgs[3])) {
                    putenv('PHP_INI_SCAN_DIR='.$envArgs[3]);
                } else {
                    putenv('PHP_INI_SCAN_DIR');
                }
            }
            return;
        }

        $this->notify(Status::NORESTART);
    }

    /**
     * Returns an array of php.ini locations with at least one entry
     *
     * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files.
     * The loaded ini location is the first entry and may be empty.
     *
     * @return array
     */
    public static function getAllIniFiles()
    {
        if (!empty(self::$name)) {
            $env = getenv(self::$name.self::SUFFIX_INIS);

            if (false !== $env) {
                return explode(PATH_SEPARATOR, $env);
            }
        }

        $paths = array(strval(php_ini_loaded_file()));

        if ($scanned = php_ini_scanned_files()) {
            $paths = array_merge($paths, array_map('trim', explode(',', $scanned)));
        }

        return $paths;
    }

    /**
     * Returns the xdebug version that triggered a successful restart
     *
     * @return string
     */
    public static function getSkippedVersion()
    {
        return strval(self::$skipped);
    }

    /**
     * Executes the restarted command then deletes the tmp ini
     *
     * @param string $command
     */
    protected function restart($command)
    {
        passthru($command, $exitCode);
        $this->notify(Status::INFO, sprintf('Restarted process exited: %d', $exitCode));

        if (!empty($this->tmpIni)) {
            @unlink($this->tmpIni);
        }

        exit($exitCode);
    }

    /**
     * Returns true if everything was written for the restart
     *
     * If any of the following fails (however unlikely) we must return false to
     * stop potential recursion:
     *   - tmp ini file creation
     *   - environment variable creation
     *
     * @return bool
     */
    private function prepareRestart()
    {
        $error = '';
        $iniFiles = self::getAllIniFiles();
        $scannedInis = count($iniFiles) > 1;
        $scanDir = getenv('PHP_INI_SCAN_DIR');

        if (!$this->cli) {
            $error = 'Unsupported SAPI: '.PHP_SAPI;
        } elseif (!defined('PHP_BINARY')) {
            $error = 'PHP version is too old: '.PHP_VERSION;
        } elseif (!$this->writeTmpIni($iniFiles)) {
            $error = 'Unable to create temporary ini file';
        } elseif (!$this->setEnvironment($scannedInis, $scanDir, $iniFiles)) {
            $error = 'Unable to set environment variables';
        }

        if ($error) {
            $this->notify(Status::ERROR, $error);
        }

        return empty($error);
    }

    /**
     * Returns true if the tmp ini file was written
     *
     * The filename is passed as the -c option when the process restarts.
     *
     * @param array $iniFiles All ini files used in the current process
     *
     * @return bool
     */
    private function writeTmpIni(array $iniFiles)
    {
        if (!$this->tmpIni = tempnam(sys_get_temp_dir(), '')) {
            return false;
        }

        // $iniFiles has at least one item and it may be empty
        if (empty($iniFiles[0])) {
            array_shift($iniFiles);
        }

        $content = '';
        $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi';

        foreach ($iniFiles as $file) {
            $data = preg_replace($regex, ';$1', file_get_contents($file));
            $content .= $data.PHP_EOL;
        }

        $loaded = ini_get_all(null, false);
        $config = parse_ini_string($content);
        $content .= $this->mergeLoadedConfig($loaded, $config);

        // Work-around for https://bugs.php.net/bug.php?id=75932
        $content .= 'opcache.enable_cli=0'.PHP_EOL;

        return @file_put_contents($this->tmpIni, $content);
    }

    /**
     * Returns the restart command line
     *
     * @param array $args The argv array
     *
     * @return string
     */
    private function getCommand(array $args)
    {
        if (defined('STDOUT') && Process::supportsColor(STDOUT)) {
            $args = Process::addColorOption($args, $this->colorOption);
        }

        if (!file_exists($args[0])) {
            // If we are auto-prepended stdin can be used.
            $args[0] = '--';
            $this->notify(Status::INFO, 'Directly executed code is being used');
        }

        $args = array_merge(array(PHP_BINARY, '-c', $this->tmpIni), $args);

        $cmd = Process::escape(array_shift($args), true, true);
        foreach ($args as $arg) {
            $cmd .= ' '.Process::escape($arg);
        }

        return $cmd;
    }

    /**
     * Returns true if the restart environment variables were set
     *
     * @param bool $scannedInis Whether there were scanned ini files
     * @param false|string $scanDir PHP_INI_SCAN_DIR environment variable
     * @param array $iniFiles All ini files used in the current process
     *
     * @return bool
     */
    private function setEnvironment($scannedInis, $scanDir, array $iniFiles)
    {
        // Set scan dir env to an empty value if there were scanned ini files
        if ($scannedInis && !putenv('PHP_INI_SCAN_DIR=')) {
            return false;
        }

        // Make original inis available to restarted process
        if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) {
            return false;
        }

        // Flag restarted process and save values for it to use
        $envArgs = array(
            self::RESTART_ID,
            $this->loaded,
            intval($scannedInis),
        );

        if ($scannedInis && false !== $scanDir) {
            // Only add original scan dir if it was set
            $envArgs[] = $scanDir;
        }

        return putenv($this->envAllowXdebug.'='.implode('|', $envArgs));
    }

    /**
     * Logs status messages
     *
     * @param string $op Status handler constant
     * @param null|string $data Optional data
     */
    private function notify($op, $data = null)
    {
        if ($this->statusWriter) {
            $this->statusWriter->report($op, $data);
        }
    }

    /**
     * Returns default or changed settings for the tmp ini
     *
     * Ini settings can be passed on the command line using the -d option. To
     * preserve these, all loaded settings that are either not present or
     * different from those in the ini files are added at the end of the tmp ini.
     *
     * @param array $loadedConfig All current ini settings
     * @param array $iniConfig Settings from user ini files
     *
     * @return string
     */
    private function mergeLoadedConfig(array $loadedConfig, array $iniConfig)
    {
        $content = '';

        foreach ($loadedConfig as $name => $value) {
            // Values will either be null, string or array (HHVM only)
            if (!is_string($value) || strpos($name, 'xdebug') === 0) {
                continue;
            }

            if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) {
                // Based on main -d option handling in php-src/sapi/cli/php_cli.c
                if ($value && !ctype_alnum($value)) {
                    $value = '"'.str_replace('"', '\\"', $value).'"';
                }
                $content .= $name.'='.$value.PHP_EOL;
            }
        }

        return $content;
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\XdebugHandler;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class Status
{
    const ENV_RESTART = 'XDEBUG_HANDLER_RESTART';
    const CHECK = 'Check';
    const ERROR = 'Error';
    const INFO = 'Info';
    const NORESTART = 'NoRestart';
    const RESTART = 'Restart';
    const RESTARTING = 'Restarting';
    const RESTARTED = 'Restarted';

    private $envAllowXdebug;
    private $loaded;
    private $logger;
    private $time;

    /**
     * Constructor
     *
     * @param LoggerInterface $logger
     * @param string $loaded The loaded xdebug version
     * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name
     */
    public function __construct(LoggerInterface $logger, $loaded, $envAllowXdebug)
    {
        $start = getenv(self::ENV_RESTART);
        putenv(self::ENV_RESTART);
        $this->time = $start ? round((microtime(true) - $start) * 1000) : 0;

        $this->logger = $logger;
        $this->loaded = $loaded;
        $this->envAllowXdebug = $envAllowXdebug;
    }

    /**
     * Calls a handler method to report a message
     *
     * @param string $op The handler constant
     * @param null|string $data Data required by the handler
     */
    public function report($op, $data)
    {
        $func = array($this, 'report'.$op);
        call_user_func($func, $data);
    }

    /**
     * Sends a status message to the logger
     *
     * @param string $text
     * @param string $level
     */
    private function output($text, $level = null)
    {
        $this->logger->log($level ?: LogLevel::DEBUG, $text);
    }

    private function reportCheck()
    {
        $this->output('Checking '.$this->envAllowXdebug);
    }

    private function reportError($error)
    {
        $this->output(sprintf("No restart (%s)", $error), LogLevel::WARNING);
    }

    private function reportInfo($info)
    {
        $this->output($info);
    }

    private function reportNoRestart()
    {
        $this->output($this->getLoadedMessage());

        if ($this->loaded) {
            $text = sprintf("No restart (%s)", $this->getEnvAllow());
            $this->output($text);
        }
    }

    private function reportRestart()
    {
        $this->output($this->getLoadedMessage());
        putenv(self::ENV_RESTART.'='.strval(microtime(true)));
    }

    private function reportRestarted()
    {
        $loaded = $this->getLoadedMessage();
        $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded);
        $level = $this->loaded ? LogLevel::WARNING : null;
        $this->output($text, $level);
    }

    private function reportRestarting()
    {
        $text = sprintf("Process restarting (%s)", $this->getEnvAllow());
        $this->output($text);
    }

    /**
     * Returns the _ALLOW_XDEBUG environment variable as name=value
     *
     * @return string
     */
    private function getEnvAllow()
    {
        return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug);
    }

    /**
     * Returns the xdebug status and version
     *
     * @return string
     */
    private function getLoadedMessage()
    {
        $loaded = $this->loaded ? sprintf('loaded (%s)', $this->loaded) : 'not loaded';
        return 'The xdebug extension is '.$loaded;
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\XdebugHandler;

/**
 * Provides utility functions to prepare a child process command-line.
 *
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class Process
{
    /**
     * Returns the process arguments, appending a color option if required
     *
     * A color option is needed because child process output is piped.
     *
     * @param array $args The argv array
     * @param $colorOption The long option to force color output
     *
     * @return array
     */
    public static function addColorOption(array $args, $colorOption)
    {
        if (!$colorOption
            || in_array($colorOption, $args)
            || !preg_match('/^--([a-z]+$)|(^--[a-z]+=)/', $colorOption, $matches)) {
            return $args;
        }

        if (isset($matches[2])) {
            // Handle --color(s)= options. Note args[0] is the script name
            if ($index = array_search($matches[2].'auto', $args)) {
                $args[$index] = $colorOption;
                return $args;
            } elseif (preg_grep('/^'.$matches[2].'/', $args)) {
                return $args;
            }
        } elseif (in_array('--no-'.$matches[1], $args)) {
            return $args;
        }

        $args[] = $colorOption;
        return $args;
    }


    /**
     * Escapes a string to be used as a shell argument.
     *
     * From https://github.com/johnstevenson/winbox-args
     * MIT Licensed (c) John Stevenson <john-stevenson@blueyonder.co.uk>
     *
     * @param string $arg  The argument to be escaped
     * @param bool   $meta Additionally escape cmd.exe meta characters
     * @param bool $module The argument is the module to invoke
     *
     * @return string The escaped argument
     */
    public static function escape($arg, $meta = true, $module = false)
    {
        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
            return escapeshellarg($arg);
        }

        $quote = strpbrk($arg, " \t") !== false || $arg === '';
        $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes);

        if ($meta) {
            $meta = $dquotes || preg_match('/%[^%]+%/', $arg);

            if (!$meta) {
                $quote = $quote || strpbrk($arg, '^&|<>()') !== false;
            } elseif ($module && !$dquotes && $quote) {
                $meta = false;
            }
        }

        if ($quote) {
            $arg = preg_replace('/(\\\\*)$/', '$1$1', $arg);
            $arg = '"'.$arg.'"';
        }

        if ($meta) {
            $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg);
        }

        return $arg;
    }

    /**
     * Returns true if the output stream supports colors
     *
     * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
     * terminals via named pipes, so we can only check the environment.
     *
     * @param mixed $output A valid CLI output stream
     *
     * @return bool
     */
    public static function supportsColor($output)
    {
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            return (function_exists('sapi_windows_vt100_support')
                && sapi_windows_vt100_support($output))
                || false !== getenv('ANSICON')
                || 'ON' === getenv('ConEmuANSI')
                || 'xterm' === getenv('TERM');
        }

        if (function_exists('stream_isatty')) {
            return stream_isatty($output);
        } elseif (function_exists('posix_isatty')) {
            return posix_isatty($output);
        }

        $stat = fstat($output);
        // Check if formatted mode is S_IFCHR
        return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
    }
}
<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
    '2b9d0f43f9552984cfa82fee95491826' => $vendorDir . '/sabre/event/lib/coroutine.php',
    'd81bab31d3feb45bfe2f283ea3c8fdf7' => $vendorDir . '/sabre/event/lib/Loop/functions.php',
    'a1cce3d26cc15c00fcd0b3354bd72c88' => $vendorDir . '/sabre/event/lib/Promise/functions.php',
);
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/type-resolver/src', $vendorDir . '/phpdocumentor/reflection-docblock/src'),
    'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
    'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
    'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'),
    'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
    'Sabre\\Event\\' => array($vendorDir . '/sabre/event/lib'),
    'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
    'Phan\\' => array($baseDir . '/src/Phan'),
    'Microsoft\\PhpParser\\' => array($vendorDir . '/microsoft/tolerant-php-parser/src'),
    'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'),
    'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'),
    'AdvancedJsonRpc\\' => array($vendorDir . '/felixfbecker/advanced-json-rpc/lib'),
);
<?php declare (strict_types=1);

namespace Sabre\Event;

/**
 * This class contains the version number for this package.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Version {

    /**
     * Full version number
     */
    const VERSION = '5.0.3';

}
<?php declare (strict_types=1);

namespace Sabre\Event;

/**
 * This exception is thrown when the user tried to reject or fulfill a promise,
 * after either of these actions were already performed.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PromiseAlreadyResolvedException extends \LogicException {

}
<?php declare (strict_types=1);

namespace Sabre\Event;

/**
 * Emitter object.
 *
 * Instantiate this class, or subclass it for easily creating event emitters.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Emitter implements EmitterInterface {

    use EmitterTrait;

}
<?php declare (strict_types=1);

namespace Sabre\Event;

use Generator;
use Throwable;

/**
 * Turn asynchronous promise-based code into something that looks synchronous
 * again, through the use of generators.
 *
 * Example without coroutines:
 *
 * $promise = $httpClient->request('GET', '/foo');
 * $promise->then(function($value) {
 *
 *   return $httpClient->request('DELETE','/foo');
 *
 * })->then(function($value) {
 *
 *   return $httpClient->request('PUT', '/foo');
 *
 * })->error(function($reason) {
 *
 *   echo "Failed because: $reason\n";
 *
 * });
 *
 * Example with coroutines:
 *
 * coroutine(function() {
 *
 *   try {
 *     yield $httpClient->request('GET', '/foo');
 *     yield $httpClient->request('DELETE', /foo');
 *     yield $httpClient->request('PUT', '/foo');
 *   } catch(\Throwable $reason) {
 *     echo "Failed because: $reason\n";
 *   }
 *
 * });
 *
 * @return \Sabre\Event\Promise
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
function coroutine(callable $gen) : Promise {

    $generator = $gen();
    if (!$generator instanceof Generator) {
        throw new \InvalidArgumentException('You must pass a generator function');
    }

    // This is the value we're returning.
    $promise = new Promise();

    /**
     * So tempted to use the mythical y-combinator here, but it's not needed in
     * PHP.
     */
    $advanceGenerator = function() use (&$advanceGenerator, $generator, $promise, &$lastYieldResult) {

        while ($generator->valid()) {

            $yieldedValue = $generator->current();
            if ($yieldedValue instanceof Promise) {
                $yieldedValue->then(
                    function($value) use ($generator, &$advanceGenerator, &$lastYieldResult) {
                        $generator->send($value);
                        $advanceGenerator();
                    },
                    function(Throwable $reason) use ($generator, $advanceGenerator) {
                        $generator->throw($reason);
                        $advanceGenerator();
                    }
                )->otherwise(function(Throwable $reason) use ($promise) {
                    // This error handler would be called, if something in the
                    // generator throws an exception, and it's not caught
                    // locally.
                    $promise->reject($reason);
                });
                // We need to break out of the loop, because $advanceGenerator
                // will be called asynchronously when the promise has a result.
                break;
            } else {
                // If the value was not a promise, we'll just let it pass through.
                $generator->send($yieldedValue);
            }

        }

        // If the generator is at the end, and we didn't run into an exception,
        // We're grabbing the "return" value and fulfilling our top-level
        // promise with its value.
         if (!$generator->valid() && $promise->state === Promise::PENDING) {
             $returnValue = $generator->getReturn();

             // The return value is a promise.
             if ($returnValue instanceof Promise) {
                 $returnValue->then(function($value) use ($promise) {
                     $promise->fulfill($value);
                 }, function(Throwable $reason) {
                     $promise->reject($reason);
                 });
             } else {

                 $promise->fulfill($returnValue);

             }


        }

    };

    try {
        $advanceGenerator();
    } catch (Throwable $e) {
        $promise->reject($e);
    }

    return $promise;

}
<?php declare (strict_types=1);

namespace Sabre\Event;

/**
 * This is the old name for the Emitter class.
 *
 * Instead of of using EventEmitter, please use Emitter. They are identical
 * otherwise.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class EventEmitter implements EmitterInterface {

    use EmitterTrait;

}
<?php declare (strict_types=1);

namespace Sabre\Event;

/**
 * Wildcard Emitter Trait
 *
 * This trait provides the implementation for WildCardEmitter
 * Refer to that class for the full documentation about this
 * trait.
 *
 * Normally you can just instantiate that class, but if you want to add
 * emitter functionality to existing classes, using the trait might be a
 * better way to do this.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
trait WildcardEmitterTrait {

    /**
     * Subscribe to an event.
     *
     * @return void
     */
    function on(string $eventName, callable $callBack, int $priority = 100) {

        // If it ends with a wildcard, we use the wildcardListeners array 
        if ($eventName[\strlen($eventName) - 1] === '*') {
            $eventName = \substr($eventName, 0, -1);
            $listeners = & $this->wildcardListeners;
        } else {
            $listeners = & $this->listeners;
        }

        // Always fully reset the listener index. This is fairly sane for most
        // applications, because there's a clear "event registering" and "event
        // emitting" phase, but can be slow if there's a lot adding and removing
        // of listeners during emitting of events.
        $this->listenerIndex = [];

        if (!isset($listeners[$eventName])) {
            $listeners[$eventName] = [];
        }
        $listeners[$eventName][] = [$priority, $callBack];

    }

    /**
     * Subscribe to an event exactly once.
     *
     * @return void
     */
    function once(string $eventName, callable $callBack, int $priority = 100) {

        $wrapper = null;
        $wrapper = function() use ($eventName, $callBack, &$wrapper) {

            $this->removeListener($eventName, $wrapper);
            return \call_user_func_array($callBack, \func_get_args());

        };

        $this->on($eventName, $wrapper, $priority);

    }

    /**
     * Emits an event.
     *
     * This method will return true if 0 or more listeners were succesfully
     * handled. false is returned if one of the events broke the event chain.
     *
     * If the continueCallBack is specified, this callback will be called every
     * time before the next event handler is called.
     *
     * If the continueCallback returns false, event propagation stops. This
     * allows you to use the eventEmitter as a means for listeners to implement
     * functionality in your application, and break the event loop as soon as
     * some condition is fulfilled.
     *
     * Note that returning false from an event subscriber breaks propagation
     * and returns false, but if the continue-callback stops propagation, this
     * is still considered a 'successful' operation and returns true.
     *
     * Lastly, if there are 5 event handlers for an event. The continueCallback
     * will be called at most 4 times.
     */
    function emit(string $eventName, array $arguments = [], callable $continueCallBack = null) : bool {

        if (\is_null($continueCallBack)) {

            foreach ($this->listeners($eventName) as $listener) {

                $result = \call_user_func_array($listener, $arguments);
                if ($result === false) {
                    return false;
                }
            }

        } else {

            $listeners = $this->listeners($eventName);
            $counter = \count($listeners);

            foreach ($listeners as $listener) {

                $counter--;
                $result = \call_user_func_array($listener, $arguments);
                if ($result === false) {
                    return false;
                }

                if ($counter > 0) {
                    if (!$continueCallBack()) break;
                }

            }

        }

        return true;


    }

    /**
     * Returns the list of listeners for an event.
     *
     * The list is returned as an array, and the list of events are sorted by
     * their priority.
     *
     * @return callable[]
     */
    function listeners(string $eventName) : array {

        if (!\array_key_exists($eventName, $this->listenerIndex)) {

            // Create a new index.
            $listeners = [];
            $listenersPriority = [];
            if (isset($this->listeners[$eventName])) foreach ($this->listeners[$eventName] as $listener) {

                $listenersPriority[] = $listener[0];
                $listeners[] = $listener[1];

            }

            foreach ($this->wildcardListeners as $wcEvent => $wcListeners) {

                // Wildcard match
                if (\substr($eventName, 0, \strlen($wcEvent)) === $wcEvent) {

                    foreach ($wcListeners as $listener) {

                        $listenersPriority[] = $listener[0];
                        $listeners[] = $listener[1];

                    }

                }

            }

            // Sorting by priority
            \array_multisort($listenersPriority, SORT_NUMERIC, $listeners);

            // Creating index
            $this->listenerIndex[$eventName] = $listeners;

        }

        return $this->listenerIndex[$eventName];

    }

    /**
     * Removes a specific listener from an event.
     *
     * If the listener could not be found, this method will return false. If it
     * was removed it will return true.
     */
    function removeListener(string $eventName, callable $listener) : bool {

        // If it ends with a wildcard, we use the wildcardListeners array 
        if ($eventName[\strlen($eventName) - 1] === '*') {
            $eventName = \substr($eventName, 0, -1);
            $listeners = & $this->wildcardListeners;
        } else {
            $listeners = & $this->listeners;
        }

        if (!isset($listeners[$eventName])) {
            return false;
        }

        foreach ($listeners[$eventName] as $index => $check) {

            if ($check[1] === $listener) {

                // Remove listener
                unset($listeners[$eventName][$index]);
                // Reset index
                $this->listenerIndex = [];
                return true;

            }

        }

        return false;

    }

    /**
     * Removes all listeners.
     *
     * If the eventName argument is specified, all listeners for that event are
     * removed. If it is not specified, every listener for every event is
     * removed.
     *
     * @return void
     */
    function removeAllListeners(string $eventName = null) {

        if (\is_null($eventName)) {
            $this->listeners = [];
            $this->wildcardListeners = [];

        } else {

            if ($eventName[\strlen($eventName) - 1] === '*') {
                // Wildcard event
                unset($this->wildcardListeners[\substr($eventName, 0, -1)]);
            } else {
                unset($this->listeners[$eventName]);
            }

        }

        // Reset index
        $this->listenerIndex = [];

    }

    /**
     * The list of listeners
     */
    protected $listeners = [];

    /**
     * The list of "wildcard listeners". 
     */
    protected $wildcardListeners = [];

    /**
     * An index of listeners for a specific event name. This helps speeding
     * up emitting events after all listeners have been set.
     *
     * If the list of listeners changes though, the index clears.
     */
    protected $listenerIndex = [];
}
<?php declare (strict_types=1);

namespace Sabre\Event;

/**
 * Event Emitter Trait
 *
 * This trait contains all the basic functions to implement an
 * EventEmitterInterface.
 *
 * Using the trait + interface allows you to add EventEmitter capabilities
 * without having to change your base-class.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
trait EmitterTrait {


    /**
     * Subscribe to an event.
     *
     * @return void
     */
    function on(string $eventName, callable $callBack, int $priority = 100) {

        if (!isset($this->listeners[$eventName])) {
            $this->listeners[$eventName] = [
                true,  // If there's only one item, it's sorted
                [$priority],
                [$callBack]
            ];
        } else {
            $this->listeners[$eventName][0] = false; // marked as unsorted
            $this->listeners[$eventName][1][] = $priority;
            $this->listeners[$eventName][2][] = $callBack;
        }

    }

    /**
     * Subscribe to an event exactly once.
     *
     * @return void
     */
    function once(string $eventName, callable $callBack, int $priority = 100) {

        $wrapper = null;
        $wrapper = function() use ($eventName, $callBack, &$wrapper) {

            $this->removeListener($eventName, $wrapper);
            return \call_user_func_array($callBack, \func_get_args());

        };

        $this->on($eventName, $wrapper, $priority);

    }

    /**
     * Emits an event.
     *
     * This method will return true if 0 or more listeners were succesfully
     * handled. false is returned if one of the events broke the event chain.
     *
     * If the continueCallBack is specified, this callback will be called every
     * time before the next event handler is called.
     *
     * If the continueCallback returns false, event propagation stops. This
     * allows you to use the eventEmitter as a means for listeners to implement
     * functionality in your application, and break the event loop as soon as
     * some condition is fulfilled.
     *
     * Note that returning false from an event subscriber breaks propagation
     * and returns false, but if the continue-callback stops propagation, this
     * is still considered a 'successful' operation and returns true.
     *
     * Lastly, if there are 5 event handlers for an event. The continueCallback
     * will be called at most 4 times.
     */
    function emit(string $eventName, array $arguments = [], callable $continueCallBack = null) : bool {

        if (\is_null($continueCallBack)) {

            foreach ($this->listeners($eventName) as $listener) {

                $result = \call_user_func_array($listener, $arguments);
                if ($result === false) {
                    return false;
                }
            }

        } else {

            $listeners = $this->listeners($eventName);
            $counter = \count($listeners);

            foreach ($listeners as $listener) {

                $counter--;
                $result = \call_user_func_array($listener, $arguments);
                if ($result === false) {
                    return false;
                }

                if ($counter > 0) {
                    if (!$continueCallBack()) break;
                }

            }

        }

        return true;

    }

    /**
     * Returns the list of listeners for an event.
     *
     * The list is returned as an array, and the list of events are sorted by
     * their priority.
     *
     * @return callable[]
     */
    function listeners(string $eventName) : array {

        if (!isset($this->listeners[$eventName])) {
            return [];
        }

        // The list is not sorted
        if (!$this->listeners[$eventName][0]) {

            // Sorting
            \array_multisort($this->listeners[$eventName][1], SORT_NUMERIC, $this->listeners[$eventName][2]);

            // Marking the listeners as sorted
            $this->listeners[$eventName][0] = true;
        }

        return $this->listeners[$eventName][2];

    }

    /**
     * Removes a specific listener from an event.
     *
     * If the listener could not be found, this method will return false. If it
     * was removed it will return true.
     */
    function removeListener(string $eventName, callable $listener) : bool {

        if (!isset($this->listeners[$eventName])) {
            return false;
        }
        foreach ($this->listeners[$eventName][2] as $index => $check) {
            if ($check === $listener) {
                unset($this->listeners[$eventName][1][$index]);
                unset($this->listeners[$eventName][2][$index]);
                return true;
            }
        }
        return false;

    }

    /**
     * Removes all listeners.
     *
     * If the eventName argument is specified, all listeners for that event are
     * removed. If it is not specified, every listener for every event is
     * removed.
     *
     * @return void
     */
    function removeAllListeners(string $eventName = null) {

        if (!\is_null($eventName)) {
            unset($this->listeners[$eventName]);
        } else {
            $this->listeners = [];
        }

    }

    /**
     * The list of listeners
     *
     * @var array
     */
    protected $listeners = [];

}
<?php declare (strict_types=1);

namespace Sabre\Event\Promise;

use Sabre\Event\Promise;
use Throwable;

/**
 * This file contains a set of functions that are useful for dealing with the
 * Promise object.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */


/**
 * This function takes an array of Promises, and returns a Promise that
 * resolves when all of the given arguments have resolved.
 *
 * The returned Promise will resolve with a value that's an array of all the
 * values the given promises have been resolved with.
 *
 * This array will be in the exact same order as the array of input promises.
 *
 * If any of the given Promises fails, the returned promise will immidiately
 * fail with the first Promise that fails, and its reason.
 *
 * @param Promise[] $promises
 */
function all(array $promises) : Promise {

    return new Promise(function($success, $fail) use ($promises) {

        if (empty($promises)) {
            $success([]);
            return;
        }

        $successCount = 0;
        $completeResult = [];

        foreach ($promises as $promiseIndex => $subPromise) {

            $subPromise->then(
                function($result) use ($promiseIndex, &$completeResult, &$successCount, $success, $promises) {
                    $completeResult[$promiseIndex] = $result;
                    $successCount++;
                    if ($successCount === count($promises)) {
                        $success($completeResult);
                    }
                    return $result;
                }
            )->otherwise(
                function($reason) use ($fail) {
                    $fail($reason);
                }
            );

        }
    });

}

/**
 * The race function returns a promise that resolves or rejects as soon as
 * one of the promises in the argument resolves or rejects.
 *
 * The returned promise will resolve or reject with the value or reason of
 * that first promise.
 *
 * @param Promise[] $promises
 */
function race(array $promises) : Promise {

    return new Promise(function($success, $fail) use ($promises) {

        $alreadyDone = false;
        foreach ($promises as $promise) {

            $promise->then(
                function($result) use ($success, &$alreadyDone) {
                    if ($alreadyDone) {
                        return;
                    }
                    $alreadyDone = true;
                    $success($result);
                },
                function($reason) use ($fail, &$alreadyDone) {
                    if ($alreadyDone) {
                        return;
                    }
                    $alreadyDone = true;
                    $fail($reason);
                }
            );

        }

    });

}


/**
 * Returns a Promise that resolves with the given value.
 *
 * If the value is a promise, the returned promise will attach itself to that
 * promise and eventually get the same state as the followed promise.
 *
 * @param mixed $value
 */
function resolve($value) : Promise {

    if ($value instanceof Promise) {
        return $value->then();
    } else {
        $promise = new Promise();
        $promise->fulfill($value);
        return $promise;
    }

}

/**
 * Returns a Promise that will reject with the given reason.
 */
function reject(Throwable $reason) : Promise {

    $promise = new Promise();
    $promise->reject($reason);
    return $promise;

}
<?php declare (strict_types=1);

namespace Sabre\Event;

/**
 * This class is an EventEmitter with support for wildcard event handlers.
 *
 * What this means is that you can emit events like this:
 *
 *   emit('change:firstName')
 *
 * and listen to this event like this:
 *
 *   on('change:*')
 *
 * A few notes:
 * 
 * - Wildcards only work at the end of an event name.
 * - Currently you can only use 1 wildcard.
 * - Using ":" as a separator is optional, but it's highly recommended to use
 *   some kind of separator.
 *
 * The WilcardEmitter is a bit slower than the regular Emitter. If you code
 * must be very high performance, it might be better to try to use the other
 * emitter. For must usage the difference is negligible though.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class WildcardEmitter implements EmitterInterface {

    use WildcardEmitterTrait;



}
<?php declare (strict_types=1);

namespace Sabre\Event;

/**
 * Event Emitter Interface
 *
 * Anything that accepts listeners and emits events should implement this
 * interface.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface EmitterInterface {

    /**
     * Subscribe to an event.
     *
     * @return void
     */
    function on(string $eventName, callable $callBack, int $priority = 100);

    /**
     * Subscribe to an event exactly once.
     *
     * @return void
     */
    function once(string $eventName, callable $callBack, int $priority = 100);

    /**
     * Emits an event.
     *
     * This method will return true if 0 or more listeners were succesfully
     * handled. false is returned if one of the events broke the event chain.
     *
     * If the continueCallBack is specified, this callback will be called every
     * time before the next event handler is called.
     *
     * If the continueCallback returns false, event propagation stops. This
     * allows you to use the eventEmitter as a means for listeners to implement
     * functionality in your application, and break the event loop as soon as
     * some condition is fulfilled.
     *
     * Note that returning false from an event subscriber breaks propagation
     * and returns false, but if the continue-callback stops propagation, this
     * is still considered a 'successful' operation and returns true.
     *
     * Lastly, if there are 5 event handlers for an event. The continueCallback
     * will be called at most 4 times.
     */
    function emit(string $eventName, array $arguments = [], callable $continueCallBack = null) : bool;

    /**
     * Returns the list of listeners for an event.
     *
     * The list is returned as an array, and the list of events are sorted by
     * their priority.
     *
     * @return callable[]
     */
    function listeners(string $eventName) : array;

    /**
     * Removes a specific listener from an event.
     *
     * If the listener could not be found, this method will return false. If it
     * was removed it will return true.
     */
    function removeListener(string $eventName, callable $listener) : bool;

    /**
     * Removes all listeners.
     *
     * If the eventName argument is specified, all listeners for that event are
     * removed. If it is not specified, every listener for every event is
     * removed.
     *
     * @return void
     */
    function removeAllListeners(string $eventName = null);

}
<?php declare (strict_types=1);

namespace Sabre\Event\Loop;

/**
 * A simple eventloop implementation.
 *
 * This eventloop supports:
 *   * nextTick
 *   * setTimeout for delayed functions
 *   * setInterval for repeating functions
 *   * stream events using stream_select
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Loop {

    /**
     * Executes a function after x seconds.
     *
     * @return void
     */
    function setTimeout(callable $cb, float $timeout) {

        $triggerTime = microtime(true) + ($timeout);

        if (!$this->timers) {
            // Special case when the timers array was empty.
            $this->timers[] = [$triggerTime, $cb];
            return;
        }

        // We need to insert these values in the timers array, but the timers
        // array must be in reverse-order of trigger times.
        //
        // So here we search the array for the insertion point.
        $index = count($this->timers) - 1;
        while (true) {
            if ($triggerTime < $this->timers[$index][0]) {
                array_splice(
                    $this->timers,
                    $index + 1,
                    0,
                    [[$triggerTime, $cb]]
                );
                break;
            } elseif ($index === 0) {
                array_unshift($this->timers, [$triggerTime, $cb]);
                break;
            }
            $index--;

        }

    }

    /**
     * Executes a function every x seconds.
     *
     * The value this function returns can be used to stop the interval with
     * clearInterval.
     */
    function setInterval(callable $cb, float $timeout) : array {

        $keepGoing = true;
        $f = null;

        $f = function() use ($cb, &$f, $timeout, &$keepGoing) {
            if ($keepGoing) {
                $cb();
                $this->setTimeout($f, $timeout);
            }
        };
        $this->setTimeout($f, $timeout);

        // Really the only thing that matters is returning the $keepGoing
        // boolean value.
        //
        // We need to pack it in an array to allow returning by reference.
        // Because I'm worried people will be confused by using a boolean as a
        // sort of identifier, I added an extra string.
        return ['I\'m an implementation detail', &$keepGoing];

    }

    /**
     * Stops a running interval.
     *
     * @return void
     */
    function clearInterval(array $intervalId) {

        $intervalId[1] = false;

    }

    /**
     * Runs a function immediately at the next iteration of the loop.
     *
     * @return void
     */
    function nextTick(callable $cb) {

        $this->nextTick[] = $cb;

    }


    /**
     * Adds a read stream.
     *
     * The callback will be called as soon as there is something to read from
     * the stream.
     *
     * You MUST call removeReadStream after you are done with the stream, to
     * prevent the eventloop from never stopping.
     *
     * @param resource $stream
     * @return void
     */
    function addReadStream($stream, callable $cb) {

        $this->readStreams[(int)$stream] = $stream;
        $this->readCallbacks[(int)$stream] = $cb;

    }

    /**
     * Adds a write stream.
     *
     * The callback will be called as soon as the system reports it's ready to
     * receive writes on the stream.
     *
     * You MUST call removeWriteStream after you are done with the stream, to
     * prevent the eventloop from never stopping.
     *
     * @param resource $stream
     * @return void
     */
    function addWriteStream($stream, callable $cb) {

        $this->writeStreams[(int)$stream] = $stream;
        $this->writeCallbacks[(int)$stream] = $cb;

    }

    /**
     * Stop watching a stream for reads.
     *
     * @param resource $stream
     * @return void
     */
    function removeReadStream($stream) {

        unset(
            $this->readStreams[(int)$stream],
            $this->readCallbacks[(int)$stream]
        );

    }

    /**
     * Stop watching a stream for writes.
     *
     * @param resource $stream
     * @return void
     */
    function removeWriteStream($stream) {

        unset(
            $this->writeStreams[(int)$stream],
            $this->writeCallbacks[(int)$stream]
        );

    }


    /**
     * Runs the loop.
     *
     * This function will run continiously, until there's no more events to
     * handle.
     *
     * @return void
     */
    function run() {

        $this->running = true;

        do {

            $hasEvents = $this->tick(true);

        } while ($this->running && $hasEvents);
        $this->running = false;

    }

    /**
     * Executes all pending events.
     *
     * If $block is turned true, this function will block until any event is
     * triggered.
     *
     * If there are now timeouts, nextTick callbacks or events in the loop at
     * all, this function will exit immediately.
     *
     * This function will return true if there are _any_ events left in the
     * loop after the tick.
     */
    function tick(bool $block = false) : bool {

        $this->runNextTicks();
        $nextTimeout = $this->runTimers();

        // Calculating how long runStreams should at most wait.
        if (!$block) {
            // Don't wait
            $streamWait = 0;
        } elseif ($this->nextTick) {
            // There's a pending 'nextTick'. Don't wait.
            $streamWait = 0;
        } elseif (is_numeric($nextTimeout)) {
            // Wait until the next Timeout should trigger.
            $streamWait = $nextTimeout;
        } else {
            // Wait indefinitely
            $streamWait = null;
        }

        $this->runStreams($streamWait);

        return ($this->readStreams || $this->writeStreams || $this->nextTick || $this->timers);

    }

    /**
     * Stops a running eventloop
     *
     * @return void
     */
    function stop() {

        $this->running = false;

    }

    /**
     * Executes all 'nextTick' callbacks.
     *
     * return void
     */
    protected function runNextTicks() {

        $nextTick = $this->nextTick;
        $this->nextTick = [];

        foreach ($nextTick as $cb) {
            $cb();
        }

    }

    /**
     * Runs all pending timers.
     *
     * After running the timer callbacks, this function returns the number of
     * seconds until the next timer should be executed.
     *
     * If there's no more pending timers, this function returns null.
     *
     * @return float|null
     */
    protected function runTimers() {

        $now = microtime(true);
        while (($timer = array_pop($this->timers)) && $timer[0] < $now) {
            $timer[1]();
        }
        // Add the last timer back to the array.
        if ($timer) {
            $this->timers[] = $timer;
            return max(0, $timer[0] - microtime(true));
        }

    }

    /**
     * Runs all pending stream events.
     *
     * If $timeout is 0, it will return immediately. If $timeout is null, it
     * will wait indefinitely.
     *
     * @param float|null timeout
     */
    protected function runStreams($timeout) {

        if ($this->readStreams || $this->writeStreams) {

            $read = $this->readStreams;
            $write = $this->writeStreams;
            $except = null;
            if (stream_select($read, $write, $except, ($timeout === null) ? null : 0, $timeout ? (int)($timeout * 1000000) : 0)) {

                // See PHP Bug https://bugs.php.net/bug.php?id=62452
                // Fixed in PHP7
                foreach ($read as $readStream) {
                    $readCb = $this->readCallbacks[(int)$readStream];
                    $readCb();
                }
                foreach ($write as $writeStream) {
                    $writeCb = $this->writeCallbacks[(int)$writeStream];
                    $writeCb();
                }

            }

        } elseif ($this->running && ($this->nextTick || $this->timers)) {
            usleep($timeout !== null ? intval($timeout * 1000000) : 200000);
        }

    }

    /**
     * Is the main loop active
     *
     * @var bool
     */
    protected $running = false;

    /**
     * A list of timers, added by setTimeout.
     *
     * @var array
     */
    protected $timers = [];

    /**
     * A list of 'nextTick' callbacks.
     *
     * @var callable[]
     */
    protected $nextTick = [];

    /**
     * List of readable streams for stream_select, indexed by stream id.
     *
     * @var resource[]
     */
    protected $readStreams = [];

    /**
     * List of writable streams for stream_select, indexed by stream id.
     *
     * @var resource[]
     */
    protected $writeStreams = [];

    /**
     * List of read callbacks, indexed by stream id.
     *
     * @var callback[]
     */
    protected $readCallbacks = [];

    /**
     * List of write callbacks, indexed by stream id.
     *
     * @var callback[]
     */
    protected $writeCallbacks = [];


}
<?php declare (strict_types=1);

namespace Sabre\Event\Loop;

/**
 * Executes a function after x seconds.
 *
 * @return void
 */
function setTimeout(callable $cb, float $timeout) {

    instance()->setTimeout($cb, $timeout);

}

/**
 * Executes a function every x seconds.
 *
 * The value this function returns can be used to stop the interval with
 * clearInterval.
 */
function setInterval(callable $cb, float $timeout) : array {

    return instance()->setInterval($cb, $timeout);

}

/**
 * Stops a running interval.
 *
 * @return void
 */
function clearInterval(array $intervalId) {

    instance()->clearInterval($intervalId);

}

/**
 * Runs a function immediately at the next iteration of the loop.
 *
 * @return void
 */
function nextTick(callable $cb) {

    instance()->nextTick($cb);

}


/**
 * Adds a read stream.
 *
 * The callback will be called as soon as there is something to read from
 * the stream.
 *
 * You MUST call removeReadStream after you are done with the stream, to
 * prevent the eventloop from never stopping.
 *
 * @param resource $stream
 * @return void
 */
function addReadStream($stream, callable $cb) {

    instance()->addReadStream($stream, $cb);

}

/**
 * Adds a write stream.
 *
 * The callback will be called as soon as the system reports it's ready to
 * receive writes on the stream.
 *
 * You MUST call removeWriteStream after you are done with the stream, to
 * prevent the eventloop from never stopping.
 *
 * @param resource $stream
 * @return void
 */
function addWriteStream($stream, callable $cb) {

    instance()->addWriteStream($stream, $cb);

}

/**
 * Stop watching a stream for reads.
 *
 * @param resource $stream
 * @return void
 */
function removeReadStream($stream) {

    instance()->removeReadStream($stream);

}

/**
 * Stop watching a stream for writes.
 *
 * @param resource $stream
 * @return void
 */
function removeWriteStream($stream) {

    instance()->removeWriteStream($stream);

}


/**
 * Runs the loop.
 *
 * This function will run continiously, until there's no more events to
 * handle.
 *
 * @return void
 */
function run() {

    instance()->run();

}

/**
 * Executes all pending events.
 *
 * If $block is turned true, this function will block until any event is
 * triggered.
 *
 * If there are now timeouts, nextTick callbacks or events in the loop at
 * all, this function will exit immediately.
 *
 * This function will return true if there are _any_ events left in the
 * loop after the tick.
 */
function tick(bool $block = false) : bool {

    return instance()->tick($block);

}

/**
 * Stops a running eventloop
 *
 * @return void
 */
function stop() {

    instance()->stop();

}

/**
 * Retrieves or sets the global Loop object.
 */
function instance(Loop $newLoop = null) : Loop {

    static $loop;
    if ($newLoop) {
        $loop = $newLoop;
    } elseif (!$loop) {
        $loop = new Loop();
    }
    return $loop;

}
<?php declare (strict_types=1);

namespace Sabre\Event;

use Exception;
use Throwable;

/**
 * An implementation of the Promise pattern.
 *
 * A promise represents the result of an asynchronous operation.
 * At any given point a promise can be in one of three states:
 *
 * 1. Pending (the promise does not have a result yet).
 * 2. Fulfilled (the asynchronous operation has completed with a result).
 * 3. Rejected (the asynchronous operation has completed with an error).
 *
 * To get a callback when the operation has finished, use the `then` method.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Promise {

    /**
     * The asynchronous operation is pending.
     */
    const PENDING = 0;

    /**
     * The asynchronous operation has completed, and has a result.
     */
    const FULFILLED = 1;

    /**
     * The asynchronous operation has completed with an error.
     */
    const REJECTED = 2;

    /**
     * The current state of this promise.
     *
     * @var int
     */
    public $state = self::PENDING;

    /**
     * Creates the promise.
     *
     * The passed argument is the executor. The executor is automatically
     * called with two arguments.
     *
     * Each are callbacks that map to $this->fulfill and $this->reject.
     * Using the executor is optional.
     */
    function __construct(callable $executor = null) {

        if ($executor) {
            $executor(
                [$this, 'fulfill'],
                [$this, 'reject']
            );
        }

    }

    /**
     * This method allows you to specify the callback that will be called after
     * the promise has been fulfilled or rejected.
     *
     * Both arguments are optional.
     *
     * This method returns a new promise, which can be used for chaining.
     * If either the onFulfilled or onRejected callback is called, you may
     * return a result from this callback.
     *
     * If the result of this callback is yet another promise, the result of
     * _that_ promise will be used to set the result of the returned promise.
     *
     * If either of the callbacks return any other value, the returned promise
     * is automatically fulfilled with that value.
     *
     * If either of the callbacks throw an exception, the returned promise will
     * be rejected and the exception will be passed back.
     */
    function then(callable $onFulfilled = null, callable $onRejected = null) : Promise {

        // This new subPromise will be returned from this function, and will
        // be fulfilled with the result of the onFulfilled or onRejected event
        // handlers.
        $subPromise = new self();

        switch ($this->state) {
            case self::PENDING :
                // The operation is pending, so we keep a reference to the
                // event handlers so we can call them later.
                $this->subscribers[] = [$subPromise, $onFulfilled, $onRejected];
                break;
            case self::FULFILLED :
                // The async operation is already fulfilled, so we trigger the
                // onFulfilled callback asap.
                $this->invokeCallback($subPromise, $onFulfilled);
                break;
            case self::REJECTED :
                // The async operation failed, so we call teh onRejected
                // callback asap.
                $this->invokeCallback($subPromise, $onRejected);
                break;
        }
        return $subPromise;

    }

    /**
     * Add a callback for when this promise is rejected.
     *
     * Its usage is identical to then(). However, the otherwise() function is
     * preferred.
     */
    function otherwise(callable $onRejected) : Promise {

        return $this->then(null, $onRejected);

    }

    /**
     * Marks this promise as fulfilled and sets its return value.
     *
     * @param mixed $value
     * @return void
     */
    function fulfill($value = null) {
        if ($this->state !== self::PENDING) {
            throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once');
        }
        $this->state = self::FULFILLED;
        $this->value = $value;
        foreach ($this->subscribers as $subscriber) {
            $this->invokeCallback($subscriber[0], $subscriber[1]);
        }
    }

    /**
     * Marks this promise as rejected, and set it's rejection reason.
     *
     * @return void
     */
    function reject(Throwable $reason) {
        if ($this->state !== self::PENDING) {
            throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once');
        }
        $this->state = self::REJECTED;
        $this->value = $reason;
        foreach ($this->subscribers as $subscriber) {
            $this->invokeCallback($subscriber[0], $subscriber[2]);
        }

    }

    /**
     * Stops execution until this promise is resolved.
     *
     * This method stops exection completely. If the promise is successful with
     * a value, this method will return this value. If the promise was
     * rejected, this method will throw an exception.
     *
     * This effectively turns the asynchronous operation into a synchronous
     * one. In PHP it might be useful to call this on the last promise in a
     * chain.
     *
     * @return mixed
     */
    function wait() {

        $hasEvents = true;
        while ($this->state === self::PENDING) {

            if (!$hasEvents) {
                throw new \LogicException('There were no more events in the loop. This promise will never be fulfilled.');
            }

            // As long as the promise is not fulfilled, we tell the event loop
            // to handle events, and to block.
            $hasEvents = Loop\tick(true);

        }

        if ($this->state === self::FULFILLED) {
            // If the state of this promise is fulfilled, we can return the value.
            return $this->value;
        } else {
            // If we got here, it means that the asynchronous operation
            // errored. Therefore we need to throw an exception.
            throw $this->value;
        }


    }


    /**
     * A list of subscribers. Subscribers are the callbacks that want us to let
     * them know if the callback was fulfilled or rejected.
     *
     * @var array
     */
    protected $subscribers = [];

    /**
     * The result of the promise.
     *
     * If the promise was fulfilled, this will be the result value. If the
     * promise was rejected, this property hold the rejection reason.
     *
     * @var mixed
     */
    protected $value = null;

    /**
     * This method is used to call either an onFulfilled or onRejected callback.
     *
     * This method makes sure that the result of these callbacks are handled
     * correctly, and any chained promises are also correctly fulfilled or
     * rejected.
     *
     * @param Promise $subPromise
     * @param callable $callBack
     * @return void
     */
    private function invokeCallback(Promise $subPromise, callable $callBack = null) {

        // We use 'nextTick' to ensure that the event handlers are always
        // triggered outside of the calling stack in which they were originally
        // passed to 'then'.
        //
        // This makes the order of execution more predictable.
        Loop\nextTick(function() use ($callBack, $subPromise) {
            if (is_callable($callBack)) {
                try {

                    $result = $callBack($this->value);
                    if ($result instanceof self) {
                        // If the callback (onRejected or onFulfilled)
                        // returned a promise, we only fulfill or reject the
                        // chained promise once that promise has also been
                        // resolved.
                        $result->then([$subPromise, 'fulfill'], [$subPromise, 'reject']);
                    } else {
                        // If the callback returned any other value, we
                        // immediately fulfill the chained promise.
                        $subPromise->fulfill($result);
                    }
                } catch (Throwable $e) {
                    // If the event handler threw an exception, we need to make sure that
                    // the chained promise is rejected as well.
                    $subPromise->reject($e);
                }
            } else {
                if ($this->state === self::FULFILLED) {
                    $subPromise->fulfill($this->value);
                } else {
                    $subPromise->reject($this->value);
                }
            }
        });
    }

}
<?php

use \Phan\Issue;

/**
 * This configuration will be read and overlayed on top of the
 * default configuration. Command line arguments will be applied
 * after this file is read.
 *
 * @see src/Phan/Config.php
 * See Config for all configurable options.
 *
 * A Note About Paths
 * ==================
 *
 * Files referenced from this file should be defined as
 *
 * ```
 *   Config::projectPath('relative_path/to/file')
 * ```
 *
 * where the relative path is relative to the root of the
 * project which is defined as either the working directory
 * of the phan executable or a path passed in via the CLI
 * '-d' flag.
 */
return [
    // Supported values: '7.0', '7.1', '7.2', null.
    // If this is set to null,
    // then Phan assumes the PHP version which is closest to the minor version
    // of the php executable used to execute phan.
    "target_php_version" => null,

    // If true, missing properties will be created when
    // they are first seen. If false, we'll report an
    // error message.
    "allow_missing_properties" => false,

    // Allow null to be cast as any type and for any
    // type to be cast to null.
    "null_casts_as_any_type" => false,

    // Allow null to be cast as any array-like type
    // This is an incremental step in migrating away from null_casts_as_any_type.
    // If null_casts_as_any_type is true, this has no effect.
    'null_casts_as_array' => false,

    // Allow any array-like type to be cast to null.
    // This is an incremental step in migrating away from null_casts_as_any_type.
    // If null_casts_as_any_type is true, this has no effect.
    'array_casts_as_null' => false,

    // If enabled, Phan will warn if **any** type in the argument's type
    // cannot be cast to a type in the parameter's expected type.
    // Setting this to true will introduce a large number of false positives (and some bugs).
    // (For self-analysis, Phan has a large number of suppressions and file-level suppressions, due to \ast\Node being difficult to type check)
    'strict_param_checking' => true,

    // If enabled, Phan will warn if **any** type in a property assignment's type
    // cannot be cast to a type in the property's expected type.
    // Setting this to true will introduce a large number of false positives (and some bugs).
    // (For self-analysis, Phan has a large number of suppressions and file-level suppressions, due to \ast\Node being difficult to type check)
    'strict_property_checking' => true,

    // If enabled, Phan will warn if **any** type in the return statement's type
    // cannot be cast to a type in the method's declared return type.
    // Setting this to true will introduce a large number of false positives (and some bugs).
    // (For self-analysis, Phan has a large number of suppressions and file-level suppressions, due to \ast\Node being difficult to type check)
    'strict_return_checking' => true,

    // If enabled, scalars (int, float, bool, string, null)
    // are treated as if they can cast to each other.
    // This does not affect checks of array keys. See scalar_array_key_cast.
    'scalar_implicit_cast' => false,

    // If enabled, any scalar array keys (int, string)
    // are treated as if they can cast to each other.
    // E.g. array<int,stdClass> can cast to array<string,stdClass> and vice versa.
    // Normally, a scalar type such as int could only cast to/from int and mixed.
    'scalar_array_key_cast' => false,

    // If this has entries, scalars (int, float, bool, string, null)
    // are allowed to perform the casts listed.
    // E.g. ['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']]
    // allows casting null to a string, but not vice versa.
    // (subset of scalar_implicit_cast)
    'scalar_implicit_partial' => [],

    // If true, seemingly undeclared variables in the global
    // scope will be ignored. This is useful for projects
    // with complicated cross-file globals that you have no
    // hope of fixing.
    'ignore_undeclared_variables_in_global_scope' => false,

    // Backwards Compatibility Checking (This is very slow)
    'backward_compatibility_checks' => false,

    // If true, check to make sure the return type declared
    // in the doc-block (if any) matches the return type
    // declared in the method signature. This process is
    // slow.
    'check_docblock_signature_return_type_match' => true,

    // If true, check to make sure the return type declared
    // in the doc-block (if any) matches the return type
    // declared in the method signature. This process is
    // slow.
    'check_docblock_signature_param_type_match' => true,

    // (*Requires check_docblock_signature_param_type_match to be true*)
    // If true, make narrowed types from phpdoc params override
    // the real types from the signature, when real types exist.
    // (E.g. allows specifying desired lists of subclasses,
    //  or to indicate a preference for non-nullable types over nullable types)
    // Affects analysis of the body of the method and the param types passed in by callers.
    'prefer_narrowed_phpdoc_param_type' => true,

    // (*Requires check_docblock_signature_return_type_match to be true*)
    // If true, make narrowed types from phpdoc returns override
    // the real types from the signature, when real types exist.
    // (E.g. allows specifying desired lists of subclasses,
    //  or to indicate a preference for non-nullable types over nullable types)
    // Affects analysis of return statements in the body of the method and the return types passed in by callers.
    'prefer_narrowed_phpdoc_return_type' => true,

    // If enabled, check all methods that override a
    // parent method to make sure its signature is
    // compatible with the parent's. This check
    // can add quite a bit of time to the analysis.
    // This will also check if final methods are overridden, etc.
    'analyze_signature_compatibility' => true,

    // Set this to true to allow contravariance in real parameter types of method overrides (Introduced in php 7.2)
    // See https://secure.php.net/manual/en/migration72.new-features.php#migration72.new-features.param-type-widening
    // (Users may enable this if analyzing projects that support only php 7.2+)
    // This is false by default. (Will warn if real parameter types are omitted in an override)
    'allow_method_param_type_widening' => false,

    // Set this to true to make Phan guess that undocumented parameter types
    // (for optional parameters) have the same type as default values
    // (Instead of combining that type with `mixed`).
    // E.g. `function($x = 'val')` would make Phan infer that $x had a type of `string`, not `string|mixed`.
    // Phan will not assume it knows specific types if the default value is false or null.
    'guess_unknown_parameter_type_using_default' => false,

    // This setting maps case insensitive strings to union types.
    // This is useful if a project uses phpdoc that differs from the phpdoc2 standard.
    // If the corresponding value is the empty string, Phan will ignore that union type (E.g. can ignore 'the' in `@return the value`)
    // If the corresponding value is not empty, Phan will act as though it saw the corresponding unionTypes(s) when the keys show up in a UnionType of @param, @return, @var, @property, etc.
    //
    // This matches the **entire string**, not parts of the string.
    // (E.g. `@return the|null` will still look for a class with the name `the`, but `@return the` will be ignored with the below setting)
    //
    // (These are not aliases, this setting is ignored outside of doc comments).
    // (Phan does not check if classes with these names exist)
    //
    // Example setting: ['unknown' => '', 'number' => 'int|float', 'char' => 'string', 'long' => 'int', 'the' => '']
    'phpdoc_type_mapping' => [ ],

    // Set to true in order to attempt to detect dead
    // (unreferenced) code. Keep in mind that the
    // results will only be a guess given that classes,
    // properties, constants and methods can be referenced
    // as variables (like `$class->$property` or
    // `$class->$method()`) in ways that we're unable
    // to make sense of.
    'dead_code_detection' => false,

    // Set to true in order to force tracking references to elements
    // (functions/methods/consts/protected).
    // dead_code_detection is another option which also causes references
    // to be tracked.
    'force_tracking_references' => false,

    // If true, then run a quick version of checks that takes less time.
    // False by default.
    "quick_mode" => false,

    // If true, then before analysis, try to simplify AST into a form
    // which improves Phan's type inference in edge cases.
    //
    // This may conflict with 'dead_code_detection'.
    // When this is true, this slows down analysis slightly.
    //
    // E.g. rewrites `if ($a = value() && $a > 0) {...}`
    // into $a = value(); if ($a) { if ($a > 0) {...}}`
    "simplify_ast" => true,

    // If true, Phan will read `class_alias` calls in the global scope,
    // then (1) create aliases from the *parsed* files if no class definition was found,
    // and (2) emit issues in the global scope if the source or target class is invalid.
    // (If there are multiple possible valid original classes for an aliased class name,
    //  the one which will be created is unspecified.)
    // NOTE: THIS IS EXPERIMENTAL, and the implementation may change.
    'enable_class_alias_support' => false,

    // Enable or disable support for generic templated
    // class types.
    'generic_types_enabled' => true,

    // Setting this to true makes the process assignment for file analysis
    // as predictable as possible, using consistent hashing.
    // Even if files are added or removed, or process counts change,
    // relatively few files will move to a different group.
    // (use when the number of files is much larger than the process count)
    // NOTE: If you rely on Phan parsing files/directories in the order
    // that they were provided in this config, don't use this)
    // See https://github.com/etsy/phan/wiki/Different-Issue-Sets-On-Different-Numbers-of-CPUs
    'consistent_hashing_file_order' => false,

    // Override to hardcode existence and types of (non-builtin) globals.
    // Class names should be prefixed with '\\'.
    // (E.g. ['_FOO' => '\\FooClass', 'page' => '\\PageClass', 'userId' => 'int'])
    'globals_type_map' => [],

    // The minimum severity level to report on. This can be
    // set to Issue::SEVERITY_LOW, Issue::SEVERITY_NORMAL or
    // Issue::SEVERITY_CRITICAL.
    'minimum_severity' => Issue::SEVERITY_LOW,

    // Add any issue types (such as 'PhanUndeclaredMethod')
    // here to inhibit them from being reported
    'suppress_issue_types' => [
        'PhanUnreferencedClosure',  // False positives seen with closures in arrays, TODO: move closure checks closer to what is done by unused variable plugin
        'PhanPossiblyFalseTypeArgument',
        'PhanPossiblyFalseTypeArgumentInternal',
        'PhanPossiblyNullTypeArgument',
        'PhanPossiblyNullTypeArgumentInternal',
        'PhanPossiblyNullTypeReturn',
        // 'PhanUndeclaredMethod',
    ],

    // If empty, no filter against issues types will be applied.
    // If non-empty, only issues within the list will be emitted
    // by Phan.
    'whitelist_issue_types' => [
        // 'PhanAccessClassConstantInternal',
        // 'PhanAccessClassConstantPrivate',
        // 'PhanAccessClassConstantProtected',
        // 'PhanAccessClassInternal',
        // 'PhanAccessConstantInternal',
        // 'PhanAccessMethodInternal',
        // 'PhanAccessMethodPrivate',
        // 'PhanAccessMethodPrivateWithCallMagicMethod',
        // 'PhanAccessMethodProtected',
        // 'PhanAccessMethodProtectedWithCallMagicMethod',
        // 'PhanAccessNonStaticToStatic',
        // 'PhanAccessOwnConstructor',
        // 'PhanAccessPropertyInternal',
        // 'PhanAccessPropertyPrivate',
        // 'PhanAccessPropertyProtected',
        // 'PhanAccessPropertyStaticAsNonStatic',
        // 'PhanAccessSignatureMismatch',
        // 'PhanAccessSignatureMismatchInternal',
        // 'PhanAccessStaticToNonStatic',
        // 'PhanClassContainsAbstractMethod',
        // 'PhanClassContainsAbstractMethodInternal',
        // 'PhanCommentParamOnEmptyParamList',
        // 'PhanCommentParamWithoutRealParam',
        // 'PhanCompatibleExpressionPHP7',
        // 'PhanCompatiblePHP7',
        // 'PhanContextNotObject',
        // 'PhanDeprecatedClass',
        // 'PhanDeprecatedFunction',
        // 'PhanDeprecatedFunctionInternal',
        // 'PhanDeprecatedInterface',
        // 'PhanDeprecatedProperty',
        // 'PhanDeprecatedTrait',
        // 'PhanEmptyFile',
        // 'PhanGenericConstructorTypes',
        // 'PhanGenericGlobalVariable',
        // 'PhanIncompatibleCompositionMethod',
        // 'PhanIncompatibleCompositionProp',
        // 'PhanInvalidCommentForDeclarationType',
        // 'PhanMismatchVariadicComment',
        // 'PhanMismatchVariadicParam',
        // 'PhanMisspelledAnnotation',
        // 'PhanNonClassMethodCall',
        // 'PhanNoopArray',
        // 'PhanNoopClosure',
        // 'PhanNoopConstant',
        // 'PhanNoopProperty',
        // 'PhanNoopVariable',
        // 'PhanParamRedefined',
        // 'PhanParamReqAfterOpt',
        // 'PhanParamSignatureMismatch',
        // 'PhanParamSignatureMismatchInternal',
        // 'PhanParamSignaturePHPDocMismatchHasNoParamType',
        // 'PhanParamSignaturePHPDocMismatchHasParamType',
        // 'PhanParamSignaturePHPDocMismatchParamIsNotReference',
        // 'PhanParamSignaturePHPDocMismatchParamIsReference',
        // 'PhanParamSignaturePHPDocMismatchParamNotVariadic',
        // 'PhanParamSignaturePHPDocMismatchParamType',
        // 'PhanParamSignaturePHPDocMismatchParamVariadic',
        // 'PhanParamSignaturePHPDocMismatchReturnType',
        // 'PhanParamSignaturePHPDocMismatchTooFewParameters',
        // 'PhanParamSignaturePHPDocMismatchTooManyRequiredParameters',
        // 'PhanParamSignatureRealMismatchHasNoParamType',
        // 'PhanParamSignatureRealMismatchHasNoParamTypeInternal',
        // 'PhanParamSignatureRealMismatchHasParamType',
        // 'PhanParamSignatureRealMismatchHasParamTypeInternal',
        // 'PhanParamSignatureRealMismatchParamIsNotReference',
        // 'PhanParamSignatureRealMismatchParamIsNotReferenceInternal',
        // 'PhanParamSignatureRealMismatchParamIsReference',
        // 'PhanParamSignatureRealMismatchParamIsReferenceInternal',
        // 'PhanParamSignatureRealMismatchParamNotVariadic',
        // 'PhanParamSignatureRealMismatchParamNotVariadicInternal',
        // 'PhanParamSignatureRealMismatchParamType',
        // 'PhanParamSignatureRealMismatchParamTypeInternal',
        // 'PhanParamSignatureRealMismatchParamVariadic',
        // 'PhanParamSignatureRealMismatchParamVariadicInternal',
        // 'PhanParamSignatureRealMismatchReturnType',
        // 'PhanParamSignatureRealMismatchReturnTypeInternal',
        // 'PhanParamSignatureRealMismatchTooFewParameters',
        // 'PhanParamSignatureRealMismatchTooFewParametersInternal',
        // 'PhanParamSignatureRealMismatchTooManyRequiredParameters',
        // 'PhanParamSignatureRealMismatchTooManyRequiredParametersInternal',
        // 'PhanParamSpecial1',
        // 'PhanParamSpecial2',
        // 'PhanParamSpecial3',
        // 'PhanParamSpecial4',
        // 'PhanParamTooFew',
        // 'PhanParamTooFewInternal',
        // 'PhanParamTooMany',
        // 'PhanParamTooManyInternal',
        // 'PhanParamTypeMismatch',
        // 'PhanParentlessClass',
        // 'PhanRedefineClass',
        // 'PhanRedefineClassAlias',
        // 'PhanRedefineClassInternal',
        // 'PhanRedefineFunction',
        // 'PhanRedefineFunctionInternal',
        // 'PhanRequiredTraitNotAdded',
        // 'PhanStaticCallToNonStatic',
        // 'PhanSyntaxError',
        // 'PhanTemplateTypeConstant',
        // 'PhanTemplateTypeStaticMethod',
        // 'PhanTemplateTypeStaticProperty',
        // 'PhanTraitParentReference',
        // 'PhanTypeArrayOperator',
        // 'PhanTypeArraySuspicious',
        // 'PhanTypeComparisonFromArray',
        // 'PhanTypeComparisonToArray',
        // 'PhanTypeConversionFromArray',
        // 'PhanTypeInstantiateAbstract',
        // 'PhanTypeInstantiateInterface',
        // 'PhanTypeInvalidClosureScope',
        // 'PhanTypeInvalidLeftOperand',
        // 'PhanTypeInvalidRightOperand',
        // 'PhanTypeMismatchArgument',
        // 'PhanTypeMismatchArgumentInternal',
        // 'PhanTypeMismatchDeclaredParam',
        // 'PhanTypeMismatchDeclaredReturn',
        // 'PhanTypeMismatchDefault',
        // 'PhanTypeMismatchForeach',
        // 'PhanTypeMismatchProperty',
        // 'PhanTypeMismatchReturn',
        // 'PhanTypeMissingReturn',
        // 'PhanTypeNonVarPassByRef',
        // 'PhanTypeParentConstructorCalled',
        // 'PhanTypeSuspiciousIndirectVariable',
        // 'PhanTypeVoidAssignment',
        // 'PhanUnanalyzable',
        // 'PhanUndeclaredAliasedMethodOfTrait',
        // 'PhanUndeclaredClass',
        // 'PhanUndeclaredClassAliasOriginal',
        // 'PhanUndeclaredClassCatch',
        // 'PhanUndeclaredClassConstant',
        // 'PhanUndeclaredClassInstanceof',
        // 'PhanUndeclaredClassMethod',
        // 'PhanUndeclaredClassReference',
        // 'PhanUndeclaredClosureScope',
        // 'PhanUndeclaredConstant',
        // 'PhanUndeclaredExtendedClass',
        // 'PhanUndeclaredFunction',
        // 'PhanUndeclaredInterface',
        // 'PhanUndeclaredMethod',
        // 'PhanUndeclaredProperty',
        // 'PhanUndeclaredStaticMethod',
        // 'PhanUndeclaredStaticProperty',
        // 'PhanUndeclaredTrait',
        // 'PhanUndeclaredTypeParameter',
        // 'PhanUndeclaredTypeProperty',
        // 'PhanUndeclaredTypeReturnType',
        // 'PhanUndeclaredVariable',
        // 'PhanUndeclaredVariableDim',
        // 'PhanUnextractableAnnotation',
        // 'PhanUnextractableAnnotationPart',
        // 'PhanUnreferencedClass',
        // 'PhanUnreferencedConstant',
        // 'PhanUnreferencedMethod',
        // 'PhanUnreferencedProperty',
        // 'PhanVariableUseClause',
    ],

    // A list of files to include in analysis
    'file_list' => [
        'phan',
        'phan_client',
        'tool/make_stubs',
        // 'vendor/phpunit/phpunit/src/Framework/TestCase.php',
    ],

    // A regular expression to match files to be excluded
    // from parsing and analysis and will not be read at all.
    //
    // This is useful for excluding groups of test or example
    // directories/files, unanalyzable files, or files that
    // can't be removed for whatever reason.
    // (e.g. '@Test\.php$@', or '@vendor/.*/(tests|Tests)/@')
    'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@',

    // A file list that defines files that will be excluded
    // from parsing and analysis and will not be read at all.
    //
    // This is useful for excluding hopelessly unanalyzable
    // files that can't be removed for whatever reason.
    'exclude_file_list' => [],

    // The number of processes to fork off during the analysis
    // phase.
    'processes' => 1,

    // A list of directories that should be parsed for class and
    // method information. After excluding the directories
    // defined in exclude_analysis_directory_list, the remaining
    // files will be statically analyzed for errors.
    //
    // Thus, both first-party and third-party code being used by
    // your application should be included in this list.
    'directory_list' => [
        'src',
        'internal',
        'tests/Phan',
        'vendor/composer/semver/src',
        'vendor/composer/xdebug-handler/src',
        'vendor/felixfbecker/advanced-json-rpc/lib',
        'vendor/microsoft/tolerant-php-parser/src',
        'vendor/netresearch/jsonmapper/src',
        'vendor/phpunit/phpunit/src',
        'vendor/psr/log/Psr',
        'vendor/sabre/event/lib',
        'vendor/symfony/console',
        '.phan/plugins',
        '.phan/stubs',
    ],

    // List of case-insensitive file extensions supported by Phan.
    // (e.g. php, html, htm)
    'analyzed_file_extensions' => ['php'],

    // A directory list that defines files that will be excluded
    // from static analysis, but whose class and method
    // information should be included.
    //
    // Generally, you'll want to include the directories for
    // third-party code (such as "vendor/") in this list.
    //
    // n.b.: If you'd like to parse but not analyze 3rd
    //       party code, directories containing that code
    //       should be added to the `directory_list` as
    //       to `exclude_analysis_directory_list`.
    "exclude_analysis_directory_list" => [
        'vendor/'
    ],

    // By default, Phan will log error messages to stdout if PHP is using options that slow the analysis.
    // (e.g. PHP is compiled with --enable-debug or when using XDebug)
    'skip_slow_php_options_warning' => false,

    // You can put paths to internal stubs in this config option.
    // Phan will continue using its detailed type annotations, but load the constants, classes, functions, and classes (and their Reflection types) from these stub files (doubling as valid php files).
    // Use a different extension from php to avoid accidentally loading these.
    // The 'mkstubs' script can be used to generate your own stubs (compatible with php 7.0+ right now)
    'autoload_internal_extension_signatures' => [
        'ast'         => '.phan/internal_stubs/ast.phan_php',
        'ctype'       => '.phan/internal_stubs/ctype.phan_php',
        'pcntl'       => '.phan/internal_stubs/pcntl.phan_php',
        'posix'       => '.phan/internal_stubs/posix.phan_php',
        'readline'    => '.phan/internal_stubs/readline.phan_php',
        'sysvmsg'     => '.phan/internal_stubs/sysvmsg.phan_php',
        'sysvsem'     => '.phan/internal_stubs/sysvsem.phan_php',
        'sysvshm'     => '.phan/internal_stubs/sysvshm.phan_php',
    ],

    // Set this to false to emit PhanUndeclaredFunction issues for internal functions that Phan has signatures for,
    // but aren't available in the codebase, or the internal functions used to run phan (may lead to false positives if an extension isn't loaded)
    // If this is true(default), then Phan will not warn.
    // Also see 'autoload_internal_extension_signatures' for an alternative way to fix this type of issue.
    'ignore_undeclared_functions_with_known_signatures' => false,

    'plugin_config' => [
        // A list of 1 or more PHP binaries (Absolute path or program name found in $PATH)
        // to use to analyze your files with PHP's native `--syntax-check`.
        //
        // This can be used to simultaneously run PHP's syntax checks with multiple PHP versions.
        // e.g. `'plugin_config' => ['php_native_syntax_check_binaries' => ['php72', 'php70', 'php56']]`
        // if all of those programs can be found in $PATH

        // 'php_native_syntax_check_binaries' => [PHP_BINARY],

        // The maximum number of `php --syntax-check` processes to run at any point in time (Minimum: 1).
        // This may be temporarily higher if php_native_syntax_check_binaries has more elements than this process count.
        'php_native_syntax_check_max_processes' => 4,
    ],

    // A list of plugin files to execute
    // NOTE: values can be the base name without the extension for plugins bundled with Phan (E.g. 'AlwaysReturnPlugin')
    // or relative/absolute paths to the plugin (Relative to the project root).
    'plugins' => [
        'AlwaysReturnPlugin',
        'DemoPlugin',
        'DollarDollarPlugin',
        'UnreachableCodePlugin',
        // NOTE: src/Phan/Language/Internal/FunctionSignatureMap.php mixes value without keys (as return type) with values having keys deliberately.
        'DuplicateArrayKeyPlugin',
        'PregRegexCheckerPlugin',
        'PrintfCheckerPlugin',
        // InvokePHPNativeSyntaxCheckPlugin invokes 'php --no-php-ini --syntax-check ${abs_path_to_analyzed_file}.php' and reports any error messages.
        // Using this can cause phan's overall analysis time to more than double.
        // 'InvokePHPNativeSyntaxCheckPlugin',

        // 'PHPUnitNotDeadCodePlugin',  // Marks phpunit test case subclasses and test cases as refernced code. only useful for runs when dead code detection is enabled

        // NOTE: This plugin only produces correct results when
        //       Phan is run on a single core (-j1).
        // 'UnusedSuppressionPlugin',
    ],
];
<?php declare(strict_types=1);

namespace Phan\Plugin\PrintfCheckerPlugin;  // Don't pollute the global namespace

use Phan\AST\ContextNode;
use Phan\AST\UnionTypeVisitor;
use Phan\CodeBase;
use Phan\Exception\CodeBaseException;
use Phan\Issue;
use Phan\Language\Context;
use Phan\Language\Element\Func;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Type;
use Phan\Language\UnionType;
use Phan\PluginV2;
use Phan\PluginV2\AnalyzeFunctionCallCapability;

use ast\Node;
use ast;

use function implode;
use function var_export;

/**
 * This plugin checks for invalid format strings and invalid uses of format strings in printf and sprintf, etc.
 * e.g. for printf("literal format %s", $arg)
 *
 * This uses ConversionSpec as a best effort at determining the the positions used by PHP format strings.
 * Some edge cases may have been overlooked.
 *
 * This validates strings of the form
 * -    constant strings, such as '%d of %s'
 * -    TODO: _(str) and gettext(str)
 * -    TODO: Better resolution of global constants and class constants
 *
 * This analyzes printf, sprintf, and fprintf.
 *
 * TODO: Add optional verbose warnings about unanalyzable strings
 * TODO: Check if arg can cast to string.
 */
class PrintfCheckerPlugin extends PluginV2 implements AnalyzeFunctionCallCapability
{

    // Pylint error codes for emitted issues.
    const ERR_UNTRANSLATED_USE_ECHO                = 1300;
    const ERR_UNTRANSLATED_NONE_USED               = 1301;
    const ERR_UNTRANSLATED_NONEXISTENT             = 1302;
    const ERR_UNTRANSLATED_UNUSED                  = 1303;
    const ERR_UNTRANSLATED_NOT_PERCENT             = 1304;
    const ERR_UNTRANSLATED_INCOMPATIBLE_SPECIFIER  = 1305;
    const ERR_UNTRANSLATED_INCOMPATIBLE_ARGUMENT   = 1306;  // E.g. passing a string where an int is expected
    const ERR_UNTRANSLATED_INCOMPATIBLE_ARGUMENT_WEAK = 1307;  // E.g. passing an int where a string is expected
    const ERR_UNTRANSLATED_WIDTH_INSTEAD_OF_POSITION = 1308; // e.g. _('%1s'). Change to _('%1$1s' if you really mean that the width is 1, add positions for others ('%2$s', etc.)
    const ERR_TRANSLATED_INCOMPATIBLE              = 1309;
    const ERR_TRANSLATED_HAS_MORE_ARGS             = 1311;

    /**
     * People who have translations may subclass this plugin and return a mapping from other locales to those locales translations of $fmt_str.
     * @return string[] mapping locale to the translation (e.g. ['fr_FR' => 'Bonjour'] for $fmt_str == 'Hello')
     * @suppress PhanPluginUnusedProtectedMethodArgument
     */
    protected static function gettextForAllLocales(string $fmt_str)
    {
        return [];
    }

    /**
     * Convert an expression(a list of tokens) to a primitive.
     * People who have custom such as methods or functions to fetch translations
     * may subclass this plugin and override this method to add checks for AST_CALL (foo()), AST_METHOD_CALL(MyClass::getTranslation($id), etc.)
     *
     * @param CodeBase $code_base
     * @param Context $context
     * @param bool|int|string|float|Node|array|null $astNode
     * @return ?PrimitiveValue
     */
    protected function astNodeToPrimitive(CodeBase $code_base, Context $context, $astNode)
    {
        // Base case: convert primitive tokens such as numbers and strings.
        if (!($astNode instanceof Node)) {
            return new PrimitiveValue($astNode);
        }
        switch ($astNode->kind) {
            case \ast\AST_CONST:
                $nameNode = $astNode->children['name'];
                if ($nameNode->kind === \ast\AST_NAME) {
                    $name = $nameNode->children['name'];
                    if (!\is_string($name)) {
                        return null;
                    }

                    if (\strcasecmp($name, '__DIR__') === 0) {
                        // Relative to the directory of that file... Hopefully doesn't contain a format specifier
                        return new PrimitiveValue('(__DIR__ literal)');
                    } elseif (\strcasecmp($name, '__FILE__') === 0) {
                        // Relative to the directory of that file... Hopefully doesn't contain a format specifier
                        return new PrimitiveValue('(__FILE__ literal)');
                    } elseif (\defined($name)) {
                        // TODO: This can be an array, which is almost definitely wrong in printf contexts
                        // FIXME use GlobalConstant to retrieve the literal's value
                        $value = \constant($name);
                        if (!\is_scalar($value)) {
                            return null;
                        }
                        return new PrimitiveValue($value);
                    }
                }
                return null;
        // TODO: Resolve class constant access when those are format strings. Same for PregRegexCheckerPlugin.
            case \ast\AST_CALL:
                $nameNode = $astNode->children['expr'];
                if ($nameNode->kind === \ast\AST_NAME) {
                    // TODO: Use Phan's function resolution?
                    // TODO: ngettext?
                    $name = $nameNode->children['name'];
                    if (!\is_string($name)) {
                        return null;
                    }
                    if ($name === '_' || strcasecmp($name, 'gettext') === 0) {
                        $childArg = $astNode->children['args']->children[0] ?? null;
                        if ($childArg === null) {
                            return null;
                        }
                        $prim = self::astNodeToPrimitive($code_base, $context, $childArg);
                        if ($prim === null) {
                            return null;
                        }
                        return new PrimitiveValue($prim->value, true);
                    }
                }
                return null;
            case \ast\AST_BINARY_OP:
                if ($astNode->flags !== ast\flags\BINARY_CONCAT) {
                    return null;
                }
                $left = $this->astNodeToPrimitive($code_base, $context, $astNode->children['left']);
                if ($left === null) {
                    return null;
                }
                $right = $this->astNodeToPrimitive($code_base, $context, $astNode->children['right']);
                if ($right === null) {
                    return null;
                }
                return $this->concatenateToPrimitive($left, $right);
        }
        // We don't know how to convert this to a primitive, give up.
        // (Subclasses may add their own logic first, then call self::astNodeToPrimitive)
        return null;
    }

    /**
     * Convert a primitive and a sequence of tokens to a primitive formed by
     * concatenating strings.
     *
     * @param PrimitiveValue $left the value on the left.
     * @param PrimitiveValue $right the value on the right.
     * @return ?PrimitiveValue
     */
    protected function concatenateToPrimitive(PrimitiveValue $left, PrimitiveValue $right)
    {
        // Combining untranslated strings with anything will cause problems.
        if ($left->is_translated) {
            return null;
        }
        if ($right->is_translated) {
            return null;
        }
        $str = $left->value . $right->value;
        return new PrimitiveValue($str);
    }

    /**
     * @return \Closure[]
     */
    public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array
    {
        /**
         * Analyzes a printf-like function with a format directive in the first position.
         * @return void
         */
        $printf_callback = function (
            CodeBase $code_base,
            Context $context,
            Func $function,
            array $args
        ) {
            // TODO: Resolve global constants and class constants?
            // TODO: Check for AST_UNPACK
            $pattern = $args[0] ?? null;
            if ($pattern === null) {
                return;
            }
            if ($pattern instanceof Node) {
                $pattern = (new ContextNode($code_base, $context, $pattern))->getEquivalentPHPScalarValue();
            }
            $remaining_args = \array_slice($args, 1);
            $this->analyzePrintfPattern($code_base, $context, $function, $pattern, $remaining_args);
        };
        /**
         * Analyzes a printf-like function with a format directive in the first position.
         * @return void
         */
        $fprintf_callback = function (
            CodeBase $code_base,
            Context $context,
            Func $function,
            array $args
        ) {
            if (\count($args) < 2) {
                return;
            }
            // TODO: Resolve global constants and class constants?
            // TODO: Check for AST_UNPACK
            $pattern = $args[1];
            if ($pattern instanceof Node) {
                $pattern = (new ContextNode($code_base, $context, $pattern))->getEquivalentPHPValue();
            }
            $remaining_args = \array_slice($args, 2);
            $this->analyzePrintfPattern($code_base, $context, $function, $pattern, $remaining_args);
        };
        /**
         * Analyzes a printf-like function with a format directive in the first position.
         * @return void
         */
        $vprintf_callback = function (
            CodeBase $code_base,
            Context $context,
            Func $function,
            array $args
        ) {
            if (\count($args) < 2) {
                return;
            }
            // TODO: Resolve global constants and class constants?
            // TODO: Check for AST_UNPACK
            $pattern = $args[0];
            if ($pattern instanceof Node) {
                $pattern = (new ContextNode($code_base, $context, $pattern))->getEquivalentPHPScalarValue();
            }
            $format_args_node = $args[1];
            $format_args = (new ContextNode($code_base, $context, $format_args_node))->getEquivalentPHPValue();
            $this->analyzePrintfPattern($code_base, $context, $function, $pattern, \is_array($format_args) ? $format_args : null);
        };
        /**
         * Analyzes a printf-like function with a format directive in the first position.
         * @return void
         */
        $vfprintf_callback = function (
            CodeBase $code_base,
            Context $context,
            Func $function,
            array $args
        ) {
            if (\count($args) < 3) {
                return;
            }
            // TODO: Resolve global constants and class constants?
            // TODO: Check for AST_UNPACK
            $pattern = $args[1];
            if ($pattern instanceof Node) {
                $pattern = (new ContextNode($code_base, $context, $pattern))->getEquivalentPHPScalarValue();
            }
            $format_args_node = $args[2];
            $format_args = (new ContextNode($code_base, $context, $format_args_node))->getEquivalentPHPValue();
            $this->analyzePrintfPattern($code_base, $context, $function, $pattern, \is_array($format_args) ? $format_args : null);
        };
        return [
            // call
            'printf'     => $printf_callback,
            'sprintf'    => $printf_callback,
            'fprintf'    => $fprintf_callback,
            'vprintf'    => $vprintf_callback,
            'vsprintf'   => $vprintf_callback,
            'vfprintf'   => $vfprintf_callback,
        ];
    }

    protected function encodeString(string $str) : string
    {
        $result = \json_encode($str, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if ($result !== false) {
            return $result;
        }
        return var_export($str, true);
    }

    /**
     * Analyzes a printf pattern, emitting issues if necessary
     * @param CodeBase $code_base
     * @param Context $context
     * @param FunctionInterface $function
     * @param Node|array|string|float|int|bool|null $pattern_node
     * @param Node[]|string[]|int[]|float[] $arg_nodes arguments following the format string. Null if the arguments could not be determined.
     * @return void
     * @suppress PhanPartialTypeMismatchArgument TODO: refactor into smaller functions
     */
    protected function analyzePrintfPattern(CodeBase $code_base, Context $context, FunctionInterface $function, $pattern_node, $arg_nodes)
    {
        // Given a node, extract the printf directive and whether or not it could be translated
        $primitive_for_fmtstr = $this->astNodeToPrimitive($code_base, $context, $pattern_node);
        if ($primitive_for_fmtstr === null) {
            // TODO: Add a verbose option
            return;
        }
        // Make sure that the untranslated format string is being used correctly.
        // If the format string will be translated, also check the translations.
        //
        // Outputs any errors found to log and stdout.
        // Check that the untranslated format string is being used correctly.

        $fmt_str = $primitive_for_fmtstr->value;
        $is_translated = $primitive_for_fmtstr->is_translated;
        $specs = is_string($fmt_str) ? ConversionSpec::extract_all($fmt_str) : [];
        $fmt_str = (string)$fmt_str;
        /**
         * @param string $issue_type
         * A name for the type of issue such as 'PhanPluginMyIssue'
         *
         * @param string $issue_message_format
         * The complete issue message format string to emit such as
         * 'class with fqsen {CLASS} is broken in some fashion' (preferred)
         * or 'class with fqsen %s is broken in some fashion'
         * The list of placeholders for between braces can be found
         * in \Phan\Issue::uncolored_format_string_for_template.
         *
         * @param string[] $issue_message_args
         * The arguments for this issue format.
         * If this array is empty, $issue_message_args is kept in place
         *
         * @param int $severity
         * A value from the set {Issue::SEVERITY_LOW,
         * Issue::SEVERITY_NORMAL, Issue::SEVERITY_HIGH}.
         *
         * @param int $issue_type_id An issue id for pylint
         */
        $emit_issue = function (string $issue_type, string $issue_message_format, array $issue_message_args, int $severity, int $issue_type_id) use ($code_base, $context) {
            $this->emitIssue(
                $code_base,
                $context,
                $issue_type,
                $issue_message_format,
                $issue_message_args,
                $severity,
                Issue::REMEDIATION_B,
                $issue_type_id
            );
        };

        // Check for extra or missing arguments
        if (\is_array($arg_nodes) && \count($arg_nodes) === 0) {
            if (count($specs) > 0) {
                $largest_positional = \max(\array_keys($specs));
                $examples = [];
                foreach ($specs[$largest_positional] as $example_spec) {
                    $examples[] = $this->encodeString($example_spec->directive);
                }
                // emit issues with 1-based offsets
                $emit_issue(
                    'PhanPluginPrintfNonexistentArgument',
                    'Format string {STRING_LITERAL} refers to nonexistent argument #{INDEX} in {STRING_LITERAL}',
                    [$this->encodeString($fmt_str), $largest_positional, \implode(',', $examples)],
                    Issue::SEVERITY_NORMAL,
                    self::ERR_UNTRANSLATED_NONEXISTENT
                );
            }
            $replacement_function_name = \in_array($function->getName(), ['vprintf', 'fprintf', 'vfprintf']) ? 'fwrite' : 'echo';
            $emit_issue(
                "PhanPluginPrintfNoArguments",
                "No format string arguments are given for {STRING_LITERAL}, consider using {FUNCTION} instead",
                [$this->encodeString($fmt_str), $replacement_function_name],
                Issue::SEVERITY_LOW,
                self::ERR_UNTRANSLATED_USE_ECHO
            );
            return;
        }
        if (count($specs) == 0) {
            $emit_issue(
                'PhanPluginPrintfNoSpecifiers',
                'None of the formatting arguments passed alongside format string {STRING_LITERAL} are used',
                [$this->encodeString($fmt_str)],
                Issue::SEVERITY_LOW,
                self::ERR_UNTRANSLATED_NONE_USED
            );
            return;
        }

        if (\is_array($arg_nodes)) {
            $largest_positional = \max(\array_keys($specs));
            if ($largest_positional > \count($arg_nodes)) {
                $examples = [];
                foreach ($specs[$largest_positional] as $example_spec) {
                    $examples[] = $this->encodeString($example_spec->directive);
                }
                // emit issues with 1-based offsets
                $emit_issue(
                    'PhanPluginPrintfNonexistentArgument',
                    'Format string {STRING_LITERAL} refers to nonexistent argument #{INDEX} in {STRING_LITERAL}',
                    [$this->encodeString($fmt_str), $largest_positional, \implode(',', $examples)],
                    Issue::SEVERITY_NORMAL,
                    self::ERR_UNTRANSLATED_NONEXISTENT
                );
            } elseif ($largest_positional < count($arg_nodes)) {
                $emit_issue(
                    'PhanPluginPrintfUnusedArgument',
                    'Format string {STRING_LITERAL} does not use provided argument #{INDEX}',
                    [$this->encodeString($fmt_str), $largest_positional + 1],
                    Issue::SEVERITY_NORMAL,
                    self::ERR_UNTRANSLATED_UNUSED
                );
            }
        }

        /** @var string[][] maps argument position to a list of possible canonical strings (e.g. '%1$d') for that argument */
        $types_of_arg = [];

        // Check format string alone for common signs of problems.
        // E.g. "% s", "%1$d %1$s"
        foreach ($specs as $i => $spec_group) {
            $types = [];
            foreach ($spec_group as $spec) {
                $canonical = $spec->toCanonicalString();
                $types[$canonical] = true;
                if ($spec->padding_char === ' ' && ($spec->width === '' || empty($spec->position))) {
                    // Warn about "100% dollars" but not about "100%1$ 2dollars" (If both position and width were parsed, assume the padding was intentional)
                    $emit_issue(
                        'PhanPluginPrintfNotPercent',
                        "Format string {STRING_LITERAL} contains something that is not a percent sign, it will be treated as a format string '{STRING_LITERAL}' with padding. Use {DETAILS} for a literal percent sign, or '{STRING_LITERAL}' to be less ambiguous",
                        [$this->encodeString($fmt_str), $spec->directive, '%%', $canonical],
                        Issue::SEVERITY_NORMAL,
                        self::ERR_UNTRANSLATED_NOT_PERCENT
                    );
                }
                if ($is_translated && !empty($spec->width) &&
                        ($spec->padding_char === '' || $spec->padding_char === ' ')) {
                    $intended_string = $spec->toCanonicalStringWithWidthAsPosition();
                    $emit_issue(
                        'PhanPluginPrintfWidthNotPosition',
                        "Format string {STRING_LITERAL} is specifying a width({STRING_LITERAL}) instead of a position({STRING_LITERAL})",
                        [$this->encodeString($fmt_str), $this->encodeString($canonical), $this->encodeString($intended_string)],
                        Issue::SEVERITY_NORMAL,
                        self::ERR_UNTRANSLATED_WIDTH_INSTEAD_OF_POSITION
                    );
                }
            }

            $types_of_arg[$i] = $types;
            if (count($types) > 1) {
                // May be an off by one error in the format string.
                $emit_issue(
                    'PhanPluginPrintfIncompatibleSpecifier',
                    'Format string {STRING_LITERAL} refers to argument #{INDEX} in different ways: {DETAILS}',
                    [$this->encodeString($fmt_str), $i, implode(',', array_keys($types))],
                    Issue::SEVERITY_LOW,
                    self::ERR_UNTRANSLATED_INCOMPATIBLE_SPECIFIER
                );
            }
        }

        if (\is_array($arg_nodes)) {
            foreach ($specs as $i => $spec_group) {
                // $arg_nodes is a 0-based array, $spec_group is 1-based.
                $arg_node = $arg_nodes[$i - 1] ?? null;
                if (!isset($arg_node)) {
                    continue;
                }
                $actual_union_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $arg_node);
                if ($actual_union_type->isEmpty()) {
                    // Nothing to check.
                    continue;
                }

                $expected_set = [];
                foreach ($spec_group as $spec) {
                    $type_name = $spec->getExpectedUnionTypeName();
                    $expected_set[$type_name] = true;
                }
                $expected_union_type = new UnionType();
                foreach ($expected_set as $type_name => $_) {
                    $expected_union_type = $expected_union_type->withType(Type::fromFullyQualifiedString($type_name));
                }
                if ($actual_union_type->canCastToUnionType($expected_union_type)) {
                    continue;
                }
                if (isset($expected_set['string'])) {
                    $can_cast_to_string = false;
                    // Allow passing objects with __toString() to printf whether or not strict types are used in the caller.
                    // TODO: Move into a common helper method?
                    try {
                        foreach ($actual_union_type->asExpandedTypes($code_base)->asClassList($code_base, $context) as $clazz) {
                            if ($clazz->hasMethodWithName($code_base, '__toString')) {
                                $can_cast_to_string = true;
                                break;
                            }
                        }
                    } catch (CodeBaseException $e) {
                        // Swallow "Cannot find class", go on to emit issue.
                    }
                    if ($can_cast_to_string) {
                        continue;
                    }
                }

                $expected_union_type_string = (string)$expected_union_type;
                if ($this->canWeakCast($actual_union_type, $expected_set)) {
                    // This can be resolved by casting the arg to (string) manually in printf.
                    $emit_issue(
                        'PhanPluginPrintfIncompatibleArgumentTypeWeak',
                        'Format string {STRING_LITERAL} refers to argument #{INDEX} as {DETAILS}, so type {TYPE} is expected. However, {FUNCTION} was passed the type {TYPE} (which is weaker than {TYPE})',
                        [
                            $this->encodeString($fmt_str),
                            $i,
                            $this->getSpecStringsRepresentation($spec_group),
                            $expected_union_type_string,
                            $function->getName(),
                            (string)$actual_union_type,
                            $expected_union_type_string,
                        ],
                        Issue::SEVERITY_LOW,
                        self::ERR_UNTRANSLATED_INCOMPATIBLE_ARGUMENT_WEAK
                    );
                } else {
                    // This can be resolved by casting the arg to (int) manually in printf.
                    $emit_issue(
                        'PhanPluginPrintfIncompatibleArgumentType',
                        'Format string {STRING_LITERAL} refers to argument #{INDEX} as {DETAILS}, so type {TYPE} is expected, but {FUNCTION} was passed incompatible type {TYPE}',
                        [
                            $this->encodeString($fmt_str),
                            $i,
                            $this->getSpecStringsRepresentation($spec_group),
                            $expected_union_type_string,
                            $function->getName(),
                            (string)$actual_union_type,
                        ],
                        Issue::SEVERITY_LOW,
                        self::ERR_UNTRANSLATED_INCOMPATIBLE_ARGUMENT
                    );
                }
            }
        }

        // Make sure the translations are compatible with this format string.
        // In order to take advantage of the ability to analyze translations, override gettextForAllLocales
        if ($is_translated) {
            $this->validateTranslations($code_base, $context, $fmt_str, $types_of_arg);
        }
    }

    /**
     * @param ConversionSpec[] $specs
     */
    private function getSpecStringsRepresentation(array $specs) : string
    {
        return \implode(',', \array_unique(\array_map(function (ConversionSpec $spec) {
            return $spec->directive;
        }, $specs)));
    }

    private function canWeakCast(UnionType $actual_union_type, array $expected_set)
    {
        if (isset($expected_set['string'])) {
            static $string_weak_types;
            if ($string_weak_types === null) {
                $string_weak_types = UnionType::fromFullyQualifiedString('int|string|float');
            }
            return $actual_union_type->canCastToUnionType($string_weak_types);
        }
        // We already allow int->float conversion
        return false;
    }

    /**
     * TODO: Finish testing this.
     *
     * By default, this is a no-op, unless gettextForAllLocales is overridden in a subclass
     *
     * Check that the translations of the format string $fmt_str
     * are compatible with the untranslated format string.
     *
     * In virtually all cases, the conversions specifiers should be
     * identical to the conversion specifier (apart from whether or not
     * position is explicitly stated)
     *
     * Emits issues.
     *
     * @param CodeBase $code_base
     * @param Context $context
     * @param string $fmt_str
     * @param ConversionSpec[][] $types_of_arg contains array of ConversionSpec for
     *                                         each position in the untranslated format string.
     * @return void
     */
    protected function validateTranslations(CodeBase $code_base, Context $context, string $fmt_str, array $types_of_arg)
    {
        $translations = static::gettextForAllLocales($fmt_str);
        foreach ($translations as $locale => $translated_fmt_str) {
            // Skip untranslated or equal strings.
            if ($translated_fmt_str === $fmt_str) {
                continue;
            }
            // Compare the translated specs for a given position to the existing spec.
            $translated_specs = ConversionSpec::extract_all($translated_fmt_str);
            foreach ($translated_specs as $i => $spec_group) {
                $expected = $types_of_arg[$i] ?? [];
                foreach ($spec_group as $spec) {
                    $canonical = $spec->toCanonicalString();
                    if (!isset($expected[$canonical])) {
                        $expected_types = empty($expected) ? 'unused'
                                                           : implode(',', array_keys($expected));

                        if ($expected_types !== 'unused') {
                            $severity = Issue::SEVERITY_NORMAL;
                            $issue_type_id = self::ERR_TRANSLATED_INCOMPATIBLE;
                            $issue_type = 'PhanPluginPrintfTranslatedIncompatible';
                        } else {
                            $severity = Issue::SEVERITY_NORMAL;
                            $issue_type_id = self::ERR_TRANSLATED_HAS_MORE_ARGS;
                            $issue_type = 'PhanPluginPrintfTranslatedHasMoreArgs';
                        }
                        $this->emitIssue(
                            $code_base,
                            $context,
                            $issue_type,
                            'Translated string {STRING_LITERAL} has local {DETAILS} which refers to argument #{INDEX} as {STRING_LITERAL}, but the original format string treats it as {DETAILS} (ORIGINAL: {STRING_LITERAL}, TRANSLATION: {STRING_LITERAL})',
                            [
                                $this->encodeString($fmt_str),
                                $locale,
                                $i,
                                $canonical,
                                $expected_types,
                                $this->encodeString($fmt_str),
                                $this->encodeString($translated_fmt_str),
                            ],
                            $severity,
                            Issue::REMEDIATION_B,
                            $issue_type_id
                        );
                    }
                }
            }
        }
    }
}

/**
 * An object representing a conversion specifier of a format string, such as "%1$d".
 */
class ConversionSpec
{
    /** @var string Original text of the directive */
    public $directive;
    /** @var ?int Which argument this refers to, starting from 1 */
    public $position;
    /** @var string Character used for padding (commonly confused with $position) */
    public $padding_char;
    /** @var string indicates which side is used for alignment */
    public $alignment;
    /** @var string minimum width of output */
    public $width;         // Minimum width of output.
    /** @var string Type to print (s,d,f,etc.) */
    public $arg_type;

    /**
     * Create a conversion specifier from a match.
     * @param array $match groups in a match.
     */
    protected function __construct(array $match)
    {
        list($this->directive, $position_str, $this->padding_char, $this->alignment, $this->width, $unused_precision, $this->arg_type) = $match;
        if ($position_str !== "") {
            $this->position = \intval(\substr($position_str, 0, -1));
        }
    }

    // A padding string regex may be a space or 0.
    // Alternate padding specifiers may be specified by prefixing it with a single quote.
    const padding_string_regex_part ='[0 ]?|\'.';

    /**
     * Based on https://secure.php.net/manual/en/function.sprintf.php
     */
    const format_string_inner_regex_part =
        '%'  // Every format string begins with a percent
        . '(\d+\$)?'  // Optional n$ position specifier must go immediately after percent
        . '(' . self::padding_string_regex_part . ')'  // optional padding specifier
        . '([+-]?)' // optional alignment specifier
        . '(\d*)'  // optional width specifier
        . '(\.\d*)?'   // Optional precision specifier in the form of a period followed by an optional decimal digit string
        . '([bcdeEfFgGosuxX])';  // A type specifier


    const format_string_regex = '/%%|' . self::format_string_inner_regex_part . '/';

    /**
     * Extract a list of directives from a format string.
     * @param string $fmt_str a format string to extract directives from.
     * @return array<int,array<int,ConversionSpec>> array(int position => array of ConversionSpec referring to arg at that position)
     */
    public static function extract_all($fmt_str) : array
    {
        // echo "format is $fmt_str\n";
        $directives = [];
        \preg_match_all(self::format_string_regex, (string) $fmt_str, $matches, PREG_SET_ORDER);
        $unnamed_count = 0;
        foreach ($matches as $match) {
            if ($match[0] === '%%') {
                continue;
            }
            $directive = new self($match);
            if (!isset($directive->position)) {
                $directive->position = ++$unnamed_count;
            }
            $directives[$directive->position][] = $directive;
        }
        \ksort($directives);
        return $directives;
    }

    /**
     * @return string an unambiguous way of referring to this conversion spec.
     */
    public function toCanonicalString() : string
    {
        return '%' . $this->position . '$' . $this->padding_char . $this->alignment . $this->width . $this->arg_type;
    }

    /**
     * @return string the conversion spec if the width was used as a position instead.
     */
    public function toCanonicalStringWithWidthAsPosition() : string
    {
        return '%' . $this->width . '$' . $this->padding_char . $this->alignment . $this->arg_type;
    }
    const ARG_TYPE_LOOKUP = [
        'b' => 'int',
        'c' => 'int',
        'd' => 'int',
        'e' => 'float',
        'E' => 'float',
        'f' => 'float',
        'F' => 'float',
        'g' => 'float',
        'G' => 'float',
        'o' => 'int',
        's' => 'string',
        'u' => 'int',
        'x' => 'int',
        'X' => 'int',
    ];

    /**
     * @return string the name of the union type expected for the arg for this conversion spec
     */
    public function getExpectedUnionTypeName() : string
    {
        return self::ARG_TYPE_LOOKUP[$this->arg_type] ?? 'string';
    }
}

/**
 * Represents the information we have about the result of evaluating an expression.
 * Currently, used only for printf arguments.
 */
class PrimitiveValue
{
    /** @var array|int|string|float|bool|null The primitive value of the expression if it could be determined. */
    public $value;
    /** @var bool Whether or not the expression value was translated. */
    public $is_translated;

    /**
     * @param array|int|string|float|bool|null $value
     */
    public function __construct($value, bool $is_translated = false)
    {
        $this->value = $value;
        $this->is_translated = $is_translated;
    }
}

return new PrintfCheckerPlugin();
<?php declare(strict_types=1);

use Phan\PluginV2;
use Phan\PluginV2\PostAnalyzeNodeCapability;
use Phan\PluginV2\PluginAwarePostAnalysisVisitor;
use ast\Node;

/**
 * This plugin checks for occurrences of `$$x`,
 * which may be a typo, or behave differently in php 5 vs 7, or be hard to analyze code.
 *
 * This file demonstrates plugins for Phan. Plugins hook into various events.
 * DollarDollarPlugin hooks into one event:
 *
 * - getPostAnalyzeNodeVisitorClassName
 *   This method returns a visitor that is called on every AST node from every
 *   file being analyzed
 *
 * A plugin file must
 *
 * - Contain a class that inherits from \Phan\Plugin
 *
 * - End by returning an instance of that class.
 *
 * It is assumed without being checked that plugins aren't
 * mangling state within the passed code base or context.
 *
 * Note: When adding new plugins,
 * add them to the corresponding section of README.md
 */
class DollarDollarPlugin extends PluginV2 implements PostAnalyzeNodeCapability
{

    /**
     * @return string - name of PluginAwarePostAnalysisVisitor subclass
     */
    public static function getPostAnalyzeNodeVisitorClassName() : string
    {
        return DollarDollarVisitor::class;
    }
}

/**
 * When __invoke on this class is called with a node, a method
 * will be dispatched based on the `kind` of the given node.
 *
 * Visitors such as this are useful for defining lots of different
 * checks on a node based on its kind.
 */
class DollarDollarVisitor extends PluginAwarePostAnalysisVisitor
{

    // A plugin's visitors should not override visit() unless they need to.

    /**
     * @param Node $node
     * A node to analyze
     *
     * @return void
     * @override
     */
    public function visitVar(Node $node)
    {
        if ($node->children['name'] instanceof Node) {
            $this->emitPluginIssue(
                $this->code_base,
                $this->context,
                'PhanPluginDollarDollar',
                "$$ Variables are not allowed.",
                []
            );
        }
    }
}

// Every plugin needs to return an instance of itself at the
// end of the file in which its defined.
return new DollarDollarPlugin;
<?php declare(strict_types=1);

use Phan\Analysis\BlockExitStatusChecker;
use Phan\PluginV2;
use Phan\PluginV2\PostAnalyzeNodeCapability;
use Phan\PluginV2\PluginAwarePostAnalysisVisitor;
use ast\Node;

/**
 * This file checks for syntactically unreachable statements in
 * the global scope or function bodies.
 *
 * It hooks into one event:
 *
 * - getPostAnalyzeNodeVisitorClassName
 *   This method returns a class that is called on every AST node from every
 *   file being analyzed
 *
 * A plugin file must
 *
 * - Contain a class that inherits from \Phan\Plugin
 *
 * - End by returning an instance of that class.
 *
 * It is assumed without being checked that plugins aren't
 * mangling state within the passed code base or context.
 *
 * Note: When adding new plugins,
 * add them to the corresponding section of README.md
 */
final class UnreachableCodePlugin extends PluginV2 implements PostAnalyzeNodeCapability
{

    /**
     * @return string - The name of the visitor that will be called (formerly analyzeNode)
     */
    public static function getPostAnalyzeNodeVisitorClassName() : string
    {
        return UnreachableCodeVisitor::class;
    }
}

/**
 * When __invoke on this class is called with a node, a method
 * will be dispatched based on the `kind` of the given node.
 *
 * Visitors such as this are useful for defining lots of different
 * checks on a node based on its kind.
 */
final class UnreachableCodeVisitor extends PluginAwarePostAnalysisVisitor
{
    // A plugin's visitors should NOT implement visit(), unless they need to.

    const DECL_KIND_SET = [
        \ast\AST_CLASS      => true,
        \ast\AST_FUNC_DECL  => true,
        \ast\AST_CONST      => true,
    ];

    /**
     * @param Node $node
     * A node to analyze
     *
     * @return void
     *
     * @override
     */
    public function visitStmtList(Node $node)
    {
        $child_nodes = $node->children;
        // Debug::printNode($node);

        $last_node_index = count($child_nodes) - 1;
        foreach ($child_nodes as $i => $node) {
            \assert(\is_int($i));
            if ($i >= $last_node_index) {
                break;
            }
            if (!($node instanceof Node)) {
                continue;
            }
            if (!BlockExitStatusChecker::willUnconditionallyThrowOrReturn($node)) {
                continue;
            }
            // Skip over empty statements and scalar statements.
            for ($j = $i + 1; array_key_exists($j, $child_nodes); $j++) {
                $next_node = $child_nodes[$j];
                if (!($next_node instanceof Node && $next_node->lineno > 0)) {
                    continue;
                }
                if (array_key_exists($next_node->kind, self::DECL_KIND_SET)) {
                    if ($this->context->isInGlobalScope()) {
                        continue;
                    }
                }
                $context = clone($this->context)->withLineNumberStart($next_node->lineno);
                if ($this->context->isInFunctionLikeScope()) {
                    if ($this->context->getFunctionLikeInScope($this->code_base)->hasSuppressIssue('PhanPluginUnreachableCode')) {
                        // don't emit the below issue.
                        break;
                    }
                }
                $this->emitPluginIssue(
                    $this->code_base,
                    $context,
                    'PhanPluginUnreachableCode',
                    'Unreachable statement detected',
                    []
                );
                break;
            }
            break;
        }
    }
}
// Every plugin needs to return an instance of itself at the
// end of the file in which its defined.
return new UnreachableCodePlugin;
<?php declare(strict_types=1);

use Phan\CodeBase;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Func;
use Phan\Language\Element\Method;
use Phan\Language\Element\Property;
use Phan\PluginV2;
use Phan\PluginV2\AnalyzeClassCapability;
use Phan\PluginV2\AnalyzeFunctionCapability;
use Phan\PluginV2\AnalyzeMethodCapability;
use Phan\PluginV2\AnalyzePropertyCapability;
use Phan\PluginV2\PluginAwarePostAnalysisVisitor;
use Phan\PluginV2\PostAnalyzeNodeCapability;
use ast\Node;

/**
 * This file demonstrates plugins for Phan.
 * This Plugin hooks into five events;
 *
 * - getPostAnalyzeNodeVisitorClassName
 *   This method returns a class that is called on every AST node from every
 *   file being analyzed
 *
 * - analyzeClass
 *   Once all classes have been parsed, this method will be
 *   called on every class that is found in the code base
 *
 * - analyzeMethod
 *   Once all methods are parsed, this method will be called
 *   on every method in the code base
 *
 * - analyzeFunction
 *   Once all functions have been parsed, this method will
 *   be called on every function in the code base.
 *
 * - analyzeProperty
 *   Once all functions have been parsed, this method will
 *   be called on every property in the code base.
 *
 * A plugin file must
 *
 * - Contain a class that inherits from \Phan\PluginV2
 *   and implements one or more `Capability`s.
 *
 * - End by returning an instance of that class.
 *
 * It is assumed without being checked that plugins aren't
 * mangling state within the passed code base or context.
 *
 * Note: When adding new plugins,
 * add them to the corresponding section of README.md
 *
 * Compare this with DemoLegacyPlugin.php to see how to upgrade a subclass of the deprecated PluginImplementation to PluginV2.
 */
class DemoPlugin extends PluginV2 implements
    AnalyzeClassCapability,
    AnalyzeFunctionCapability,
    AnalyzeMethodCapability,
    PostAnalyzeNodeCapability,
    AnalyzePropertyCapability
{

    /**
     * @return string - The name of the visitor that will be called (formerly analyzeNode)
     * @override
     */
    public static function getPostAnalyzeNodeVisitorClassName() : string
    {
        return DemoNodeVisitor::class;
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the class exists
     *
     * @param Clazz $class
     * A class being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeClass(
        CodeBase $code_base,
        Clazz $class
    ) {
        // As an example, we test to see if the name of
        // the class is `Class`, and emit an issue explain that
        // the name is not allowed.
        // NOTE: Placeholders can be found in \Phan\Issue::uncolored_format_string_for_replace
        if ($class->getName() == 'Class') {
            $this->emitIssue(
                $code_base,
                $class->getContext(),
                'DemoPluginClassName',
                "Class {CLASS} cannot be called `Class`",
                [(string)$class->getFQSEN()]
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the method exists
     *
     * @param Method $method
     * A method being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeMethod(
        CodeBase $code_base,
        Method $method
    ) {
        // As an example, we test to see if the name of the
        // method is `function`, and emit an issue if it is.
        // NOTE: Placeholders can be found in \Phan\Issue::uncolored_format_string_for_replace
        if ($method->getName() == 'function') {
            $this->emitIssue(
                $code_base,
                $method->getContext(),
                'DemoPluginMethodName',
                "Method {METHOD} cannot be called `function`",
                [(string)$method->getFQSEN()]
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the function exists
     *
     * @param Func $function
     * A function being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeFunction(
        CodeBase $code_base,
        Func $function
    ) {
        // As an example, we test to see if the name of the
        // function is `function`, and emit an issue if it is.
        if ($function->getName() == 'function') {
            $this->emitIssue(
                $code_base,
                $function->getContext(),
                'DemoPluginFunctionName',
                "Function {FUNCTION} cannot be called `function`",
                [(string)$function->getFQSEN()]
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the function exists
     *
     * @param Property $property
     * A function being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeProperty(
        CodeBase $code_base,
        Property $property
    ) {
        // As an example, we test to see if the name of the
        // function is `foo`, and emit an issue if it is.
        if ($property->getName() == 'property') {
            $this->emitIssue(
                $code_base,
                $property->getContext(),
                'DemoPluginPropertyName',
                "Property {PROPERTY} should not be called `property`",
                [(string)$property->getFQSEN()]
            );
        }
    }
}

/**
 * When __invoke on this class is called with a node, a method
 * will be dispatched based on the `kind` of the given node.
 *
 * Visitors such as this are useful for defining lots of different
 * checks on a node based on its kind.
 */
class DemoNodeVisitor extends PluginAwarePostAnalysisVisitor
{
    // Subclasses should declare protected $parent_node_list as an instance property if they need to know the list.

    // @var array<int,Node> - Set after the constructor is called if an instance property with this name is declared
    // protected $parent_node_list;

    // A plugin's visitors should NOT implement visit(), unless they need to.

    /**
     * @param Node $node
     * A node to analyze
     *
     * @return void
     *
     * @override
     */
    public function visitInstanceof(Node $node)
    {
        // Debug::printNode($node);

        $class_name = $node->children['class']->children['name'] ?? null;

        // If we can't figure out the name of the class,  don't
        // bother continuing.
        if (empty($class_name)) {
            return;
        }

        // As an example, enforce that we cannot call
        // instanceof against 'object'.
        if ($class_name == 'object') {
            $this->emit(
                'PhanPluginInstanceOfObject',
                "Cannot call instanceof against `object`"
            );
        }
    }
}

// Every plugin needs to return an instance of itself at the
// end of the file in which its defined.
return new DemoPlugin;
<?php declare(strict_types=1);

use Phan\CodeBase;
use Phan\Analysis\BlockExitStatusChecker;
use Phan\Language\Element\Func;
use Phan\Language\Element\FunctionInterface;
use Phan\Language\Element\Method;
use Phan\Language\Type\NullType;
use Phan\Language\Type\VoidType;
use Phan\PluginV2;
use Phan\PluginV2\AnalyzeFunctionCapability;
use Phan\PluginV2\AnalyzeMethodCapability;

use ast\Node;

/**
 * This file checks if a function, closure or method unconditionally returns.
 * If the function doesn't have a void return type,
 * then this plugin will emit an issue.
 *
 * It hooks into two events:
 *
 * - analyzeMethod
 *   Once all methods are parsed, this method will be called
 *   on every method in the code base
 *
 * - analyzeFunction
 *   Once all functions have been parsed, this method will
 *   be called on every function in the code base.
 *
 * A plugin file must
 *
 * - Contain a class that inherits from \Phan\Plugin
 *
 * - End by returning an instance of that class.
 *
 * It is assumed without being checked that plugins aren't
 * mangling state within the passed code base or context.
 *
 * Note: When adding new plugins,
 * add them to the corresponding section of README.md
 */
final class AlwaysReturnPlugin extends PluginV2 implements
    AnalyzeFunctionCapability,
    AnalyzeMethodCapability
{

    /**
     * @param CodeBase $code_base
     * The code base in which the method exists
     *
     * @param Method $method
     * A method being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeMethod(
        CodeBase $code_base,
        Method $method
    ) {
        $stmts_list = $this->getStatementListToAnalyze($method);
        if ($stmts_list === null) {
            // check for abstract methods, generators, etc.
            return;
        }
        if ($method->getFQSEN() !== $method->getDefiningFQSEN()) {
            // Check if this was inherited by a descendant class.
            return;
        }

        if (self::returnTypeOfFunctionLikeAllowsNullNull($method)) {
            return;
        }
        if (!BlockExitStatusChecker::willUnconditionallyThrowOrReturn($stmts_list)) {
            if (!$method->hasSuppressIssue('PhanPluginAlwaysReturnMethod')) {
                $this->emitIssue(
                    $code_base,
                    $method->getContext(),
                    'PhanPluginAlwaysReturnMethod',
                    "Method {METHOD} has a return type of {TYPE}, but may fail to return a value",
                    [(string)$method->getFQSEN(), (string)$method->getUnionType()]
                );
            }
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the function exists
     *
     * @param Func $function
     * A function or closure being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeFunction(
        CodeBase $code_base,
        Func $function
    ) {
        $stmts_list = $this->getStatementListToAnalyze($function);
        if ($stmts_list === null) {
            // check for abstract methods, generators, etc.
            return;
        }

        if (self::returnTypeOfFunctionLikeAllowsNullNull($function)) {
            return;
        }
        if (!BlockExitStatusChecker::willUnconditionallyThrowOrReturn($stmts_list)) {
            if (!$function->hasSuppressIssue('PhanPluginAlwaysReturnFunction')) {
                $this->emitIssue(
                    $code_base,
                    $function->getContext(),
                    'PhanPluginAlwaysReturnFunction',
                    "Function {FUNCTION} has a return type of {TYPE}, but may fail to return a value",
                    [(string)$function->getFQSEN(), (string)$function->getUnionType()]
                );
            }
        }
    }

    /**
     * @param Func|Method $func
     * @return ?Node - returns null if there's no statement list to analyze
     */
    private function getStatementListToAnalyze($func)
    {
        if (!$func->hasNode()) {
            return null;
        } elseif ($func->getHasYield()) {
            // generators always return Generator.
            return null;
        }
        $node = $func->getNode();
        if (!$node) {
            return null;
        }
        assert($node->kind === \ast\AST_FUNC_DECL || $node->kind === \ast\AST_CLOSURE || $node->kind === \ast\AST_METHOD);
        return $node->children['stmts'];
    }

    /**
     * @param FunctionInterface $func
     * @return bool - Is void(absense of a return type) an acceptable return type.
     * NOTE: projects can customize this as needed.
     */
    private function returnTypeOfFunctionLikeAllowsNullNull(FunctionInterface $func) : bool
    {
        $real_return_type = $func->getRealReturnType();
        if (!$real_return_type->isEmpty() && !$real_return_type->isType(VoidType::instance(false))) {
            return false;
        }
        $return_type = $func->getUnionType();
        return ($return_type->isEmpty()
            || $return_type->containsNullable()
            || $return_type->hasType(VoidType::instance(false))
            || $return_type->hasType(NullType::instance(false)));
    }
}

// Every plugin needs to return an instance of itself at the
// end of the file in which its defined.
return new AlwaysReturnPlugin;
<?php declare(strict_types=1);

use Phan\AST\UnionTypeVisitor;
use Phan\Language\Context;
use Phan\PluginV2;
use Phan\PluginV2\PostAnalyzeNodeCapability;
use Phan\PluginV2\PluginAwarePostAnalysisVisitor;
use ast\Node;

class NonBoolInLogicalArithPlugin extends PluginV2 implements PostAnalyzeNodeCapability
{

    /**
     * @return string - name of PluginAwarePostAnalysisVisitor subclass
     *
     * @override
     */
    public static function getPostAnalyzeNodeVisitorClassName() : string
    {
        return NonBoolInLogicalArithVisitor::class;
    }
}

class NonBoolInLogicalArithVisitor extends PluginAwarePostAnalysisVisitor
{

    /** define boolean operator list */
    const BINARY_BOOL_OPERATORS = [
        ast\flags\BINARY_BOOL_OR,
        ast\flags\BINARY_BOOL_AND,
        ast\flags\BINARY_BOOL_XOR,
    ];

    // A plugin's visitors should not override visit() unless they need to.

    /**
     * @override
     */
    public function visitBinaryop(Node $node) : Context
    {
        // check every boolean binary operation
        if (in_array($node->flags, self::BINARY_BOOL_OPERATORS, true)) {
            // get left node and parse it
            // (dig nodes to avoid NOT('!') operator's converting its value to boolean type)
            $left_node = $node->children['left'];
            while (isset($left_node->flags) && $left_node->flags === ast\flags\UNARY_BOOL_NOT) {
                $left_node = $left_node->children['expr'];
            }

            // get right node and parse it
            $right_node = $node->children['right'];
            while (isset($right_node->flags) && $right_node->flags === ast\flags\UNARY_BOOL_NOT) {
                $right_node = $right_node->children['expr'];
            }

            // get the type of two nodes
            $left_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $left_node);
            $right_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $right_node);

            // if left or right type is NOT boolean, emit issue
            if (!$left_type->isExclusivelyBoolTypes()) {
                if ($left_node instanceof Node) {
                    $this->context = $this->context->withLineNumberStart($left_node->lineno);
                }
                $this->emit(
                    'PhanPluginNonBoolInLogicalArith',
                    'Non bool value of type {TYPE} in logical arithmetic',
                    [(string)$left_type]
                );
            }
            if (!$right_type->isExclusivelyBoolTypes()) {
                if ($right_node instanceof Node) {
                    $this->context = $this->context->withLineNumberStart($right_node->lineno);
                }
                $this->emit(
                    'PhanPluginNonBoolInLogicalArith',
                    'Non bool value of type {TYPE} in logical arithmetic',
                    [(string)$right_type]
                );
            }
        }
        return $this->context;
    }
}

return new NonBoolInLogicalArithPlugin;
<?php declare(strict_types=1);

use Phan\Config;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Method;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\Type;
use Phan\PluginV2;
use Phan\PluginV2\PostAnalyzeNodeCapability;
use Phan\PluginV2\PluginAwarePostAnalysisVisitor;

use ast\Node;

/**
 * Mark all phpunit test cases as used for dead code detection during Phan's self analysis.
 *
 * Implements the following capabilities
 * (This choice of capability makes this plugin efficiently analyze only classes that are in the analyzed file list)
 *
 * - public static function getPostAnalyzeNodeVisitorClassName() : string
 *   Returns the name of a class extending PluginAwarePostAnalysisVisitor, which will be used to analyze nodes in the analysis phase.
 *   If the PluginAwarePostAnalysisVisitor subclass has an instance property called parent_node_list,
 *   Phan will automatically set that property to the list of parent nodes (The nodes deepest in the AST are at the end of the list)
 *   (implement \Phan\PluginV2\PostAnalyzeNodeCapability)
 */
class PHPUnitNotDeadCodePlugin extends PluginV2 implements PostAnalyzeNodeCapability
{
    /**
     * @override
     */
    public static function getPostAnalyzeNodeVisitorClassName() : string
    {
        return PHPUnitNotDeadPluginVisitor::class;
    }
}

class PHPUnitNotDeadPluginVisitor extends PluginAwarePostAnalysisVisitor
{
    /** @var FullyQualifiedClassName */
    private static $phpunit_test_case_fqsen;

    /** @var Type */
    private static $phpunit_test_case_type;

    private static $did_warn_unused = false;

    /**
     * @override
     * This is called after the parse phase is completely finished, so $this->code_base contains all class definitions
     */
    public function visitClass(Node $unused_node)
    {
        if (!Config::get_track_references()) {
            return;
        }
        $code_base = $this->code_base;
        if (!$code_base->hasClassWithFQSEN(self::$phpunit_test_case_fqsen)) {
            if (!self::$did_warn_unused) {
                fprintf(STDERR, "Using plugin %s but could not find PHPUnit\Framework\TestCase\n", __CLASS__);
                self::$did_warn_unused = true;
            }
            return;
        }
        // This assumes PreOrderAnalysisVisitor->visitClass is called first.
        $context = $this->context;
        $class = $context->getClassInScope($code_base);
        if (!$class->getFQSEN()->asType()->asExpandedTypes($code_base)->hasType(self::$phpunit_test_case_type)) {
            // This isn't a phpunit test case.
            return;
        }

        // Mark subclasses of TestCase as referenced
        $class->addReference($context);
        // Mark all test cases as referenced
        foreach ($class->getMethodMap($code_base) as $method) {
            if (static::isTestCase($method)) {
                // TODO: Parse @dataProvider methodName, check for method existence,
                // then mark method for dataProvider as referenced.
                $method->addReference($context);
                $this->markDataProvidersAsReferenced($class, $method);
            }
        }
        // https://phpunit.de/manual/current/en/fixtures.html (PHPUnit framework checks for this override)
        if ($class->hasPropertyWithName($code_base, 'backupStaticAttributesBlacklist')) {
            $property = $class->getPropertyByName($code_base, 'backupStaticAttributesBlacklist');
            $property->addReference($context);
            $property->setHasReadReference();
        }
    }

    /**
     * This regex contains a single pattern, which matches a valid PHP identifier.
     * (e.g. for variable names, magic property names, etc.
     * This does not allow backslashes.
     */
    const WORD_REGEX = '([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)';

    /**
     * Marks all data provider methods as being referenced
     *
     * @param Method $method the Method representing a unit test in a test case subclass
     *
     * @return void
     */
    private function markDataProvidersAsReferenced(Clazz $class, Method $method)
    {
        if (preg_match('/@dataProvider\s+' . self::WORD_REGEX . '/', $method->getNode()->children['docComment'] ?? '', $match)) {
            $dataProviderName = $match[1];
            if ($class->hasMethodWithName($this->code_base, $dataProviderName)) {
                $class->getMethodByName($this->code_base, $dataProviderName)->addReference($this->context);
            }
        }
    }
    protected static function isTestCase(Method $method) : bool
    {
        if (!$method->isPublic()) {
            return false;
        }
        if (preg_match('@^test@i', $method->getName())) {
            return true;
        }
        if (preg_match('/@test\b/', $method->getNode()->children['docComment'] ?? '')) {
            return true;
        }
        return false;
    }

    public static function init()
    {
        $fqsen = FullyQualifiedClassName::make('\\PHPUnit\Framework', 'TestCase');
        self::$phpunit_test_case_fqsen = $fqsen;
        self::$phpunit_test_case_type = $fqsen->asType();
    }
}
PHPUnitNotDeadPluginVisitor::init();

// Every plugin needs to return an instance of itself at the
// end of the file in which its defined.
return new PHPUnitNotDeadCodePlugin();
<?php declare(strict_types=1);

use Phan\CodeBase;
use Phan\Config;
use Phan\Language\Context;
use Phan\PluginV2;
use Phan\PluginV2\AfterAnalyzeFileCapability;
use Phan\PluginV2\BeforeAnalyzeFileCapability;
use Phan\PluginV2\FinalizeProcessCapability;
use ast\Node;

/**
 * This plugin invokes the equivalent of `php --no-php-ini --syntax-check $analyzed_file_path`.
 *
 * php-ast reports syntax errors, but does not report all **semantic** errors that `php --syntax-check` would detect.
 *
 * Note that loading PHP modules would slow down analysis, so this plugin adds `--no-php-ini`.
 *
 * NOTE: This may not work in languages other than english.
 * NOTE: .phan/config.php can contain a config to override the PHP binary/binaries used
 *      This can replace the default binary (PHP_BINARY) with an array of absolute path or program names(in $PATH)
 *       E.g. have 'plugin_config' => ['php_native_syntax_check_binaries' => ['php72', 'php70', 'php56']]
 * Note: This may cause Phan to take over twice as long. This is recommended for use with `--processes N`.
 */
class InvokePHPNativeSyntaxCheckPlugin extends PluginV2 implements
    AfterAnalyzeFileCapability,
    BeforeAnalyzeFileCapability,
    FinalizeProcessCapability
{
    const LINE_NUMBER_REGEX = "@ on line ([1-9][0-9]*)$@";
    const STDIN_FILENAME_REGEX = "@ in (Standard input code|-)@";

    /** @var array<int,InvokeExecutionPromise> */
    private $processes = [];

    /**
     * TODO: Disable in LSP mode?
     *
     * @param CodeBase $code_base @phan-unused-param
     * The code base in which the node exists
     *
     * @param Context $context
     * A context with the file name for $file_contents and the scope after analyzing $node.
     *
     * @param string $file_contents the unmodified file contents @phan-unused-param
     * @param Node $node the node @phan-unused-param
     * @return void
     * @override
     */
    public function beforeAnalyzeFile(
        CodeBase $code_base,
        Context $context,
        string $file_contents,
        Node $node
    ) {
        $php_binaries = (Config::getValue('plugin_config')['php_native_syntax_check_binaries'] ?? null) ?: [PHP_BINARY];

        foreach ($php_binaries as $binary) {
            $this->processes[] = new InvokeExecutionPromise($binary, $file_contents, $context);
        }
    }

    /**
     * TODO: Disable in LSP mode?
     *
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context @phan-unused-param
     * A context with the file name for $file_contents and the scope after analyzing $node.
     *
     * @param string $file_contents the unmodified file contents @phan-unused-param
     * @param Node $node the node @phan-unused-param
     * @return void
     * @override
     */
    public function afterAnalyzeFile(
        CodeBase $code_base,
        Context $context,
        string $file_contents,
        Node $node
    ) {
        $configured_max_incomplete_processes = (int)(Config::getValue('plugin_config')['php_native_syntax_check_max_processes'] ?? 1) - 1;
        $max_incomplete_processes = max(0, $configured_max_incomplete_processes);
        $this->awaitIncompleteProcesses($code_base, $max_incomplete_processes);
    }

    /**
     * @suppress PhanPartialTypeMismatchArgument
     */
    private function awaitIncompleteProcesses(CodeBase $code_base, int $max_incomplete_processes)
    {
        foreach ($this->processes as $i => $process) {
            if (!$process->read()) {
                continue;
            }
            unset($this->processes[$i]);
            $this->handleError($code_base, $process);
        }
        $max_incomplete_processes = max(0, $max_incomplete_processes);
        while (count($this->processes) > $max_incomplete_processes) {
            $process = array_pop($this->processes);
            unset($this->processes[$i]);
            $process->blockingRead();
            $this->handleError($code_base, $process);
        }
    }

    /**
     * @override
     */
    public function finalizeProcess(CodeBase $code_base)
    {
        $this->awaitIncompleteProcesses($code_base, 0);
    }

    /**
     * @return void
     */
    private function handleError(CodeBase $code_base, InvokeExecutionPromise $process)
    {
        $check_error_message = $process->getError();
        if (!is_string($check_error_message)) {
            return;
        }
        $context = $process->getContext();
        $binary = $process->getBinary();
        $lineno = 1;
        if (preg_match(self::LINE_NUMBER_REGEX, $check_error_message, $matches)) {
            $lineno = (int)$matches[1];
            $check_error_message = trim(preg_replace(self::LINE_NUMBER_REGEX, '', $check_error_message));
        }
        $check_error_message = preg_replace(self::STDIN_FILENAME_REGEX, '', $check_error_message);


        $this->emitIssue(
            $code_base,
            clone($context)->withLineNumberStart($lineno),
            'PhanNativePHPSyntaxCheckPlugin',
            'Saw error or notice for {FILE} --syntax-check: {DETAILS}',
            [
                $binary === PHP_BINARY ? 'php' : $binary,
                json_encode($check_error_message),

            ]
        );
    }
}

class InvokeExecutionPromise
{
    /** @var string path to the php binary invoked */
    private $binary;

    /** @var bool */
    private $done = false;

    /** @var resource */
    private $process;

    /** @var array{0:resource,1:resource,2:resource} */
    private $pipes;

    /** @var ?string */
    private $error = null;

    /** @var string */
    private $raw_stdout = '';

    /** @var Context */
    private $context;

    public function __construct(string $binary, string $file_contents, Context $context)
    {
        // TODO: Use symfony process
        // Note: We might have invalid utf-8, ensure that the streams are opened in binary mode.
        // I'm not sure if this is necessary.
        if (DIRECTORY_SEPARATOR === "\\") {
            $cmd = $binary . ' --syntax-check --no-php-ini';
            $abs_path = Config::projectPath($context->getFile());
            if (!file_exists($abs_path)) {
                $this->done = true;
                $this->error = "File does not exist";
                return;
            }

            // Possibly https://bugs.php.net/bug.php?id=51800
            // NOTE: Work around this by writing from the original file. This may not work as expected in LSP mode
            if (DIRECTORY_SEPARATOR === "\\") {
                $abs_path = str_replace("/", "\\", $abs_path);
            }

            $cmd .= ' < ' . escapeshellarg($abs_path);

            $descriptorspec = [
                1 => ['pipe', 'wb'],
            ];
            $this->binary = $binary;
            $process = proc_open($cmd, $descriptorspec, $pipes);
            if (!is_resource($process)) {
                $this->done = true;
                $this->error = "Failed to run proc_open in " . __METHOD__;
                return;
            }
            $this->process = $process;
        } else {
            $cmd = escapeshellarg($binary) . ' --syntax-check --no-php-ini';
            $descriptorspec = [
                ['pipe', 'rb'],
                ['pipe', 'wb'],
            ];
            $this->binary = $binary;
            $process = proc_open($cmd, $descriptorspec, $pipes);
            if (!is_resource($process)) {
                $this->done = true;
                $this->error = "Failed to run proc_open in " . __METHOD__;
                return;
            }
            $this->process = $process;

            self::streamPutContents($pipes[0], $file_contents);
        }
        $this->pipes = $pipes;

        if (!stream_set_blocking($pipes[1], false)) {
            $this->error = "unable to set read stdout to non-blocking";
        }
        $this->context = clone($context);
    }

    /**
     * @param resource $stream stream to write $file_contents to before fclose()
     * @param string $file_contents
     * @return void
     * See https://bugs.php.net/bug.php?id=39598
     * @suppress PhanPluginUnusedVariable loop variable
     */
    private static function streamPutContents($stream, string $file_contents)
    {
        try {
            while (strlen($file_contents) > 0) {
                $bytes_written = fwrite($stream, $file_contents);
                if ($bytes_written === false) {
                    error_log('failed to write in ' . __METHOD__);
                    return;
                }
                if ($bytes_written === 0) {
                    $read_streams = [];
                    $write_streams = [$stream];
                    $except_streams = [];
                    stream_select($read_streams, $write_streams, $except_streams, 0);
                    if (!$write_streams) {
                        usleep(1000);
                        // This is blocked?
                        continue;
                    }
                    // $stream is ready to be written to?
                    $bytes_written = fwrite($stream, $file_contents);
                    if (!$bytes_written) {
                        error_log('failed to write in ' . __METHOD__ . ' but the stream should be ready');
                        return;
                    }
                }
                if ($bytes_written > 0) {
                    $file_contents = \substr($file_contents, $bytes_written);
                }
            }
        } finally {
            fclose($stream);
        }
    }

    public function read() : bool
    {
        if ($this->done) {
            return true;
        }
        $stdout = $this->pipes[1];
        while (!feof($stdout)) {
            $bytes = fread($stdout, 4096);
            if (strlen($bytes) === 0) {
                break;
            }
            $this->raw_stdout .= $bytes;
        }
        if (!feof($stdout)) {
            return false;
        }
        fclose($stdout);

        $this->done = true;

        $exit_code = proc_close($this->process);
        if ($exit_code === 0) {
            $this->error = null;
            return true;
        }
        $output = str_replace("\r", "", trim($this->raw_stdout));
        $first_line = explode("\n", $output)[0];
        $this->error = $first_line;
        return true;
    }

    /**
     * @return void
     */
    public function blockingRead()
    {
        if ($this->done) {
            return;
        }
        if (!stream_set_blocking($this->pipes[1], true)) {
            throw new Error("Unable to make stdout blocking");
        }
        if (!$this->read()) {
            throw new Error("Failed to read");
        }
    }

    /**
     * @return ?string
     */
    public function getError()
    {
        if (!$this->done) {
            throw new RangeException("Called " . __METHOD__ . " too early");
        }
        return $this->error;
    }

    public function getContext() : Context
    {
        return $this->context;
    }

    public function getBinary() : string
    {
        return $this->binary;
    }
}

// Every plugin needs to return an instance of itself at the
// end of the file in which its defined.
return new InvokePHPNativeSyntaxCheckPlugin;
<?php declare(strict_types=1);

use Phan\AST\ContextNode;
use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\Element\Func;
use Phan\PluginV2;
use Phan\PluginV2\AnalyzeFunctionCallCapability;
use ast\Node;

/**
 * This plugin checks for invalid regexes in calls to preg_match. (And all of the other internal PCRE functions).
 *
 * This plugin performs this check by attempting to match the empty string,
 * then checking if PHP emitted a warning (Instead of failing to match)
 * (PHP doesn't have preg_validate())
 *
 * - getAnalyzeFunctionCallClosures
 *   This method returns a map from function/method FQSEN to closures that are called on invocations of those closures.
 */
class PregRegexCheckerPlugin extends PluginV2 implements AnalyzeFunctionCallCapability
{
    // Skip over analyzing regex keys that couldn't be resolved.
    // Don't try to convert values to PHP data (should be closures)
    const RESOLVE_REGEX_KEY_FLAGS = (ContextNode::RESOLVE_DEFAULT | ContextNode::RESOLVE_KEYS_SKIP_UNKNOWN_KEYS) &
        ~(ContextNode::RESOLVE_KEYS_SKIP_UNKNOWN_KEYS | ContextNode::RESOLVE_ARRAY_VALUES);


    /** @return void */
    private function analyzePattern(CodeBase $code_base, Context $context, Func $function, string $pattern)
    {
        /**
         * @suppress PhanParamSuspiciousOrder 100% deliberate use of varying regex and constant $subject for preg_match
         */
        $err = with_disabled_phan_error_handler(function () use ($pattern) {
            $old_error_reporting = error_reporting();
            \error_reporting(0);
            \ob_start();
            \error_clear_last();
            try {
                $result = @\preg_match($pattern, '');
                if ($result === false) {
                    $err = \error_get_last() ?? [];
                    var_export($err);
                    return $err;
                }
                return null;
            } finally {
                \ob_end_clean();
                \error_reporting($old_error_reporting);
            }
        });
        if ($err !== null) {
            // TODO: scan for 'at offset %d$' and print the corresponding section of the regex. Note: Have to remove delimiters and unescape characters within the delimiters.
            $this->emitIssue(
                $code_base,
                $context,
                'PhanPluginInvalidPregRegex',
                'Call to {FUNCTION} was passed an invalid regex {STRING_LITERAL}: {DETAILS}',
                [(string)$function->getFQSEN(), \var_export($pattern, true), \preg_replace('@^preg_match\(\): @', '', $err['message'] ?? 'unknown error')]
            );
            return;
        }
    }

    /**
     * @return array<string,\Closure>
     * @phan-return array<string, Closure(CodeBase,Context,Func,array):void>
     */
    public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array
    {
        /**
         * @return void
         */
        $preg_pattern_callback = function (
            CodeBase $code_base,
            Context $context,
            Func $function,
            array $args
        ) {
            if (\count($args) < 1) {
                return;
            }
            // TODO: Resolve global constants and class constants?
            $pattern = $args[0];
            if ($pattern instanceof Node) {
                $pattern = (new ContextNode($code_base, $context, $pattern))->getEquivalentPHPScalarValue();
            }
            if (\is_string($pattern)) {
                $this->analyzePattern($code_base, $context, $function, $pattern);
            }
        };

        /**
         * @return void
         */
        $preg_pattern_or_array_callback = function (
            CodeBase $code_base,
            Context $context,
            Func $function,
            array $args
        ) {
            if (\count($args) < 1) {
                return;
            }
            // TODO: Resolve global constants and class constants?
            $pattern = $args[0];
            if ($pattern instanceof Node) {
                $pattern = (new ContextNode($code_base, $context, $pattern))->getEquivalentPHPValue();
            }
            if (\is_string($pattern)) {
                $this->analyzePattern($code_base, $context, $function, $pattern);
                return;
            }
            if (\is_array($pattern)) {
                foreach ($pattern as $child_pattern) {
                    if (\is_string($child_pattern)) {
                        $this->analyzePattern($code_base, $context, $function, $child_pattern);
                    }
                }
                return;
            }
        };

        /**
         * @return void
         */
        $preg_replace_callback_array_callback = function (
            CodeBase $code_base,
            Context $context,
            Func $function,
            array $args
        ) {
            if (\count($args) < 1) {
                return;
            }
            // TODO: Resolve global constants and class constants?
            $pattern = $args[0];
            if ($pattern instanceof Node) {
                $pattern = (new ContextNode($code_base, $context, $pattern))->getEquivalentPHPValue(self::RESOLVE_REGEX_KEY_FLAGS);
            }
            if (\is_array($pattern)) {
                foreach ($pattern as $child_pattern => $_) {
                    if (\is_scalar($child_pattern)) {
                        $this->analyzePattern($code_base, $context, $function, (string)$child_pattern);
                    }
                }
                return;
            }
        };

        // TODO: Check that the callbacks have the right signatures in another PR?
        return [
            // call
            'preg_filter'                 => $preg_pattern_or_array_callback,
            'preg_grep'                   => $preg_pattern_callback,
            'preg_match'                  => $preg_pattern_callback,
            'preg_match_all'              => $preg_pattern_callback,
            'preg_replace_callback_array' => $preg_replace_callback_array_callback,
            'preg_replace_callback'       => $preg_pattern_or_array_callback,
            'preg_replace'                => $preg_pattern_or_array_callback,
            'preg_split'                  => $preg_pattern_callback,
        ];
    }
}

// Every plugin needs to return an instance of itself at the
// end of the file in which its defined.
return new PregRegexCheckerPlugin;
<?php declare(strict_types=1);

use Phan\AST\AnalysisVisitor;
use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Func;
use Phan\Language\Element\Method;
use Phan\Plugin;
use Phan\Plugin\PluginImplementation;
use ast\Node;

/**
 * @deprecated - Compare this with DemoPlugin.php to see how to upgrade a Plugin to PluginV2.
 *
 * This file demonstrates the old version of plugins for Phan. Plugins hook into
 * four events;
 *
 * - analyzeNode
 *   This method is called on every AST node from every
 *   file being analyzed
 *
 * - analyzeClass
 *   Once all classes have been parsed, this method will be
 *   called on every class that is found in the code base
 *
 * - analyzeMethod
 *   Once all methods are parsed, this method will be called
 *   on every method in the code base
 *
 * - analyzeFunction
 *   Once all functions have been parsed, this method will
 *   be called on every function in the code base.
 *
 * A plugin file must
 *
 * - Contain a class that inherits from \Phan\Plugin
 *
 * - End by returning an instance of that class.
 *
 * It is assumed without being checked that plugins aren't
 * mangling state within the passed code base or context.
 *
 * Note: When adding new plugins,
 * add them to the corresponding section of README.md
 */
class DemoLegacyPlugin extends PluginImplementation
{

    /**
     * @param CodeBase $code_base
     * The code base in which the node exists
     *
     * @param Context $context
     * The context in which the node exits. This is
     * the context inside the given node rather than
     * the context outside of the given node
     *
     * @param Node $node
     * The php-ast Node being analyzed.
     *
     * @param Node $node
     * The parent node of the given node (if one exists).
     *
     * @return void
     *
     * @override
     */
    public function analyzeNode(
        CodeBase $code_base,
        Context $context,
        Node $node,
        Node $unused_parent_node = null
    ) {
        // Invoke the `DemoLegacyNodeVisitor` (defined later in
        // this file) on the given node, allowing it to run
        // a method based on the kind of the given node.
        (new DemoLegacyNodeVisitor($code_base, $context, $this))(
            $node
        );
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the class exists
     *
     * @param Clazz $class
     * A class being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeClass(
        CodeBase $code_base,
        Clazz $class
    ) {
        // As an example, we test to see if the name of
        // the class is `Class`, and emit an issue explain that
        // the name is not allowed.
        // NOTE: Placeholders can be found in \Phan\Issue::uncolored_format_string_for_replace
        if ($class->getName() == 'Class') {
            $this->emitIssue(
                $code_base,
                $class->getContext(),
                'DemoPluginClassName',
                "Class {CLASS} cannot be called `Class`",
                [(string)$class->getFQSEN()]
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the method exists
     *
     * @param Method $method
     * A method being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeMethod(
        CodeBase $code_base,
        Method $method
    ) {
        // As an example, we test to see if the name of the
        // method is `function`, and emit an issue if it is.
        // NOTE: Placeholders can be found in \Phan\Issue::uncolored_format_string_for_replace
        if ($method->getName() == 'function') {
            $this->emitIssue(
                $code_base,
                $method->getContext(),
                'DemoPluginMethodName',
                "Method {METHOD} cannot be called `function`",
                [(string)$method->getFQSEN()]
            );
        }
    }

    /**
     * @param CodeBase $code_base
     * The code base in which the function exists
     *
     * @param Func $function
     * A function being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeFunction(
        CodeBase $code_base,
        Func $function
    ) {
        // As an example, we test to see if the name of the
        // function is `function`, and emit an issue if it is.
        if ($function->getName() == 'function') {
            $this->emitIssue(
                $code_base,
                $function->getContext(),
                'DemoPluginFunctionName',
                "Function {FUNCTION} cannot be called `function`",
                [(string)$function->getFQSEN()]
            );
        }
    }
}

/**
 * When __invoke on this class is called with a node, a method
 * will be dispatched based on the `kind` of the given node.
 *
 * Visitors such as this are useful for defining lots of different
 * checks on a node based on its kind.
 */
class DemoLegacyNodeVisitor extends AnalysisVisitor
{

    /** @var Plugin */
    private $plugin;

    public function __construct(
        CodeBase $code_base,
        Context $context,
        Plugin $plugin
    ) {
        // After constructing on parent, `$code_base` and
        // `$context` will be available as protected properties
        // `$this->code_base` and `$this->context`.
        parent::__construct($code_base, $context);

        // We take the plugin so that we can call
        // `$this->plugin->emitIssue(...)` on it to emit issues
        // to the user.
        $this->plugin = $plugin;
    }

    /**
     * Default visitor that does nothing
     *
     * @param Node $unused_node
     * A node to analyze
     *
     * @return void
     *
     * @override
     */
    public function visit(Node $unused_node)
    {
        // This method will be called on all nodes for which
        // there is no implementation of it's kind visitor.
        //
        // To see what kinds of nodes are passing through here,
        // you can run `Debug::printNode($unused_node)`.

        // Debug::printNode($unused_node);
    }

    /**
     * @param Node $node
     * A node to analyze
     *
     * @return void
     *
     * @override
     */
    public function visitInstanceof(Node $node)
    {
        // Debug::printNode($node);

        $class_name = $node->children['class']->children['name'] ?? null;

        // If we can't figure out the name of the class,  don't
        // bother continuing.
        if (empty($class_name)) {
            return;
        }

        // As an example, enforce that we cannot call
        // instanceof against 'object'.
        if ($class_name == 'object') {
            $this->plugin->emitIssue(
                $this->code_base,
                $this->context,
                'PhanPluginInstanceOfObject',
                "Cannot call instanceof against `object`"
            );
        }
    }
}

// Every plugin needs to return an instance of itself at the
// end of the file in which its defined.
/** @suppress PhanDeprecatedClass - Only using a closure so that I can suppress Phan issue types */
return (function () : DemoLegacyPlugin {
    return new DemoLegacyPlugin();
})();
<?php declare(strict_types=1);

use Phan\AST\ContextNode;
use Phan\Exception\IssueException;
use Phan\Exception\NodeException;
use Phan\Issue;
use Phan\PluginV2;
use Phan\PluginV2\PostAnalyzeNodeCapability;
use Phan\PluginV2\PluginAwarePostAnalysisVisitor;
use ast\Node;

/**
 * Checks for duplicate/equivalent array keys and case statements, as well as arrays mixing `key => value, with `value,`.
 *
 * @see DollarDollarPlugin for generic plugin documentation.
 */
class DuplicateArrayKeyPlugin extends PluginV2 implements PostAnalyzeNodeCapability
{
    /**
     * @return string - name of PluginAwarePostAnalysisVisitor subclass
     * @override
     */
    public static function getPostAnalyzeNodeVisitorClassName() : string
    {
        return DuplicateArrayKeyVisitor::class;
    }
}

/**
 * This class has visitArray called on all array literals in files to check for potential problems with keys.
 *
 * When __invoke on this class is called with a node, a method
 * will be dispatched based on the `kind` of the given node.
 *
 * Visitors such as this are useful for defining lots of different
 * checks on a node based on its kind.
 */
class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
{
    // Do not define the visit() method unless a plugin has code and needs to visit most/all node types.

    /**
     * @param Node $node
     * A switch statement's case statement(AST_SWITCH_LIST) node to analyze
     *
     * @return void
     *
     * @override
     */
    public function visitSwitchList(Node $node)
    {
        $children = $node->children;
        if (count($children) <= 1) {
            // This plugin will never emit errors if there are 0 or 1 elements.
            return;
        }

        $case_constant_set = [];
        foreach ($children as $case_node) {
            $case_cond = $case_node->children['cond'];
            if ($case_cond === null) {
                continue;  // This is `default:`. php --syntax-check already checks for duplicates.
            }
            // Skip array entries without literal keys. (Do it before resolving the key value)
            $case_cond = $this->tryToResolveKey($case_cond);

            if (!is_scalar($case_cond)) {
                // Skip non-literal keys.
                continue;
            }
            if (isset($case_constant_set[$case_cond])) {
                $normalized_case_cond = self::normalizeKey($case_cond);
                $this->emitPluginIssue(
                    $this->code_base,
                    clone($this->context)->withLineNumberStart($case_node->lineno),
                    'PhanPluginDuplicateSwitchCase',
                    "Duplicate/Equivalent switch case({STRING_LITERAL}) detected in switch statement - the later entry will be ignored.",
                    [(string)$normalized_case_cond],
                    Issue::SEVERITY_NORMAL,
                    Issue::REMEDIATION_A,
                    15071
                );
            }
            $case_constant_set[$case_cond] = true;
        }
    }

    /**
     * @param Node $node
     * An array literal(AST_ARRAY) node to analyze
     *
     * @return void
     *
     * @override
     */
    public function visitArray(Node $node)
    {
        $children = $node->children;
        if (count($children) <= 1) {
            // This plugin will never emit errors if there are 0 or 1 elements.
            return;
        }

        $hasEntryWithoutKey = false;
        $keySet = [];
        foreach ($children as $entry) {
            if ($entry === null) {
                continue;  // Triggered by code such as `list(, $a) = $expr`. In php 7.1, the array and list() syntax was unified.
            }
            $key = $entry->children['key'];
            // Skip array entries without literal keys. (Do it before resolving the key value)
            if ($key === null) {
                $hasEntryWithoutKey = true;
                continue;
            }
            $key = $this->tryToResolveKey($key);

            if (!is_scalar($key)) {
                // Skip non-literal keys.
                continue;
            }
            if (isset($keySet[$key])) {
                $normalized_key = self::normalizeKey($key);
                $this->emitPluginIssue(
                    $this->code_base,
                    clone($this->context)->withLineNumberStart($entry->lineno ?? $node->lineno),
                    'PhanPluginDuplicateArrayKey',
                    "Duplicate/Equivalent array key value({STRING_LITERAL}) detected in array - the earlier entry will be ignored.",
                    [(string)$normalized_key],
                    Issue::SEVERITY_NORMAL,
                    Issue::REMEDIATION_A,
                    15071
                );
            }
            $keySet[$key] = true;
        }
        if ($hasEntryWithoutKey && count($keySet) > 0) {
            // This is probably a typo in most codebases. (e.g. ['foo' => 'bar', 'baz'])
            // In phan, InternalFunctionSignatureMap.php does this deliberately with the first parameter being the return type.
            $this->emit(
                'PhanPluginMixedKeyNoKey',
                "Should not mix array entries of the form [key => value,] with entries of the form [value,].",
                [],
                Issue::SEVERITY_NORMAL,
                Issue::REMEDIATION_A,
                15071
            );
        }
    }

    /**
     * @param int|string|float|array|Node $key (not actually an array)
     * @return int|string|float|Node|array - If possible, converted to a scalar.
     */
    private function tryToResolveKey($key)
    {
        if (!($key instanceof ast\Node)) {
            return $key;
        }
        $kind = $key->kind;
        if (!in_array($kind, [\ast\AST_CLASS_CONST, \ast\AST_CONST, \ast\AST_MAGIC_CONST], true)) {
            return $key;
        }
        // if key is constant, take it in account
        $context_node = new ContextNode($this->code_base, $this->context, $key);
        try {
            if ($kind === \ast\AST_CLASS_CONST) {
                $key = $context_node->getClassConst()->getNodeForValue();
            } elseif ($kind === \ast\AST_CONST) {
                $key = $context_node->getConst()->getNodeForValue();
            } else {
                $key = $context_node->getValueForMagicConst();
            }
            if ($key === null) {
                $key = '';
            }
        } catch (IssueException $e) {
            // This is redundant, but do it anyway
            Issue::maybeEmitInstance(
                $this->code_base,
                $this->context,
                $e->getIssueInstance()
            );
        } catch (NodeException $e) {
            // E.g. Can't figure out constant class in node
            // (ignore)
        }
        return $key;
    }

    /**
     * Converts a key to the value it would be if used as an array key.
     * E.g. 0, 0.5, and "0" all become the same value(0) when used as an array key.
     *
     * @param int|string|float $key - The array key literal to be normalized.
     * @return string - The normalized representation.
     */
    private static function normalizeKey($key) : string
    {
        $tmp = [$key => true];
        return var_export(key($tmp), true);
    }
}

// Every plugin needs to return an instance of itself at the
// end of the file in which its defined.
return new DuplicateArrayKeyPlugin();
<?php declare(strict_types=1);
// .phan/plugins/NonBoolBranchPlugin.php

use Phan\AST\UnionTypeVisitor;
use Phan\Language\Context;
use Phan\PluginV2;
use Phan\PluginV2\PostAnalyzeNodeCapability;
use Phan\PluginV2\PluginAwarePostAnalysisVisitor;
use ast\Node;

class NonBoolBranchPlugin extends PluginV2 implements PostAnalyzeNodeCapability
{
    /**
     * @return string - name of PluginAwarePostAnalysisVisitor subclass
     *
     * @override
     */
    public static function getPostAnalyzeNodeVisitorClassName() : string
    {
        return NonBoolBranchVisitor::class;
    }
}

class NonBoolBranchVisitor extends PluginAwarePostAnalysisVisitor
{
    // A plugin's visitors should not override visit() unless they need to.

    /** @override */
    public function visitIf(Node $node) : Context
    {
        // Here, we visit the group of if/elseif/else instead of the individuals (visitIfElem)
        // so that we have the Union types of the variables **before** the PreOrderAnalysisVisitor makes inferences
        foreach ($node->children as $if_node) {
            $condition = $if_node->children['cond'];

            // dig nodes to avoid NOT('!') operator's converting its value to boolean type
            // Also, use right hand side of $x = (expr)
            while (($condition instanceof Node) && (
                ($condition->flags === ast\flags\UNARY_BOOL_NOT && $condition->kind === ast\AST_UNARY_OP)
                || (\in_array($condition->kind, [\ast\AST_ASSIGN, \ast\AST_ASSIGN_REF], true)))
            ) {
                $condition = $condition->children['expr'];
            }

            if ($condition === null) {
                // $condition === null will be appeared in else-clause, then avoid them
                continue;
            }

            if ($condition instanceof Node) {
                $this->context = $this->context->withLineNumberStart($condition->lineno);
            }
            // evaluate the type of conditional expression
            $union_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $condition);
            if (!$union_type->isExclusivelyBoolTypes()) {
                $this->emit(
                    'PhanPluginNonBoolBranch',
                    'Non bool value of type {TYPE} evaluated in if clause',
                    [(string)$union_type]
                );
            }
        }
        return $this->context;
    }
}

return new NonBoolBranchPlugin;
<?php declare(strict_types=1);
// .phan/plugins/InvalidVariableIssetPlugin.php

use Phan\Language\Context;
use Phan\Language\Element\Variable;
use Phan\PluginV2;
use Phan\PluginV2\PostAnalyzeNodeCapability;
use Phan\PluginV2\PluginAwarePostAnalysisVisitor;
use ast\Node;

class InvalidVariableIssetPlugin extends PluginV2 implements PostAnalyzeNodeCapability
{

    /**
     * @return string - name of PluginAwarePostAnalysisVisitor subclass
     *
     * @override
     */
    public static function getPostAnalyzeNodeVisitorClassName() : string
    {
        return InvalidVariableIssetVisitor::class;
    }
}

class InvalidVariableIssetVisitor extends PluginAwarePostAnalysisVisitor
{

    /** define classes to parse */
    const CLASSES = [
        ast\AST_STATIC_CALL,
        ast\AST_STATIC_PROP,
    ];

    /** define expression to parse */
    const EXPRESSIONS = [
        ast\AST_CALL,
        ast\AST_DIM,
        ast\AST_INSTANCEOF,
        ast\AST_METHOD_CALL,
        ast\AST_PROP,
    ];

    // A plugin's visitors should not override visit() unless they need to.

    /** @override */
    public function visitIsset(Node $node) : Context
    {
        $argument = $node->children['var'];
        $variable = $argument;

        // get variable name from argument
        while (!isset($variable->children['name'])) {
            if (in_array($variable->kind, self::EXPRESSIONS, true)) {
                $variable = $variable->children['expr'];
            } elseif (in_array($variable->kind, self::CLASSES, true)) {
                $variable = $variable->children['class'];
            }
        }
        $name = $variable->children['name'];

        // emit issue if name is not declared
        // Check for edge cases such as isset($$var)
        if (is_string($name) && $name) {
            if (!Variable::isHardcodedVariableInScopeWithName($name, $this->context->isInGlobalScope()) &&
                    !$this->context->getScope()->hasVariableWithName($name)) {
                $this->emit(
                    'PhanPluginUndeclaredVariableIsset',
                    'undeclared variable ${VARIABLE} in isset()',
                    [$name]
                );
            }
        } elseif ($argument->kind !== ast\AST_VAR) {
            // emit issue if argument is not array access
            $this->emit(
                'PhanPluginInvalidVariableIsset',
                "non array/property access in isset()",
                []
            );
        } elseif (!is_string($name)) {
            // emit issue if argument is not array access
            $this->emit(
                'PhanPluginComplexVariableInIsset',
                "Unanalyzable complex variable expression in isset",
                []
            );
        }
        return $this->context;
    }
}

return new InvalidVariableIssetPlugin;
<?php declare(strict_types=1);

use Phan\CodeBase;
use Phan\Language\Element\AddressableElement;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Func;
use Phan\Language\Element\Method;
use Phan\Language\Element\Property;
use Phan\PluginV2;
use Phan\PluginV2\AnalyzeClassCapability;
use Phan\PluginV2\AnalyzeFunctionCapability;
use Phan\PluginV2\AnalyzePropertyCapability;
use Phan\PluginV2\AnalyzeMethodCapability;
use Phan\PluginV2\FinalizeProcessCapability;

/**
 * Check for unused (at)suppress annotations.
 *
 * NOTE! This plugin only produces correct results when Phan
 *       is run on a single processor (via the `-j1` flag).
 */
class UnusedSuppressionPlugin extends PluginV2 implements
    AnalyzeClassCapability,
    AnalyzeFunctionCapability,
    AnalyzeMethodCapability,
    AnalyzePropertyCapability,
    FinalizeProcessCapability
{

    /**
     * @var AddressableElement[] - Analysis is postponed until finalizeProcess.
     * Issues may have been emitted after `$this->analyze*()` were called,
     * which is why those methods postpone the check until analysis is finished.
     *
     * Also, looping over all elements again would be slow.
     *
     * These are currently unique, even when quick_mode is false.
     */
    private $elements_for_postponed_analysis = [];

    /**
     * @param CodeBase $code_base
     * The code base in which the function exists
     *
     * @param AddressableElement $element
     * Any element such as function, method, class
     * (which has an FQSEN)
     *
     * @return void
     */
    private function analyzeAddressableElement(
        CodeBase $code_base,
        AddressableElement $element
    ) {
        // Get the set of suppressed issues on the element
        $suppress_issue_list =
            $element->getSuppressIssueList();

        if (\array_key_exists('UnusedSuppression', $suppress_issue_list)) {
            // The element's doc comment is suppressing everything emitted by this plugin.
            return;
        }

        // Check to see if any are unused
        foreach ($suppress_issue_list as $issue_type => $use_count) {
            if (0 === $use_count) {
                $this->emitIssue(
                    $code_base,
                    $element->getContext(),
                    'UnusedSuppression',
                    "Element {FUNCTIONLIKE} suppresses issue {ISSUETYPE} but does not use it",
                    [(string)$element->getFQSEN(), $issue_type]
                );
            }
        }
    }

    /**
     * @param CodeBase $unused_code_base
     * The code base in which the class exists
     *
     * @param Clazz $class
     * A class being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeClass(
        CodeBase $unused_code_base,
        Clazz $class
    ) {
        $this->elements_for_postponed_analysis[] = $class;
    }

    /**
     * @param CodeBase $unused_code_base
     * The code base in which the method exists
     *
     * @param Method $method
     * A method being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeMethod(
        CodeBase $unused_code_base,
        Method $method
    ) {

        // Ignore methods inherited by subclasses
        if ($method->getFQSEN() !== $method->getDefiningFQSEN()) {
            return;
        }

        $this->elements_for_postponed_analysis[] = $method;
    }

    /**
     * @param CodeBase $unused_code_base
     * The code base in which the function exists
     *
     * @param Func $function
     * A function being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeFunction(
        CodeBase $unused_code_base,
        Func $function
    ) {
        $this->elements_for_postponed_analysis[] = $function;
    }

    /**
     * @param CodeBase $unused_code_base
     * The code base in which the function exists
     *
     * @param Property $property
     * A property being analyzed
     *
     * @return void
     *
     * @override
     */
    public function analyzeProperty(
        CodeBase $unused_code_base,
        Property $property
    ) {
        $this->elements_for_postponed_analysis[] = $property;
    }

    /**
     * NOTE! This plugin only produces correct results when Phan
     *       is run on a single processor (via the `-j1` flag).
     *       Putting this hook in finalizeProcess() just minimizes the incorrect result counts.
     * @override
     */
    public function finalizeProcess(CodeBase $code_base)
    {
        foreach ($this->elements_for_postponed_analysis as $element) {
            $this->analyzeAddressableElement($code_base, $element);
        }
    }
}

// Every plugin needs to return an instance of itself at the
// end of the file in which its defined.
return new UnusedSuppressionPlugin;
<?php declare(strict_types=1);

use Phan\AST\UnionTypeVisitor;
use Phan\Language\Context;
use Phan\PluginV2;
use Phan\PluginV2\PostAnalyzeNodeCapability;
use Phan\PluginV2\PluginAwarePostAnalysisVisitor;
use ast\Node;

class NumericalComparisonPlugin extends PluginV2 implements PostAnalyzeNodeCapability
{

    /**
     * @return string - name of PluginAwarePostAnalysisVisitor subclass
     *
     * @override
     */
    public static function getPostAnalyzeNodeVisitorClassName() : string
    {
        return NumericalComparisonVisitor::class;
    }
}

class NumericalComparisonVisitor extends PluginAwarePostAnalysisVisitor
{
    /** define equal operator list */
    const BINARY_EQUAL_OPERATORS = [
        ast\flags\BINARY_IS_EQUAL,
        ast\flags\BINARY_IS_NOT_EQUAL,
    ];

    /** define identical operator list */
    const BINARY_IDENTICAL_OPERATORS = [
        ast\flags\BINARY_IS_IDENTICAL,
        ast\flags\BINARY_IS_NOT_IDENTICAL,
    ];

    // A plugin's visitors should not override visit() unless they need to.

    /**
     * @override
     */
    public function visitBinaryop(Node $node) : Context
    {
        // get the types of left and right values
        $left_node = $node->children['left'];
        $left_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $left_node);
        $right_node = $node->children['right'];
        $right_type = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $right_node);

        // non numerical values are not allowed in the operator equal(==, !=)
        if (in_array($node->flags, self::BINARY_EQUAL_OPERATORS)) {
            if (!($this->isNumericalType($left_type->serialize())) &
                !($this->isNumericalType($right_type->serialize()))
            ) {
                $this->emit(
                    'PhanPluginNumericalComparison',
                    "non numerical values compared by the operators '==' or '!=='",
                    []
                );
            }
            // numerical values are not allowed in the operator identical('===', '!==')
        } elseif (in_array($node->flags, self::BINARY_IDENTICAL_OPERATORS)) {
            if ($this->isNumericalType($left_type->serialize()) |
                $this->isNumericalType($right_type->serialize())
            ) {
                // TODO: different name for this issue type?
                $this->emit(
                    'PhanPluginNumericalComparison',
                    "numerical values compared by the operators '===' or '!=='",
                    []
                );
            }
        }
        return $this->context;
    }

    /**
     * Judge the argument is 'int', 'float' or not
     *
     * @param string $type serialized UnionType string
     * @return bool argument string indicates numerical type or not
     */
    private function isNumericalType(string $type) : bool
    {
        return $type === 'int' || $type === 'float';
    }
}

return new NumericalComparisonPlugin;
��	�t����s{]ȗ<GBMB