Update the Composer dependencies
authorAlexander Ebert <ebert@woltlab.com>
Thu, 20 Jun 2024 10:48:35 +0000 (12:48 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 20 Jun 2024 10:48:35 +0000 (12:48 +0200)
246 files changed:
wcfsetup/install/files/lib/system/api/composer.json
wcfsetup/install/files/lib/system/api/composer.lock
wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php
wcfsetup/install/files/lib/system/api/composer/autoload_psr4.php
wcfsetup/install/files/lib/system/api/composer/autoload_static.php
wcfsetup/install/files/lib/system/api/composer/installed.json
wcfsetup/install/files/lib/system/api/composer/installed.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/composer.json
wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/PHPStan/Extension/ApiAndInternalAnnotationCheck.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/PHPStan/Extension/TreeMapperPHPStanExtension.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/Psalm/Plugin/ArgumentsMapperPsalmPlugin.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/CacheCompiler.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/CompiledPhpFileCache.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/HasArguments.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/MixedValueCacheCompiler.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/PhpCacheFile.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/FileSystemCache.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/FileWatchingCache.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/KeySanitizerCache.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Warmup/RecursiveCacheWarmupService.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/AttributeDefinition.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Attributes.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/AttributesContainer.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/ClassDefinition.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/ExtendTagTypeError.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/InvalidExtendTagClassName.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/InvalidExtendTagType.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/InvalidTypeAliasImportClass.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/InvalidTypeAliasImportClassType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/SeveralExtendTagsFound.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/TypesDoNotMatch.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/UnknownTypeAliasImport.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/FunctionDefinition.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/FunctionObject.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/MethodDefinition.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Methods.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/NativeAttributes.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/ParameterDefinition.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Parameters.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Properties.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/PropertyDefinition.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/AttributesRepository.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CacheClassDefinitionRepository.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CacheFunctionDefinitionRepository.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/ClassDefinitionCompiler.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/FunctionDefinitionCompiler.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/MethodDefinitionCompiler.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/ParameterDefinitionCompiler.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/PropertyDefinitionCompiler.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/TypeCompiler.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/ClassDefinitionRepository.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/NativeAttributesRepository.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionFunctionDefinitionRepository.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionMethodDefinitionBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionParameterDefinitionBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionPropertyDefinitionBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionTypeResolver.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Library/Container.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/ArgumentsMapperError.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringArgumentsMapping.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringMapping.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Argument.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Arguments.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/ArgumentsValues.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Constructor.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/CannotInstantiateObject.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorClassTypeParameter.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorMethodWithAttributeReturnType.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorReturnType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/MissingConstructorClassTypeParameter.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/ObjectBuildersCollision.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/CacheObjectBuilderFactory.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/DateTimeZoneObjectBuilderFactory.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/ReflectionObjectBuilderFactory.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/FilteredObjectBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/FunctionObjectBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/MethodArguments.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/NativeConstructorObjectBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/ReflectionObjectBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/FilteredObjectNodeBuilder.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NativeClassNodeBuilder.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ObjectImplementations.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/TreeNode.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/UnionNodeBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/CannotInferFinalClass.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InterfaceHasBothConstructorAndInfer.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/MissingObjectImplementationRegistration.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/UnresolvableShellType.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/DefaultMessage.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/MessageBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/UserlandError.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Shell.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeArgumentsMapper.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeTreeMapper.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/MapperBuilder.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/ArrayNormalizer.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/AsTransformer.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Exception/KeyTransformerHasTooManyParameters.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Exception/KeyTransformerParameterInvalidType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Exception/TransformerHasInvalidCallableParameter.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Exception/TransformerHasNoParameter.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Exception/TransformerHasTooManyParameters.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Format.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Formatter/Exception/CannotFormatInvalidTypeToJson.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Formatter/JsonFormatter.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Formatter/StreamFormatter.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/JsonNormalizer.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/StreamNormalizer.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/KeyTransformersHandler.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/RecursiveTransformer.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/ValueTransformersHandler.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/CombiningType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/GenericType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/ExtendTagTypeError.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidExtendTagClassName.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidExtendTagType.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/MissingGenerics.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/SeveralExtendTagsFound.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayClosingBracketMissing.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayInvalidUnsealedType.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayUnexpectedTokenAfterSealedType.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayWithoutElementsWithSealedType.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Factory/LexingTypeParserFactory.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Factory/Specifications/AliasSpecification.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Factory/Specifications/ClassContextSpecification.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Factory/Specifications/GenericCheckerSpecification.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Factory/Specifications/TypeAliasAssignerSpecification.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Factory/Specifications/TypeParserSpecification.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/GenericCheckerParser.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/AdvancedClassLexer.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/AliasLexer.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/ClassContextLexer.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/NativeLexer.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/SpecificationsLexer.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/AdvancedClassNameToken.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/ArrayToken.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/ClassNameToken.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/IntegerToken.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/ObjectToken.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/TripleDotsToken.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/UnknownSymbolToken.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/VacantToken.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/TokensExtractor.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/TypeAliasLexer.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/LexingParser.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/ParserSymbols.php [deleted file]
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ArrayKeyType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/InterfaceType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IntersectionType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ListType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeClassType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ShapedArrayType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/UnionType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/UnresolvableType.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/Reflection/DocParser.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/Reflection/Reflection.php
wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/Reflection/TokenParser.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/README.md
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/composer.json
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/AbstractSerializer.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/CallbackStream.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/Exception/InvalidForwardedHeaderNameException.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/Exception/InvalidProxyAddressException.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/HeaderSecurity.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/RequestTrait.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/Response/HtmlResponse.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/Response/RedirectResponse.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/Response/TextResponse.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/Response/XmlResponse.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/Stream.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/UploadedFile.php
wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/UriFactory.php
wcfsetup/install/files/lib/system/api/paragonie/constant_time_encoding/README.md
wcfsetup/install/files/lib/system/api/paragonie/constant_time_encoding/src/Base32.php
wcfsetup/install/files/lib/system/api/paragonie/constant_time_encoding/src/Base64.php
wcfsetup/install/files/lib/system/api/paragonie/constant_time_encoding/src/Binary.php
wcfsetup/install/files/lib/system/api/paragonie/constant_time_encoding/src/Encoding.php
wcfsetup/install/files/lib/system/api/paragonie/constant_time_encoding/src/Hex.php
wcfsetup/install/files/lib/system/api/paragonie/constant_time_encoding/src/RFC4648.php
wcfsetup/install/files/lib/system/api/psr/http-factory/composer.json
wcfsetup/install/files/lib/system/api/psr/http-factory/src/UploadedFileFactoryInterface.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/CHANGELOG.md
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/README.md
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/composer.json
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/CSSList/CSSList.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/CSSList/Document.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/CSSList/KeyFrame.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/OutputFormat.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/OutputFormatter.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Parser.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Parsing/Anchor.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Parsing/ParserState.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Property/Charset.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Property/Import.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Rule/Rule.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/RuleSet/AtRuleSet.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/RuleSet/DeclarationBlock.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/RuleSet/RuleSet.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Settings.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Value/CSSFunction.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Value/CSSString.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Value/CalcFunction.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Value/Color.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Value/RuleValueList.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Value/Size.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Value/URL.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Value/Value.php
wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Value/ValueList.php
wcfsetup/install/files/lib/system/api/sebastian/diff/ChangeLog.md
wcfsetup/install/files/lib/system/api/sebastian/diff/LICENSE
wcfsetup/install/files/lib/system/api/sebastian/diff/composer.json
wcfsetup/install/files/lib/system/api/sebastian/diff/src/Differ.php
wcfsetup/install/files/lib/system/api/sebastian/diff/src/Exception/ConfigurationException.php
wcfsetup/install/files/lib/system/api/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php
wcfsetup/install/files/lib/system/api/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php
wcfsetup/install/files/lib/system/api/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php
wcfsetup/install/files/lib/system/api/sebastian/diff/src/Parser.php
wcfsetup/install/files/lib/system/api/symfony/css-selector/Node/ElementNode.php
wcfsetup/install/files/lib/system/api/symfony/css-selector/Node/SelectorNode.php
wcfsetup/install/files/lib/system/api/symfony/css-selector/Parser/Parser.php
wcfsetup/install/files/lib/system/api/symfony/css-selector/XPath/Translator.php
wcfsetup/install/files/lib/system/api/symfony/deprecation-contracts/composer.json
wcfsetup/install/files/lib/system/api/symfony/polyfill-php82/Php82.php
wcfsetup/install/files/lib/system/api/symfony/polyfill-php82/bootstrap.php
wcfsetup/install/files/lib/system/api/symfony/polyfill-php82/composer.json
wcfsetup/install/files/lib/system/api/symfony/polyfill-php83/Php83.php
wcfsetup/install/files/lib/system/api/symfony/polyfill-php83/README.md
wcfsetup/install/files/lib/system/api/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/api/symfony/polyfill-php83/bootstrap.php
wcfsetup/install/files/lib/system/api/symfony/polyfill-php83/bootstrap81.php
wcfsetup/install/files/lib/system/api/symfony/polyfill-php83/composer.json

index 5866a608acacb3a3c4b81a49720ee4d8611c3d9d..0bd94e0b67591289b8d78089d884607b1916e832 100644 (file)
         }
     },
     "require": {
-        "cuyz/valinor": "^1.8.2",
+        "cuyz/valinor": "^1.12.0",
         "dragonmantank/cron-expression": "^3.3.3",
         "erusev/parsedown": "^1.7.4",
         "ezyang/htmlpurifier": "^4.17",
         "guzzlehttp/guzzle": "^7.8.1",
         "guzzlehttp/psr7": "^2.6.2",
-        "laminas/laminas-diactoros": "^3.3.0",
+        "laminas/laminas-diactoros": "^3.3.1",
         "laminas/laminas-httphandlerrunner": "^2.10.0",
         "laminas/laminas-progressbar": "^2.13",
-        "paragonie/constant_time_encoding": "^2.6.3",
+        "paragonie/constant_time_encoding": "^2.7.0",
         "pelago/emogrifier": "^7.2.0",
         "psr/clock": "^1.0",
         "psr/event-dispatcher": "^1.0",
@@ -28,9 +28,9 @@
         "psr/http-server-middleware": "^1.0.2",
         "psr/log": "^3.0",
         "scssphp/scssphp": "^1.12.1",
-        "sebastian/diff": "^5.1.0",
-        "symfony/polyfill-php82": "^1.28.0",
-        "symfony/polyfill-php83": "^1.28",
+        "sebastian/diff": "^5.1.1",
+        "symfony/polyfill-php82": "^1.30.0",
+        "symfony/polyfill-php83": "^1.30",
         "willdurand/negotiation": "^3.1"
     },
     "replace": {
index 48a1249f1897692d6e2dae187551e5a5389b01a5..1de7f76c90332dac26982003c55b6da58140d70d 100644 (file)
@@ -4,37 +4,37 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "1a986b53b455f0e5e4a58126dc8364ce",
+    "content-hash": "f252359a254921f59dc57b78f2c04059",
     "packages": [
         {
             "name": "cuyz/valinor",
-            "version": "1.8.2",
+            "version": "1.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/CuyZ/Valinor.git",
-                "reference": "daf8206d11b1cb6b308ecd2eb6b65657d2248544"
+                "reference": "3bc40798a5ff64aee8a28509b73f7f84d5c66ac9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/CuyZ/Valinor/zipball/daf8206d11b1cb6b308ecd2eb6b65657d2248544",
-                "reference": "daf8206d11b1cb6b308ecd2eb6b65657d2248544",
+                "url": "https://api.github.com/repos/CuyZ/Valinor/zipball/3bc40798a5ff64aee8a28509b73f7f84d5c66ac9",
+                "reference": "3bc40798a5ff64aee8a28509b73f7f84d5c66ac9",
                 "shasum": ""
             },
             "require": {
                 "composer-runtime-api": "^2.0",
-                "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
+                "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
                 "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
             },
             "require-dev": {
                 "friendsofphp/php-cs-fixer": "^3.4",
-                "infection/infection": "^0.26",
+                "infection/infection": "^0.27",
                 "marcocesarato/php-conventional-changelog": "^1.12",
                 "mikey179/vfsstream": "^1.6.10",
                 "phpstan/phpstan": "^1.3",
                 "phpstan/phpstan-phpunit": "^1.0",
                 "phpstan/phpstan-strict-rules": "^1.0",
-                "phpunit/phpunit": "^9.5",
-                "rector/rector": "~0.17.0",
+                "phpunit/phpunit": "^10.5",
+                "rector/rector": "^1.0",
                 "vimeo/psalm": "^5.0"
             },
             "type": "library",
@@ -69,7 +69,7 @@
             ],
             "support": {
                 "issues": "https://github.com/CuyZ/Valinor/issues",
-                "source": "https://github.com/CuyZ/Valinor/tree/1.8.2"
+                "source": "https://github.com/CuyZ/Valinor/tree/1.12.0"
             },
             "funding": [
                 {
@@ -77,7 +77,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2024-01-08T20:31:48+00:00"
+            "time": "2024-04-04T16:42:55+00:00"
         },
         {
             "name": "dragonmantank/cron-expression",
         },
         {
             "name": "laminas/laminas-diactoros",
-            "version": "3.3.0",
+            "version": "3.3.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laminas/laminas-diactoros.git",
-                "reference": "4db52734837c60259c9b2d7caf08eef8f7f9b9ac"
+                "reference": "74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/4db52734837c60259c9b2d7caf08eef8f7f9b9ac",
-                "reference": "4db52734837c60259c9b2d7caf08eef8f7f9b9ac",
+                "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45",
+                "reference": "74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45",
                 "shasum": ""
             },
             "require": {
                 "http-interop/http-factory-tests": "^0.9.0",
                 "laminas/laminas-coding-standard": "~2.5.0",
                 "php-http/psr7-integration-tests": "^1.3",
-                "phpunit/phpunit": "^9.5.28",
+                "phpunit/phpunit": "^9.6.16",
                 "psalm/plugin-phpunit": "^0.18.4",
-                "vimeo/psalm": "^5.15.0"
+                "vimeo/psalm": "^5.22.1"
             },
             "type": "library",
             "extra": {
                     "type": "community_bridge"
                 }
             ],
-            "time": "2023-10-26T11:01:07+00:00"
+            "time": "2024-02-16T16:06:16+00:00"
         },
         {
             "name": "laminas/laminas-httphandlerrunner",
         },
         {
             "name": "paragonie/constant_time_encoding",
-            "version": "v2.6.3",
+            "version": "v2.7.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/paragonie/constant_time_encoding.git",
-                "reference": "58c3f47f650c94ec05a151692652a868995d2938"
+                "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938",
-                "reference": "58c3f47f650c94ec05a151692652a868995d2938",
+                "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105",
+                "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105",
                 "shasum": ""
             },
             "require": {
                 "issues": "https://github.com/paragonie/constant_time_encoding/issues",
                 "source": "https://github.com/paragonie/constant_time_encoding"
             },
-            "time": "2022-06-14T06:56:20+00:00"
+            "time": "2024-05-08T12:18:48+00:00"
         },
         {
             "name": "pelago/emogrifier",
         },
         {
             "name": "psr/http-factory",
-            "version": "1.0.2",
+            "version": "1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/http-factory.git",
-                "reference": "e616d01114759c4c489f93b099585439f795fe35"
+                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
-                "reference": "e616d01114759c4c489f93b099585439f795fe35",
+                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0.0",
+                "php": ">=7.1",
                 "psr/http-message": "^1.0 || ^2.0"
             },
             "type": "library",
                     "homepage": "https://www.php-fig.org/"
                 }
             ],
-            "description": "Common interfaces for PSR-7 HTTP message factories",
+            "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
             "keywords": [
                 "factory",
                 "http",
                 "response"
             ],
             "support": {
-                "source": "https://github.com/php-fig/http-factory/tree/1.0.2"
+                "source": "https://github.com/php-fig/http-factory"
             },
-            "time": "2023-04-10T20:10:41+00:00"
+            "time": "2024-04-15T12:06:14+00:00"
         },
         {
             "name": "psr/http-message",
         },
         {
             "name": "sabberworm/php-css-parser",
-            "version": "8.4.0",
+            "version": "v8.5.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
-                "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30"
+                "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
+                "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30",
-                "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30",
+                "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/4a3d572b0f8b28bb6fd016ae8bbfc445facef152",
+                "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.6.20"
             },
             "require-dev": {
-                "codacy/coverage": "^1.4",
-                "phpunit/phpunit": "^4.8.36"
+                "phpunit/phpunit": "^5.7.27"
             },
             "suggest": {
                 "ext-mbstring": "for parsing UTF-8 CSS"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "9.0.x-dev"
+                }
+            },
             "autoload": {
                 "psr-4": {
                     "Sabberworm\\CSS\\": "src/"
             "authors": [
                 {
                     "name": "Raphael Schweikert"
+                },
+                {
+                    "name": "Oliver Klee",
+                    "email": "github@oliverklee.de"
+                },
+                {
+                    "name": "Jake Hotson",
+                    "email": "jake.github@qzdesign.co.uk"
                 }
             ],
             "description": "Parser for CSS Files written in PHP",
                 "stylesheet"
             ],
             "support": {
-                "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues",
-                "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0"
+                "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
+                "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.5.1"
             },
-            "time": "2021-12-11T13:40:54+00:00"
+            "time": "2024-02-15T16:41:13+00:00"
         },
         {
             "name": "scssphp/scssphp",
         },
         {
             "name": "sebastian/diff",
-            "version": "5.1.0",
+            "version": "5.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/diff.git",
-                "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f"
+                "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
-                "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
+                "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
                 "shasum": ""
             },
             "require": {
             },
             "require-dev": {
                 "phpunit/phpunit": "^10.0",
-                "symfony/process": "^4.2 || ^5"
+                "symfony/process": "^6.4"
             },
             "type": "library",
             "extra": {
             "support": {
                 "issues": "https://github.com/sebastianbergmann/diff/issues",
                 "security": "https://github.com/sebastianbergmann/diff/security/policy",
-                "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0"
+                "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2023-12-22T10:55:06+00:00"
+            "time": "2024-03-02T07:15:17+00:00"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v6.4.0",
+            "version": "v6.4.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4"
+                "reference": "4b61b02fe15db48e3687ce1c45ea385d1780fe08"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/d036c6c0d0b09e24a14a35f8292146a658f986e4",
-                "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/4b61b02fe15db48e3687ce1c45ea385d1780fe08",
+                "reference": "4b61b02fe15db48e3687ce1c45ea385d1780fe08",
                 "shasum": ""
             },
             "require": {
             "description": "Converts CSS selectors to XPath expressions",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/css-selector/tree/v6.4.0"
+                "source": "https://github.com/symfony/css-selector/tree/v6.4.8"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-10-31T08:40:20+00:00"
+            "time": "2024-05-31T14:49:08+00:00"
         },
         {
             "name": "symfony/deprecation-contracts",
-            "version": "v3.4.0",
+            "version": "v3.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/deprecation-contracts.git",
-                "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
+                "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
-                "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
+                "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "3.4-dev"
+                    "dev-main": "3.5-dev"
                 },
                 "thanks": {
                     "name": "symfony/contracts",
             "description": "A generic function and convention to trigger deprecation notices",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-05-23T14:45:45+00:00"
+            "time": "2024-04-18T09:32:20+00:00"
         },
         {
             "name": "symfony/polyfill-php82",
-            "version": "v1.28.0",
+            "version": "v1.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php82.git",
-                "reference": "7716bea9c86776fb3362d6b52fe1fc9471056a49"
+                "reference": "77ff49780f56906788a88974867ed68bc49fae5b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/7716bea9c86776fb3362d6b52fe1fc9471056a49",
-                "reference": "7716bea9c86776fb3362d6b52fe1fc9471056a49",
+                "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/77ff49780f56906788a88974867ed68bc49fae5b",
+                "reference": "77ff49780f56906788a88974867ed68bc49fae5b",
                 "shasum": ""
             },
             "require": {
             },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "1.28-dev"
-                },
                 "thanks": {
                     "name": "symfony/polyfill",
                     "url": "https://github.com/symfony/polyfill"
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php82/tree/v1.28.0"
+                "source": "https://github.com/symfony/polyfill-php82/tree/v1.30.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-08-25T17:27:25+00:00"
+            "time": "2024-06-19T12:30:46+00:00"
         },
         {
             "name": "symfony/polyfill-php83",
-            "version": "v1.28.0",
+            "version": "v1.30.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php83.git",
-                "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11"
+                "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11",
-                "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11",
+                "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9",
+                "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1",
-                "symfony/polyfill-php80": "^1.14"
+                "php": ">=7.1"
             },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "1.28-dev"
-                },
                 "thanks": {
                     "name": "symfony/polyfill",
                     "url": "https://github.com/symfony/polyfill"
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0"
+                "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-08-16T06:22:46+00:00"
+            "time": "2024-06-19T12:35:24+00:00"
         },
         {
             "name": "webmozart/assert",
index 6ebd8e1f50f1e9a1be52ac00ccf268271097334f..858534267d460140fe959902e94157f9f8a97420 100644 (file)
@@ -19,11 +19,6 @@ return array(
     'Cron\\MinutesField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/MinutesField.php',
     'Cron\\MonthField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/MonthField.php',
     'CuyZ\\Valinor\\Cache\\ChainCache' => $vendorDir . '/cuyz/valinor/src/Cache/ChainCache.php',
-    'CuyZ\\Valinor\\Cache\\Compiled\\CacheCompiler' => $vendorDir . '/cuyz/valinor/src/Cache/Compiled/CacheCompiler.php',
-    'CuyZ\\Valinor\\Cache\\Compiled\\CompiledPhpFileCache' => $vendorDir . '/cuyz/valinor/src/Cache/Compiled/CompiledPhpFileCache.php',
-    'CuyZ\\Valinor\\Cache\\Compiled\\HasArguments' => $vendorDir . '/cuyz/valinor/src/Cache/Compiled/HasArguments.php',
-    'CuyZ\\Valinor\\Cache\\Compiled\\MixedValueCacheCompiler' => $vendorDir . '/cuyz/valinor/src/Cache/Compiled/MixedValueCacheCompiler.php',
-    'CuyZ\\Valinor\\Cache\\Compiled\\PhpCacheFile' => $vendorDir . '/cuyz/valinor/src/Cache/Compiled/PhpCacheFile.php',
     'CuyZ\\Valinor\\Cache\\Exception\\CacheDirectoryNotWritable' => $vendorDir . '/cuyz/valinor/src/Cache/Exception/CacheDirectoryNotWritable.php',
     'CuyZ\\Valinor\\Cache\\Exception\\CompiledPhpCacheFileNotWritten' => $vendorDir . '/cuyz/valinor/src/Cache/Exception/CompiledPhpCacheFileNotWritten.php',
     'CuyZ\\Valinor\\Cache\\Exception\\CorruptedCompiledPhpCacheFile' => $vendorDir . '/cuyz/valinor/src/Cache/Exception/CorruptedCompiledPhpCacheFile.php',
@@ -34,20 +29,22 @@ return array(
     'CuyZ\\Valinor\\Cache\\RuntimeCache' => $vendorDir . '/cuyz/valinor/src/Cache/RuntimeCache.php',
     'CuyZ\\Valinor\\Cache\\WarmupCache' => $vendorDir . '/cuyz/valinor/src/Cache/WarmupCache.php',
     'CuyZ\\Valinor\\Cache\\Warmup\\RecursiveCacheWarmupService' => $vendorDir . '/cuyz/valinor/src/Cache/Warmup/RecursiveCacheWarmupService.php',
+    'CuyZ\\Valinor\\Definition\\AttributeDefinition' => $vendorDir . '/cuyz/valinor/src/Definition/AttributeDefinition.php',
     'CuyZ\\Valinor\\Definition\\Attributes' => $vendorDir . '/cuyz/valinor/src/Definition/Attributes.php',
-    'CuyZ\\Valinor\\Definition\\AttributesContainer' => $vendorDir . '/cuyz/valinor/src/Definition/AttributesContainer.php',
     'CuyZ\\Valinor\\Definition\\ClassDefinition' => $vendorDir . '/cuyz/valinor/src/Definition/ClassDefinition.php',
     'CuyZ\\Valinor\\Definition\\Exception\\ClassTypeAliasesDuplication' => $vendorDir . '/cuyz/valinor/src/Definition/Exception/ClassTypeAliasesDuplication.php',
+    'CuyZ\\Valinor\\Definition\\Exception\\ExtendTagTypeError' => $vendorDir . '/cuyz/valinor/src/Definition/Exception/ExtendTagTypeError.php',
+    'CuyZ\\Valinor\\Definition\\Exception\\InvalidExtendTagClassName' => $vendorDir . '/cuyz/valinor/src/Definition/Exception/InvalidExtendTagClassName.php',
+    'CuyZ\\Valinor\\Definition\\Exception\\InvalidExtendTagType' => $vendorDir . '/cuyz/valinor/src/Definition/Exception/InvalidExtendTagType.php',
     'CuyZ\\Valinor\\Definition\\Exception\\InvalidTypeAliasImportClass' => $vendorDir . '/cuyz/valinor/src/Definition/Exception/InvalidTypeAliasImportClass.php',
     'CuyZ\\Valinor\\Definition\\Exception\\InvalidTypeAliasImportClassType' => $vendorDir . '/cuyz/valinor/src/Definition/Exception/InvalidTypeAliasImportClassType.php',
-    'CuyZ\\Valinor\\Definition\\Exception\\TypesDoNotMatch' => $vendorDir . '/cuyz/valinor/src/Definition/Exception/TypesDoNotMatch.php',
+    'CuyZ\\Valinor\\Definition\\Exception\\SeveralExtendTagsFound' => $vendorDir . '/cuyz/valinor/src/Definition/Exception/SeveralExtendTagsFound.php',
     'CuyZ\\Valinor\\Definition\\Exception\\UnknownTypeAliasImport' => $vendorDir . '/cuyz/valinor/src/Definition/Exception/UnknownTypeAliasImport.php',
     'CuyZ\\Valinor\\Definition\\FunctionDefinition' => $vendorDir . '/cuyz/valinor/src/Definition/FunctionDefinition.php',
     'CuyZ\\Valinor\\Definition\\FunctionObject' => $vendorDir . '/cuyz/valinor/src/Definition/FunctionObject.php',
     'CuyZ\\Valinor\\Definition\\FunctionsContainer' => $vendorDir . '/cuyz/valinor/src/Definition/FunctionsContainer.php',
     'CuyZ\\Valinor\\Definition\\MethodDefinition' => $vendorDir . '/cuyz/valinor/src/Definition/MethodDefinition.php',
     'CuyZ\\Valinor\\Definition\\Methods' => $vendorDir . '/cuyz/valinor/src/Definition/Methods.php',
-    'CuyZ\\Valinor\\Definition\\NativeAttributes' => $vendorDir . '/cuyz/valinor/src/Definition/NativeAttributes.php',
     'CuyZ\\Valinor\\Definition\\ParameterDefinition' => $vendorDir . '/cuyz/valinor/src/Definition/ParameterDefinition.php',
     'CuyZ\\Valinor\\Definition\\Parameters' => $vendorDir . '/cuyz/valinor/src/Definition/Parameters.php',
     'CuyZ\\Valinor\\Definition\\Properties' => $vendorDir . '/cuyz/valinor/src/Definition/Properties.php',
@@ -65,7 +62,7 @@ return array(
     'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\TypeCompiler' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/TypeCompiler.php',
     'CuyZ\\Valinor\\Definition\\Repository\\ClassDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/ClassDefinitionRepository.php',
     'CuyZ\\Valinor\\Definition\\Repository\\FunctionDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/FunctionDefinitionRepository.php',
-    'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\NativeAttributesRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Reflection/NativeAttributesRepository.php',
+    'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\ReflectionAttributesRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php',
     'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\ReflectionClassDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php',
     'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\ReflectionFunctionDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionFunctionDefinitionRepository.php',
     'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\ReflectionMethodDefinitionBuilder' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionMethodDefinitionBuilder.php',
@@ -78,16 +75,20 @@ return array(
     'CuyZ\\Valinor\\Mapper\\ArgumentsMapper' => $vendorDir . '/cuyz/valinor/src/Mapper/ArgumentsMapper.php',
     'CuyZ\\Valinor\\Mapper\\ArgumentsMapperError' => $vendorDir . '/cuyz/valinor/src/Mapper/ArgumentsMapperError.php',
     'CuyZ\\Valinor\\Mapper\\Exception\\InvalidMappingTypeSignature' => $vendorDir . '/cuyz/valinor/src/Mapper/Exception/InvalidMappingTypeSignature.php',
+    'CuyZ\\Valinor\\Mapper\\Exception\\TypeErrorDuringArgumentsMapping' => $vendorDir . '/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringArgumentsMapping.php',
+    'CuyZ\\Valinor\\Mapper\\Exception\\TypeErrorDuringMapping' => $vendorDir . '/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringMapping.php',
     'CuyZ\\Valinor\\Mapper\\MappingError' => $vendorDir . '/cuyz/valinor/src/Mapper/MappingError.php',
     'CuyZ\\Valinor\\Mapper\\Object\\Argument' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Argument.php',
     'CuyZ\\Valinor\\Mapper\\Object\\Arguments' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Arguments.php',
     'CuyZ\\Valinor\\Mapper\\Object\\ArgumentsValues' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/ArgumentsValues.php',
+    'CuyZ\\Valinor\\Mapper\\Object\\Constructor' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Constructor.php',
     'CuyZ\\Valinor\\Mapper\\Object\\DateTimeFormatConstructor' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/DateTimeFormatConstructor.php',
     'CuyZ\\Valinor\\Mapper\\Object\\DynamicConstructor' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/DynamicConstructor.php',
     'CuyZ\\Valinor\\Mapper\\Object\\Exception\\CannotFindObjectBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/CannotFindObjectBuilder.php',
     'CuyZ\\Valinor\\Mapper\\Object\\Exception\\CannotInstantiateObject' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/CannotInstantiateObject.php',
     'CuyZ\\Valinor\\Mapper\\Object\\Exception\\CannotParseToDateTime' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/CannotParseToDateTime.php',
     'CuyZ\\Valinor\\Mapper\\Object\\Exception\\InvalidConstructorClassTypeParameter' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorClassTypeParameter.php',
+    'CuyZ\\Valinor\\Mapper\\Object\\Exception\\InvalidConstructorMethodWithAttributeReturnType' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorMethodWithAttributeReturnType.php',
     'CuyZ\\Valinor\\Mapper\\Object\\Exception\\InvalidConstructorReturnType' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorReturnType.php',
     'CuyZ\\Valinor\\Mapper\\Object\\Exception\\InvalidSource' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/InvalidSource.php',
     'CuyZ\\Valinor\\Mapper\\Object\\Exception\\MissingConstructorClassTypeParameter' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/MissingConstructorClassTypeParameter.php',
@@ -130,11 +131,12 @@ return array(
     'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\CasterNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/CasterNodeBuilder.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\CasterProxyNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ErrorCatcherNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php',
+    'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\FilteredObjectNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/FilteredObjectNodeBuilder.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\InterfaceNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\IterableNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/IterableNodeBuilder.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ListNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ListNodeBuilder.php',
-    'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\NativeClassNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/NativeClassNodeBuilder.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\NodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/NodeBuilder.php',
+    'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\NullNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ObjectImplementations' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ObjectImplementations.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ObjectNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\RootNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php',
@@ -147,6 +149,7 @@ return array(
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotInferFinalClass' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotInferFinalClass.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotResolveObjectType' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveObjectType.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotResolveTypeFromUnion' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php',
+    'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InterfaceHasBothConstructorAndInfer' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InterfaceHasBothConstructorAndInfer.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidAbstractObjectName' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidAbstractObjectName.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidListKey' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidListKey.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidNodeHasNoMappedValue' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeHasNoMappedValue.php',
@@ -159,10 +162,13 @@ return array(
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ObjectImplementationCallbackError' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/ObjectImplementationCallbackError.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ObjectImplementationNotRegistered' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/ObjectImplementationNotRegistered.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ResolvedImplementationIsNotAccepted' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/ResolvedImplementationIsNotAccepted.php',
+    'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceIsNotNull' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceMustBeIterable' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceMustBeIterable.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceValueWasNotFilled' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php',
+    'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\TooManyResolvedTypesFromUnion' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\UnexpectedArrayKeysForClass' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/UnexpectedArrayKeysForClass.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\UnexpectedShapedArrayKeys' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/UnexpectedShapedArrayKeys.php',
+    'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\UnresolvableShellType' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/UnresolvableShellType.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Message\\DefaultMessage' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Message/DefaultMessage.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Message\\ErrorMessage' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Message/ErrorMessage.php',
     'CuyZ\\Valinor\\Mapper\\Tree\\Message\\Formatter\\AggregateMessageFormatter' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Message/Formatter/AggregateMessageFormatter.php',
@@ -185,6 +191,7 @@ return array(
     'CuyZ\\Valinor\\Mapper\\TypeTreeMapper' => $vendorDir . '/cuyz/valinor/src/Mapper/TypeTreeMapper.php',
     'CuyZ\\Valinor\\Mapper\\TypeTreeMapperError' => $vendorDir . '/cuyz/valinor/src/Mapper/TypeTreeMapperError.php',
     'CuyZ\\Valinor\\Normalizer\\ArrayNormalizer' => $vendorDir . '/cuyz/valinor/src/Normalizer/ArrayNormalizer.php',
+    'CuyZ\\Valinor\\Normalizer\\AsTransformer' => $vendorDir . '/cuyz/valinor/src/Normalizer/AsTransformer.php',
     'CuyZ\\Valinor\\Normalizer\\Exception\\CircularReferenceFoundDuringNormalization' => $vendorDir . '/cuyz/valinor/src/Normalizer/Exception/CircularReferenceFoundDuringNormalization.php',
     'CuyZ\\Valinor\\Normalizer\\Exception\\KeyTransformerHasTooManyParameters' => $vendorDir . '/cuyz/valinor/src/Normalizer/Exception/KeyTransformerHasTooManyParameters.php',
     'CuyZ\\Valinor\\Normalizer\\Exception\\KeyTransformerParameterInvalidType' => $vendorDir . '/cuyz/valinor/src/Normalizer/Exception/KeyTransformerParameterInvalidType.php',
@@ -193,7 +200,12 @@ return array(
     'CuyZ\\Valinor\\Normalizer\\Exception\\TransformerHasTooManyParameters' => $vendorDir . '/cuyz/valinor/src/Normalizer/Exception/TransformerHasTooManyParameters.php',
     'CuyZ\\Valinor\\Normalizer\\Exception\\TypeUnhandledByNormalizer' => $vendorDir . '/cuyz/valinor/src/Normalizer/Exception/TypeUnhandledByNormalizer.php',
     'CuyZ\\Valinor\\Normalizer\\Format' => $vendorDir . '/cuyz/valinor/src/Normalizer/Format.php',
+    'CuyZ\\Valinor\\Normalizer\\Formatter\\Exception\\CannotFormatInvalidTypeToJson' => $vendorDir . '/cuyz/valinor/src/Normalizer/Formatter/Exception/CannotFormatInvalidTypeToJson.php',
+    'CuyZ\\Valinor\\Normalizer\\Formatter\\JsonFormatter' => $vendorDir . '/cuyz/valinor/src/Normalizer/Formatter/JsonFormatter.php',
+    'CuyZ\\Valinor\\Normalizer\\Formatter\\StreamFormatter' => $vendorDir . '/cuyz/valinor/src/Normalizer/Formatter/StreamFormatter.php',
+    'CuyZ\\Valinor\\Normalizer\\JsonNormalizer' => $vendorDir . '/cuyz/valinor/src/Normalizer/JsonNormalizer.php',
     'CuyZ\\Valinor\\Normalizer\\Normalizer' => $vendorDir . '/cuyz/valinor/src/Normalizer/Normalizer.php',
+    'CuyZ\\Valinor\\Normalizer\\StreamNormalizer' => $vendorDir . '/cuyz/valinor/src/Normalizer/StreamNormalizer.php',
     'CuyZ\\Valinor\\Normalizer\\Transformer\\KeyTransformersHandler' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/KeyTransformersHandler.php',
     'CuyZ\\Valinor\\Normalizer\\Transformer\\RecursiveTransformer' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/RecursiveTransformer.php',
     'CuyZ\\Valinor\\Normalizer\\Transformer\\ValueTransformersHandler' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/ValueTransformersHandler.php',
@@ -216,14 +228,10 @@ return array(
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Enum\\MissingSpecificEnumCase' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Enum/MissingSpecificEnumCase.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\AssignedGenericNotFound' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Generic/AssignedGenericNotFound.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\CannotAssignGeneric' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Generic/CannotAssignGeneric.php',
-    'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\ExtendTagTypeError' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Generic/ExtendTagTypeError.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\GenericClosingBracketMissing' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Generic/GenericClosingBracketMissing.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\GenericCommaMissing' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Generic/GenericCommaMissing.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\InvalidAssignedGeneric' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidAssignedGeneric.php',
-    'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\InvalidExtendTagClassName' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidExtendTagClassName.php',
-    'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\InvalidExtendTagType' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidExtendTagType.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\MissingGenerics' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Generic/MissingGenerics.php',
-    'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\SeveralExtendTagsFound' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Generic/SeveralExtendTagsFound.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\InvalidIntersectionType' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/InvalidIntersectionType.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\InvalidType' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/InvalidType.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ArrayClosingBracketMissing' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ArrayClosingBracketMissing.php',
@@ -239,6 +247,9 @@ return array(
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayElementDuplicatedKey' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayElementDuplicatedKey.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayElementTypeMissing' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayElementTypeMissing.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayEmptyElements' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayEmptyElements.php',
+    'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayInvalidUnsealedType' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayInvalidUnsealedType.php',
+    'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayUnexpectedTokenAfterSealedType' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayUnexpectedTokenAfterSealedType.php',
+    'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayWithoutElementsWithSealedType' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayWithoutElementsWithSealedType.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\SimpleArrayClosingBracketMissing' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/SimpleArrayClosingBracketMissing.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\MissingClosingQuoteChar' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/MissingClosingQuoteChar.php',
     'CuyZ\\Valinor\\Type\\Parser\\Exception\\RightIntersectionTypeMissing' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Exception/RightIntersectionTypeMissing.php',
@@ -259,15 +270,14 @@ return array(
     'CuyZ\\Valinor\\Type\\Parser\\Factory\\LexingTypeParserFactory' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Factory/LexingTypeParserFactory.php',
     'CuyZ\\Valinor\\Type\\Parser\\Factory\\Specifications\\AliasSpecification' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Factory/Specifications/AliasSpecification.php',
     'CuyZ\\Valinor\\Type\\Parser\\Factory\\Specifications\\ClassContextSpecification' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Factory/Specifications/ClassContextSpecification.php',
+    'CuyZ\\Valinor\\Type\\Parser\\Factory\\Specifications\\GenericCheckerSpecification' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Factory/Specifications/GenericCheckerSpecification.php',
     'CuyZ\\Valinor\\Type\\Parser\\Factory\\Specifications\\TypeAliasAssignerSpecification' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Factory/Specifications/TypeAliasAssignerSpecification.php',
     'CuyZ\\Valinor\\Type\\Parser\\Factory\\Specifications\\TypeParserSpecification' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Factory/Specifications/TypeParserSpecification.php',
     'CuyZ\\Valinor\\Type\\Parser\\Factory\\TypeParserFactory' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Factory/TypeParserFactory.php',
-    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\AdvancedClassLexer' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/AdvancedClassLexer.php',
-    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\AliasLexer' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/AliasLexer.php',
-    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\ClassContextLexer' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/ClassContextLexer.php',
+    'CuyZ\\Valinor\\Type\\Parser\\GenericCheckerParser' => $vendorDir . '/cuyz/valinor/src/Type/Parser/GenericCheckerParser.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\NativeLexer' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/NativeLexer.php',
+    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\SpecificationsLexer' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/SpecificationsLexer.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\TokenStream' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/TokenStream.php',
-    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\AdvancedClassNameToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/AdvancedClassNameToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\ArrayToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/ArrayToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\CallableToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/CallableToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\CaseFinder' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/CaseFinder.php',
@@ -289,19 +299,20 @@ return array(
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\ListToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/ListToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\NativeToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/NativeToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\NullableToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/NullableToken.php',
+    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\ObjectToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/ObjectToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\OpeningBracketToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/OpeningBracketToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\OpeningCurlyBracketToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/OpeningCurlyBracketToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\OpeningSquareBracketToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/OpeningSquareBracketToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\QuoteToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/QuoteToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\Token' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/Token.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\TraversingToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/TraversingToken.php',
+    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\TripleDotsToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/TripleDotsToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\TypeToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/TypeToken.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\UnionToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/UnionToken.php',
-    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\UnknownSymbolToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/UnknownSymbolToken.php',
-    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\TypeAliasLexer' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/TypeAliasLexer.php',
+    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\VacantToken' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/Token/VacantToken.php',
+    'CuyZ\\Valinor\\Type\\Parser\\Lexer\\TokensExtractor' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/TokensExtractor.php',
     'CuyZ\\Valinor\\Type\\Parser\\Lexer\\TypeLexer' => $vendorDir . '/cuyz/valinor/src/Type/Parser/Lexer/TypeLexer.php',
     'CuyZ\\Valinor\\Type\\Parser\\LexingParser' => $vendorDir . '/cuyz/valinor/src/Type/Parser/LexingParser.php',
-    'CuyZ\\Valinor\\Type\\Parser\\ParserSymbols' => $vendorDir . '/cuyz/valinor/src/Type/Parser/ParserSymbols.php',
     'CuyZ\\Valinor\\Type\\Parser\\TypeParser' => $vendorDir . '/cuyz/valinor/src/Type/Parser/TypeParser.php',
     'CuyZ\\Valinor\\Type\\ScalarType' => $vendorDir . '/cuyz/valinor/src/Type/ScalarType.php',
     'CuyZ\\Valinor\\Type\\StringType' => $vendorDir . '/cuyz/valinor/src/Type/StringType.php',
@@ -891,6 +902,7 @@ return array(
     'Random\\Engine\\Secure' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php',
     'Random\\RandomError' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php',
     'Random\\RandomException' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php',
+    'SQLite3Exception' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php',
     'Sabberworm\\CSS\\CSSList\\AtRuleBlockList' => $vendorDir . '/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php',
     'Sabberworm\\CSS\\CSSList\\CSSBlockList' => $vendorDir . '/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php',
     'Sabberworm\\CSS\\CSSList\\CSSList' => $vendorDir . '/sabberworm/php-css-parser/src/CSSList/CSSList.php',
@@ -901,6 +913,7 @@ return array(
     'Sabberworm\\CSS\\OutputFormat' => $vendorDir . '/sabberworm/php-css-parser/src/OutputFormat.php',
     'Sabberworm\\CSS\\OutputFormatter' => $vendorDir . '/sabberworm/php-css-parser/src/OutputFormatter.php',
     'Sabberworm\\CSS\\Parser' => $vendorDir . '/sabberworm/php-css-parser/src/Parser.php',
+    'Sabberworm\\CSS\\Parsing\\Anchor' => $vendorDir . '/sabberworm/php-css-parser/src/Parsing/Anchor.php',
     'Sabberworm\\CSS\\Parsing\\OutputException' => $vendorDir . '/sabberworm/php-css-parser/src/Parsing/OutputException.php',
     'Sabberworm\\CSS\\Parsing\\ParserState' => $vendorDir . '/sabberworm/php-css-parser/src/Parsing/ParserState.php',
     'Sabberworm\\CSS\\Parsing\\SourceException' => $vendorDir . '/sabberworm/php-css-parser/src/Parsing/SourceException.php',
index 78f3e785330a4caa0215d03a014371acb95d9762..b75b3a32f0127f67df82742fafb47871215b4b5a 100644 (file)
@@ -15,7 +15,7 @@ return array(
     'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
     'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
     'Psr\\Http\\Server\\' => array($vendorDir . '/psr/http-server-handler/src', $vendorDir . '/psr/http-server-middleware/src'),
-    'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
+    'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'),
     'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
     'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
     'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
index 88105fab17eda4327b8db07b728217198a2d55ad..db46c9c43c2c5d5e3d8c54e89e07a187e0824cc0 100644 (file)
@@ -111,8 +111,8 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         ),
         'Psr\\Http\\Message\\' => 
         array (
-            0 => __DIR__ . '/..' . '/psr/http-factory/src',
-            1 => __DIR__ . '/..' . '/psr/http-message/src',
+            0 => __DIR__ . '/..' . '/psr/http-message/src',
+            1 => __DIR__ . '/..' . '/psr/http-factory/src',
         ),
         'Psr\\Http\\Client\\' => 
         array (
@@ -207,11 +207,6 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'Cron\\MinutesField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/MinutesField.php',
         'Cron\\MonthField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/MonthField.php',
         'CuyZ\\Valinor\\Cache\\ChainCache' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/ChainCache.php',
-        'CuyZ\\Valinor\\Cache\\Compiled\\CacheCompiler' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Compiled/CacheCompiler.php',
-        'CuyZ\\Valinor\\Cache\\Compiled\\CompiledPhpFileCache' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Compiled/CompiledPhpFileCache.php',
-        'CuyZ\\Valinor\\Cache\\Compiled\\HasArguments' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Compiled/HasArguments.php',
-        'CuyZ\\Valinor\\Cache\\Compiled\\MixedValueCacheCompiler' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Compiled/MixedValueCacheCompiler.php',
-        'CuyZ\\Valinor\\Cache\\Compiled\\PhpCacheFile' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Compiled/PhpCacheFile.php',
         'CuyZ\\Valinor\\Cache\\Exception\\CacheDirectoryNotWritable' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Exception/CacheDirectoryNotWritable.php',
         'CuyZ\\Valinor\\Cache\\Exception\\CompiledPhpCacheFileNotWritten' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Exception/CompiledPhpCacheFileNotWritten.php',
         'CuyZ\\Valinor\\Cache\\Exception\\CorruptedCompiledPhpCacheFile' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Exception/CorruptedCompiledPhpCacheFile.php',
@@ -222,20 +217,22 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Cache\\RuntimeCache' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/RuntimeCache.php',
         'CuyZ\\Valinor\\Cache\\WarmupCache' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/WarmupCache.php',
         'CuyZ\\Valinor\\Cache\\Warmup\\RecursiveCacheWarmupService' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Warmup/RecursiveCacheWarmupService.php',
+        'CuyZ\\Valinor\\Definition\\AttributeDefinition' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/AttributeDefinition.php',
         'CuyZ\\Valinor\\Definition\\Attributes' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Attributes.php',
-        'CuyZ\\Valinor\\Definition\\AttributesContainer' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/AttributesContainer.php',
         'CuyZ\\Valinor\\Definition\\ClassDefinition' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/ClassDefinition.php',
         'CuyZ\\Valinor\\Definition\\Exception\\ClassTypeAliasesDuplication' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Exception/ClassTypeAliasesDuplication.php',
+        'CuyZ\\Valinor\\Definition\\Exception\\ExtendTagTypeError' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Exception/ExtendTagTypeError.php',
+        'CuyZ\\Valinor\\Definition\\Exception\\InvalidExtendTagClassName' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Exception/InvalidExtendTagClassName.php',
+        'CuyZ\\Valinor\\Definition\\Exception\\InvalidExtendTagType' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Exception/InvalidExtendTagType.php',
         'CuyZ\\Valinor\\Definition\\Exception\\InvalidTypeAliasImportClass' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Exception/InvalidTypeAliasImportClass.php',
         'CuyZ\\Valinor\\Definition\\Exception\\InvalidTypeAliasImportClassType' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Exception/InvalidTypeAliasImportClassType.php',
-        'CuyZ\\Valinor\\Definition\\Exception\\TypesDoNotMatch' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Exception/TypesDoNotMatch.php',
+        'CuyZ\\Valinor\\Definition\\Exception\\SeveralExtendTagsFound' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Exception/SeveralExtendTagsFound.php',
         'CuyZ\\Valinor\\Definition\\Exception\\UnknownTypeAliasImport' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Exception/UnknownTypeAliasImport.php',
         'CuyZ\\Valinor\\Definition\\FunctionDefinition' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/FunctionDefinition.php',
         'CuyZ\\Valinor\\Definition\\FunctionObject' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/FunctionObject.php',
         'CuyZ\\Valinor\\Definition\\FunctionsContainer' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/FunctionsContainer.php',
         'CuyZ\\Valinor\\Definition\\MethodDefinition' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/MethodDefinition.php',
         'CuyZ\\Valinor\\Definition\\Methods' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Methods.php',
-        'CuyZ\\Valinor\\Definition\\NativeAttributes' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/NativeAttributes.php',
         'CuyZ\\Valinor\\Definition\\ParameterDefinition' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/ParameterDefinition.php',
         'CuyZ\\Valinor\\Definition\\Parameters' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Parameters.php',
         'CuyZ\\Valinor\\Definition\\Properties' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Properties.php',
@@ -253,7 +250,7 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\TypeCompiler' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/TypeCompiler.php',
         'CuyZ\\Valinor\\Definition\\Repository\\ClassDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/ClassDefinitionRepository.php',
         'CuyZ\\Valinor\\Definition\\Repository\\FunctionDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/FunctionDefinitionRepository.php',
-        'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\NativeAttributesRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Reflection/NativeAttributesRepository.php',
+        'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\ReflectionAttributesRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php',
         'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\ReflectionClassDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionClassDefinitionRepository.php',
         'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\ReflectionFunctionDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionFunctionDefinitionRepository.php',
         'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\ReflectionMethodDefinitionBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionMethodDefinitionBuilder.php',
@@ -266,16 +263,20 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Mapper\\ArgumentsMapper' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/ArgumentsMapper.php',
         'CuyZ\\Valinor\\Mapper\\ArgumentsMapperError' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/ArgumentsMapperError.php',
         'CuyZ\\Valinor\\Mapper\\Exception\\InvalidMappingTypeSignature' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Exception/InvalidMappingTypeSignature.php',
+        'CuyZ\\Valinor\\Mapper\\Exception\\TypeErrorDuringArgumentsMapping' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringArgumentsMapping.php',
+        'CuyZ\\Valinor\\Mapper\\Exception\\TypeErrorDuringMapping' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringMapping.php',
         'CuyZ\\Valinor\\Mapper\\MappingError' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/MappingError.php',
         'CuyZ\\Valinor\\Mapper\\Object\\Argument' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Argument.php',
         'CuyZ\\Valinor\\Mapper\\Object\\Arguments' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Arguments.php',
         'CuyZ\\Valinor\\Mapper\\Object\\ArgumentsValues' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/ArgumentsValues.php',
+        'CuyZ\\Valinor\\Mapper\\Object\\Constructor' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Constructor.php',
         'CuyZ\\Valinor\\Mapper\\Object\\DateTimeFormatConstructor' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/DateTimeFormatConstructor.php',
         'CuyZ\\Valinor\\Mapper\\Object\\DynamicConstructor' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/DynamicConstructor.php',
         'CuyZ\\Valinor\\Mapper\\Object\\Exception\\CannotFindObjectBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/CannotFindObjectBuilder.php',
         'CuyZ\\Valinor\\Mapper\\Object\\Exception\\CannotInstantiateObject' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/CannotInstantiateObject.php',
         'CuyZ\\Valinor\\Mapper\\Object\\Exception\\CannotParseToDateTime' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/CannotParseToDateTime.php',
         'CuyZ\\Valinor\\Mapper\\Object\\Exception\\InvalidConstructorClassTypeParameter' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorClassTypeParameter.php',
+        'CuyZ\\Valinor\\Mapper\\Object\\Exception\\InvalidConstructorMethodWithAttributeReturnType' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorMethodWithAttributeReturnType.php',
         'CuyZ\\Valinor\\Mapper\\Object\\Exception\\InvalidConstructorReturnType' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorReturnType.php',
         'CuyZ\\Valinor\\Mapper\\Object\\Exception\\InvalidSource' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/InvalidSource.php',
         'CuyZ\\Valinor\\Mapper\\Object\\Exception\\MissingConstructorClassTypeParameter' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/MissingConstructorClassTypeParameter.php',
@@ -318,11 +319,12 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\CasterNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/CasterNodeBuilder.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\CasterProxyNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ErrorCatcherNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php',
+        'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\FilteredObjectNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/FilteredObjectNodeBuilder.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\InterfaceNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\IterableNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/IterableNodeBuilder.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ListNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ListNodeBuilder.php',
-        'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\NativeClassNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/NativeClassNodeBuilder.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\NodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/NodeBuilder.php',
+        'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\NullNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ObjectImplementations' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ObjectImplementations.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ObjectNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\RootNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php',
@@ -335,6 +337,7 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotInferFinalClass' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotInferFinalClass.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotResolveObjectType' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveObjectType.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotResolveTypeFromUnion' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php',
+        'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InterfaceHasBothConstructorAndInfer' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InterfaceHasBothConstructorAndInfer.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidAbstractObjectName' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidAbstractObjectName.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidListKey' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidListKey.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidNodeHasNoMappedValue' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeHasNoMappedValue.php',
@@ -347,10 +350,13 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ObjectImplementationCallbackError' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/ObjectImplementationCallbackError.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ObjectImplementationNotRegistered' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/ObjectImplementationNotRegistered.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ResolvedImplementationIsNotAccepted' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/ResolvedImplementationIsNotAccepted.php',
+        'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceIsNotNull' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceMustBeIterable' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceMustBeIterable.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceValueWasNotFilled' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php',
+        'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\TooManyResolvedTypesFromUnion' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\UnexpectedArrayKeysForClass' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/UnexpectedArrayKeysForClass.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\UnexpectedShapedArrayKeys' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/UnexpectedShapedArrayKeys.php',
+        'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\UnresolvableShellType' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/UnresolvableShellType.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Message\\DefaultMessage' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Message/DefaultMessage.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Message\\ErrorMessage' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Message/ErrorMessage.php',
         'CuyZ\\Valinor\\Mapper\\Tree\\Message\\Formatter\\AggregateMessageFormatter' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Message/Formatter/AggregateMessageFormatter.php',
@@ -373,6 +379,7 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Mapper\\TypeTreeMapper' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/TypeTreeMapper.php',
         'CuyZ\\Valinor\\Mapper\\TypeTreeMapperError' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/TypeTreeMapperError.php',
         'CuyZ\\Valinor\\Normalizer\\ArrayNormalizer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/ArrayNormalizer.php',
+        'CuyZ\\Valinor\\Normalizer\\AsTransformer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/AsTransformer.php',
         'CuyZ\\Valinor\\Normalizer\\Exception\\CircularReferenceFoundDuringNormalization' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Exception/CircularReferenceFoundDuringNormalization.php',
         'CuyZ\\Valinor\\Normalizer\\Exception\\KeyTransformerHasTooManyParameters' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Exception/KeyTransformerHasTooManyParameters.php',
         'CuyZ\\Valinor\\Normalizer\\Exception\\KeyTransformerParameterInvalidType' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Exception/KeyTransformerParameterInvalidType.php',
@@ -381,7 +388,12 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Normalizer\\Exception\\TransformerHasTooManyParameters' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Exception/TransformerHasTooManyParameters.php',
         'CuyZ\\Valinor\\Normalizer\\Exception\\TypeUnhandledByNormalizer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Exception/TypeUnhandledByNormalizer.php',
         'CuyZ\\Valinor\\Normalizer\\Format' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Format.php',
+        'CuyZ\\Valinor\\Normalizer\\Formatter\\Exception\\CannotFormatInvalidTypeToJson' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Formatter/Exception/CannotFormatInvalidTypeToJson.php',
+        'CuyZ\\Valinor\\Normalizer\\Formatter\\JsonFormatter' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Formatter/JsonFormatter.php',
+        'CuyZ\\Valinor\\Normalizer\\Formatter\\StreamFormatter' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Formatter/StreamFormatter.php',
+        'CuyZ\\Valinor\\Normalizer\\JsonNormalizer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/JsonNormalizer.php',
         'CuyZ\\Valinor\\Normalizer\\Normalizer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Normalizer.php',
+        'CuyZ\\Valinor\\Normalizer\\StreamNormalizer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/StreamNormalizer.php',
         'CuyZ\\Valinor\\Normalizer\\Transformer\\KeyTransformersHandler' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/KeyTransformersHandler.php',
         'CuyZ\\Valinor\\Normalizer\\Transformer\\RecursiveTransformer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/RecursiveTransformer.php',
         'CuyZ\\Valinor\\Normalizer\\Transformer\\ValueTransformersHandler' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/ValueTransformersHandler.php',
@@ -404,14 +416,10 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Enum\\MissingSpecificEnumCase' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Enum/MissingSpecificEnumCase.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\AssignedGenericNotFound' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Generic/AssignedGenericNotFound.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\CannotAssignGeneric' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Generic/CannotAssignGeneric.php',
-        'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\ExtendTagTypeError' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Generic/ExtendTagTypeError.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\GenericClosingBracketMissing' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Generic/GenericClosingBracketMissing.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\GenericCommaMissing' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Generic/GenericCommaMissing.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\InvalidAssignedGeneric' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidAssignedGeneric.php',
-        'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\InvalidExtendTagClassName' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidExtendTagClassName.php',
-        'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\InvalidExtendTagType' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidExtendTagType.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\MissingGenerics' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Generic/MissingGenerics.php',
-        'CuyZ\\Valinor\\Type\\Parser\\Exception\\Generic\\SeveralExtendTagsFound' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Generic/SeveralExtendTagsFound.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\InvalidIntersectionType' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/InvalidIntersectionType.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\InvalidType' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/InvalidType.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ArrayClosingBracketMissing' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ArrayClosingBracketMissing.php',
@@ -427,6 +435,9 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayElementDuplicatedKey' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayElementDuplicatedKey.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayElementTypeMissing' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayElementTypeMissing.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayEmptyElements' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayEmptyElements.php',
+        'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayInvalidUnsealedType' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayInvalidUnsealedType.php',
+        'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayUnexpectedTokenAfterSealedType' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayUnexpectedTokenAfterSealedType.php',
+        'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\ShapedArrayWithoutElementsWithSealedType' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayWithoutElementsWithSealedType.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\Iterable\\SimpleArrayClosingBracketMissing' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/Iterable/SimpleArrayClosingBracketMissing.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\MissingClosingQuoteChar' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/MissingClosingQuoteChar.php',
         'CuyZ\\Valinor\\Type\\Parser\\Exception\\RightIntersectionTypeMissing' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Exception/RightIntersectionTypeMissing.php',
@@ -447,15 +458,14 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Type\\Parser\\Factory\\LexingTypeParserFactory' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Factory/LexingTypeParserFactory.php',
         'CuyZ\\Valinor\\Type\\Parser\\Factory\\Specifications\\AliasSpecification' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Factory/Specifications/AliasSpecification.php',
         'CuyZ\\Valinor\\Type\\Parser\\Factory\\Specifications\\ClassContextSpecification' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Factory/Specifications/ClassContextSpecification.php',
+        'CuyZ\\Valinor\\Type\\Parser\\Factory\\Specifications\\GenericCheckerSpecification' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Factory/Specifications/GenericCheckerSpecification.php',
         'CuyZ\\Valinor\\Type\\Parser\\Factory\\Specifications\\TypeAliasAssignerSpecification' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Factory/Specifications/TypeAliasAssignerSpecification.php',
         'CuyZ\\Valinor\\Type\\Parser\\Factory\\Specifications\\TypeParserSpecification' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Factory/Specifications/TypeParserSpecification.php',
         'CuyZ\\Valinor\\Type\\Parser\\Factory\\TypeParserFactory' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Factory/TypeParserFactory.php',
-        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\AdvancedClassLexer' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/AdvancedClassLexer.php',
-        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\AliasLexer' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/AliasLexer.php',
-        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\ClassContextLexer' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/ClassContextLexer.php',
+        'CuyZ\\Valinor\\Type\\Parser\\GenericCheckerParser' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/GenericCheckerParser.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\NativeLexer' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/NativeLexer.php',
+        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\SpecificationsLexer' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/SpecificationsLexer.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\TokenStream' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/TokenStream.php',
-        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\AdvancedClassNameToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/AdvancedClassNameToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\ArrayToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/ArrayToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\CallableToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/CallableToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\CaseFinder' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/CaseFinder.php',
@@ -477,19 +487,20 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\ListToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/ListToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\NativeToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/NativeToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\NullableToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/NullableToken.php',
+        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\ObjectToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/ObjectToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\OpeningBracketToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/OpeningBracketToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\OpeningCurlyBracketToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/OpeningCurlyBracketToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\OpeningSquareBracketToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/OpeningSquareBracketToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\QuoteToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/QuoteToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\Token' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/Token.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\TraversingToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/TraversingToken.php',
+        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\TripleDotsToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/TripleDotsToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\TypeToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/TypeToken.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\UnionToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/UnionToken.php',
-        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\UnknownSymbolToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/UnknownSymbolToken.php',
-        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\TypeAliasLexer' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/TypeAliasLexer.php',
+        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\Token\\VacantToken' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/Token/VacantToken.php',
+        'CuyZ\\Valinor\\Type\\Parser\\Lexer\\TokensExtractor' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/TokensExtractor.php',
         'CuyZ\\Valinor\\Type\\Parser\\Lexer\\TypeLexer' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/Lexer/TypeLexer.php',
         'CuyZ\\Valinor\\Type\\Parser\\LexingParser' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/LexingParser.php',
-        'CuyZ\\Valinor\\Type\\Parser\\ParserSymbols' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/ParserSymbols.php',
         'CuyZ\\Valinor\\Type\\Parser\\TypeParser' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/Parser/TypeParser.php',
         'CuyZ\\Valinor\\Type\\ScalarType' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/ScalarType.php',
         'CuyZ\\Valinor\\Type\\StringType' => __DIR__ . '/..' . '/cuyz/valinor/src/Type/StringType.php',
@@ -1079,6 +1090,7 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'Random\\Engine\\Secure' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php',
         'Random\\RandomError' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php',
         'Random\\RandomException' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php',
+        'SQLite3Exception' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php',
         'Sabberworm\\CSS\\CSSList\\AtRuleBlockList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php',
         'Sabberworm\\CSS\\CSSList\\CSSBlockList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php',
         'Sabberworm\\CSS\\CSSList\\CSSList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/CSSList/CSSList.php',
@@ -1089,6 +1101,7 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
         'Sabberworm\\CSS\\OutputFormat' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/OutputFormat.php',
         'Sabberworm\\CSS\\OutputFormatter' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/OutputFormatter.php',
         'Sabberworm\\CSS\\Parser' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parser.php',
+        'Sabberworm\\CSS\\Parsing\\Anchor' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parsing/Anchor.php',
         'Sabberworm\\CSS\\Parsing\\OutputException' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parsing/OutputException.php',
         'Sabberworm\\CSS\\Parsing\\ParserState' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parsing/ParserState.php',
         'Sabberworm\\CSS\\Parsing\\SourceException' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parsing/SourceException.php',
index b34bb87bc158a1ec1ccdf3a25b78d58fd43b0535..e12181b6a627f38fe70f60f202a2a3af36a772ac 100644 (file)
@@ -2,37 +2,37 @@
     "packages": [
         {
             "name": "cuyz/valinor",
-            "version": "1.8.2",
-            "version_normalized": "1.8.2.0",
+            "version": "1.12.0",
+            "version_normalized": "1.12.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/CuyZ/Valinor.git",
-                "reference": "daf8206d11b1cb6b308ecd2eb6b65657d2248544"
+                "reference": "3bc40798a5ff64aee8a28509b73f7f84d5c66ac9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/CuyZ/Valinor/zipball/daf8206d11b1cb6b308ecd2eb6b65657d2248544",
-                "reference": "daf8206d11b1cb6b308ecd2eb6b65657d2248544",
+                "url": "https://api.github.com/repos/CuyZ/Valinor/zipball/3bc40798a5ff64aee8a28509b73f7f84d5c66ac9",
+                "reference": "3bc40798a5ff64aee8a28509b73f7f84d5c66ac9",
                 "shasum": ""
             },
             "require": {
                 "composer-runtime-api": "^2.0",
-                "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
+                "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
                 "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
             },
             "require-dev": {
                 "friendsofphp/php-cs-fixer": "^3.4",
-                "infection/infection": "^0.26",
+                "infection/infection": "^0.27",
                 "marcocesarato/php-conventional-changelog": "^1.12",
                 "mikey179/vfsstream": "^1.6.10",
                 "phpstan/phpstan": "^1.3",
                 "phpstan/phpstan-phpunit": "^1.0",
                 "phpstan/phpstan-strict-rules": "^1.0",
-                "phpunit/phpunit": "^9.5",
-                "rector/rector": "~0.17.0",
+                "phpunit/phpunit": "^10.5",
+                "rector/rector": "^1.0",
                 "vimeo/psalm": "^5.0"
             },
-            "time": "2024-01-08T20:31:48+00:00",
+            "time": "2024-04-04T16:42:55+00:00",
             "type": "library",
             "installation-source": "dist",
             "autoload": {
@@ -66,7 +66,7 @@
             ],
             "support": {
                 "issues": "https://github.com/CuyZ/Valinor/issues",
-                "source": "https://github.com/CuyZ/Valinor/tree/1.8.2"
+                "source": "https://github.com/CuyZ/Valinor/tree/1.12.0"
             },
             "funding": [
                 {
         },
         {
             "name": "laminas/laminas-diactoros",
-            "version": "3.3.0",
-            "version_normalized": "3.3.0.0",
+            "version": "3.3.1",
+            "version_normalized": "3.3.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laminas/laminas-diactoros.git",
-                "reference": "4db52734837c60259c9b2d7caf08eef8f7f9b9ac"
+                "reference": "74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/4db52734837c60259c9b2d7caf08eef8f7f9b9ac",
-                "reference": "4db52734837c60259c9b2d7caf08eef8f7f9b9ac",
+                "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45",
+                "reference": "74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45",
                 "shasum": ""
             },
             "require": {
                 "http-interop/http-factory-tests": "^0.9.0",
                 "laminas/laminas-coding-standard": "~2.5.0",
                 "php-http/psr7-integration-tests": "^1.3",
-                "phpunit/phpunit": "^9.5.28",
+                "phpunit/phpunit": "^9.6.16",
                 "psalm/plugin-phpunit": "^0.18.4",
-                "vimeo/psalm": "^5.15.0"
+                "vimeo/psalm": "^5.22.1"
             },
-            "time": "2023-10-26T11:01:07+00:00",
+            "time": "2024-02-16T16:06:16+00:00",
             "type": "library",
             "extra": {
                 "laminas": {
         },
         {
             "name": "paragonie/constant_time_encoding",
-            "version": "v2.6.3",
-            "version_normalized": "2.6.3.0",
+            "version": "v2.7.0",
+            "version_normalized": "2.7.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/paragonie/constant_time_encoding.git",
-                "reference": "58c3f47f650c94ec05a151692652a868995d2938"
+                "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938",
-                "reference": "58c3f47f650c94ec05a151692652a868995d2938",
+                "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105",
+                "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105",
                 "shasum": ""
             },
             "require": {
                 "phpunit/phpunit": "^6|^7|^8|^9",
                 "vimeo/psalm": "^1|^2|^3|^4"
             },
-            "time": "2022-06-14T06:56:20+00:00",
+            "time": "2024-05-08T12:18:48+00:00",
             "type": "library",
             "installation-source": "dist",
             "autoload": {
         },
         {
             "name": "psr/http-factory",
-            "version": "1.0.2",
-            "version_normalized": "1.0.2.0",
+            "version": "1.1.0",
+            "version_normalized": "1.1.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/http-factory.git",
-                "reference": "e616d01114759c4c489f93b099585439f795fe35"
+                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
-                "reference": "e616d01114759c4c489f93b099585439f795fe35",
+                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0.0",
+                "php": ">=7.1",
                 "psr/http-message": "^1.0 || ^2.0"
             },
-            "time": "2023-04-10T20:10:41+00:00",
+            "time": "2024-04-15T12:06:14+00:00",
             "type": "library",
             "extra": {
                 "branch-alias": {
                     "homepage": "https://www.php-fig.org/"
                 }
             ],
-            "description": "Common interfaces for PSR-7 HTTP message factories",
+            "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
             "keywords": [
                 "factory",
                 "http",
                 "response"
             ],
             "support": {
-                "source": "https://github.com/php-fig/http-factory/tree/1.0.2"
+                "source": "https://github.com/php-fig/http-factory"
             },
             "install-path": "../psr/http-factory"
         },
         },
         {
             "name": "sabberworm/php-css-parser",
-            "version": "8.4.0",
-            "version_normalized": "8.4.0.0",
+            "version": "v8.5.1",
+            "version_normalized": "8.5.1.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
-                "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30"
+                "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
+                "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30",
-                "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30",
+                "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/4a3d572b0f8b28bb6fd016ae8bbfc445facef152",
+                "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.6.20"
             },
             "require-dev": {
-                "codacy/coverage": "^1.4",
-                "phpunit/phpunit": "^4.8.36"
+                "phpunit/phpunit": "^5.7.27"
             },
             "suggest": {
                 "ext-mbstring": "for parsing UTF-8 CSS"
             },
-            "time": "2021-12-11T13:40:54+00:00",
+            "time": "2024-02-15T16:41:13+00:00",
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "9.0.x-dev"
+                }
+            },
             "installation-source": "dist",
             "autoload": {
                 "psr-4": {
             "authors": [
                 {
                     "name": "Raphael Schweikert"
+                },
+                {
+                    "name": "Oliver Klee",
+                    "email": "github@oliverklee.de"
+                },
+                {
+                    "name": "Jake Hotson",
+                    "email": "jake.github@qzdesign.co.uk"
                 }
             ],
             "description": "Parser for CSS Files written in PHP",
                 "stylesheet"
             ],
             "support": {
-                "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues",
-                "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0"
+                "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
+                "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.5.1"
             },
             "install-path": "../sabberworm/php-css-parser"
         },
         },
         {
             "name": "sebastian/diff",
-            "version": "5.1.0",
-            "version_normalized": "5.1.0.0",
+            "version": "5.1.1",
+            "version_normalized": "5.1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/diff.git",
-                "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f"
+                "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
-                "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
+                "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
                 "shasum": ""
             },
             "require": {
             },
             "require-dev": {
                 "phpunit/phpunit": "^10.0",
-                "symfony/process": "^4.2 || ^5"
+                "symfony/process": "^6.4"
             },
-            "time": "2023-12-22T10:55:06+00:00",
+            "time": "2024-03-02T07:15:17+00:00",
             "type": "library",
             "extra": {
                 "branch-alias": {
             "support": {
                 "issues": "https://github.com/sebastianbergmann/diff/issues",
                 "security": "https://github.com/sebastianbergmann/diff/security/policy",
-                "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0"
+                "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1"
             },
             "funding": [
                 {
         },
         {
             "name": "symfony/css-selector",
-            "version": "v6.4.0",
-            "version_normalized": "6.4.0.0",
+            "version": "v6.4.8",
+            "version_normalized": "6.4.8.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4"
+                "reference": "4b61b02fe15db48e3687ce1c45ea385d1780fe08"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/d036c6c0d0b09e24a14a35f8292146a658f986e4",
-                "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/4b61b02fe15db48e3687ce1c45ea385d1780fe08",
+                "reference": "4b61b02fe15db48e3687ce1c45ea385d1780fe08",
                 "shasum": ""
             },
             "require": {
                 "php": ">=8.1"
             },
-            "time": "2023-10-31T08:40:20+00:00",
+            "time": "2024-05-31T14:49:08+00:00",
             "type": "library",
             "installation-source": "dist",
             "autoload": {
             "description": "Converts CSS selectors to XPath expressions",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/css-selector/tree/v6.4.0"
+                "source": "https://github.com/symfony/css-selector/tree/v6.4.8"
             },
             "funding": [
                 {
         },
         {
             "name": "symfony/deprecation-contracts",
-            "version": "v3.4.0",
-            "version_normalized": "3.4.0.0",
+            "version": "v3.5.0",
+            "version_normalized": "3.5.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/deprecation-contracts.git",
-                "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
+                "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
-                "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
+                "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
                 "shasum": ""
             },
             "require": {
                 "php": ">=8.1"
             },
-            "time": "2023-05-23T14:45:45+00:00",
+            "time": "2024-04-18T09:32:20+00:00",
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "3.4-dev"
+                    "dev-main": "3.5-dev"
                 },
                 "thanks": {
                     "name": "symfony/contracts",
             "description": "A generic function and convention to trigger deprecation notices",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
             },
             "funding": [
                 {
         },
         {
             "name": "symfony/polyfill-php82",
-            "version": "v1.28.0",
-            "version_normalized": "1.28.0.0",
+            "version": "v1.30.0",
+            "version_normalized": "1.30.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php82.git",
-                "reference": "7716bea9c86776fb3362d6b52fe1fc9471056a49"
+                "reference": "77ff49780f56906788a88974867ed68bc49fae5b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/7716bea9c86776fb3362d6b52fe1fc9471056a49",
-                "reference": "7716bea9c86776fb3362d6b52fe1fc9471056a49",
+                "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/77ff49780f56906788a88974867ed68bc49fae5b",
+                "reference": "77ff49780f56906788a88974867ed68bc49fae5b",
                 "shasum": ""
             },
             "require": {
                 "php": ">=7.1"
             },
-            "time": "2023-08-25T17:27:25+00:00",
+            "time": "2024-06-19T12:30:46+00:00",
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "1.28-dev"
-                },
                 "thanks": {
                     "name": "symfony/polyfill",
                     "url": "https://github.com/symfony/polyfill"
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php82/tree/v1.28.0"
+                "source": "https://github.com/symfony/polyfill-php82/tree/v1.30.0"
             },
             "funding": [
                 {
         },
         {
             "name": "symfony/polyfill-php83",
-            "version": "v1.28.0",
-            "version_normalized": "1.28.0.0",
+            "version": "v1.30.0",
+            "version_normalized": "1.30.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php83.git",
-                "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11"
+                "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11",
-                "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11",
+                "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9",
+                "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1",
-                "symfony/polyfill-php80": "^1.14"
+                "php": ">=7.1"
             },
-            "time": "2023-08-16T06:22:46+00:00",
+            "time": "2024-06-19T12:35:24+00:00",
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "1.28-dev"
-                },
                 "thanks": {
                     "name": "symfony/polyfill",
                     "url": "https://github.com/symfony/polyfill"
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0"
+                "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0"
             },
             "funding": [
                 {
index 265ff4b235396186a3850c80d1c6ed4b8fcbeaee..dc64a679aaebe12c9e032a103ae7fa573461039b 100644 (file)
@@ -3,7 +3,7 @@
         'name' => '__root__',
         'pretty_version' => '6.0.x-dev',
         'version' => '6.0.9999999.9999999-dev',
-        'reference' => '06074fe9cfc6d00fc36fd1b72cafa582b73ac5d8',
+        'reference' => '284adc7603f0e4cc0733ad4011c123c12ffa6e3a',
         'type' => 'project',
         'install_path' => __DIR__ . '/../',
         'aliases' => array(),
         '__root__' => array(
             'pretty_version' => '6.0.x-dev',
             'version' => '6.0.9999999.9999999-dev',
-            'reference' => '06074fe9cfc6d00fc36fd1b72cafa582b73ac5d8',
+            'reference' => '284adc7603f0e4cc0733ad4011c123c12ffa6e3a',
             'type' => 'project',
             'install_path' => __DIR__ . '/../',
             'aliases' => array(),
             'dev_requirement' => false,
         ),
         'cuyz/valinor' => array(
-            'pretty_version' => '1.8.2',
-            'version' => '1.8.2.0',
-            'reference' => 'daf8206d11b1cb6b308ecd2eb6b65657d2248544',
+            'pretty_version' => '1.12.0',
+            'version' => '1.12.0.0',
+            'reference' => '3bc40798a5ff64aee8a28509b73f7f84d5c66ac9',
             'type' => 'library',
             'install_path' => __DIR__ . '/../cuyz/valinor',
             'aliases' => array(),
@@ -83,9 +83,9 @@
             'dev_requirement' => false,
         ),
         'laminas/laminas-diactoros' => array(
-            'pretty_version' => '3.3.0',
-            'version' => '3.3.0.0',
-            'reference' => '4db52734837c60259c9b2d7caf08eef8f7f9b9ac',
+            'pretty_version' => '3.3.1',
+            'version' => '3.3.1.0',
+            'reference' => '74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45',
             'type' => 'library',
             'install_path' => __DIR__ . '/../laminas/laminas-diactoros',
             'aliases' => array(),
             ),
         ),
         'paragonie/constant_time_encoding' => array(
-            'pretty_version' => 'v2.6.3',
-            'version' => '2.6.3.0',
-            'reference' => '58c3f47f650c94ec05a151692652a868995d2938',
+            'pretty_version' => 'v2.7.0',
+            'version' => '2.7.0.0',
+            'reference' => '52a0d99e69f56b9ec27ace92ba56897fe6993105',
             'type' => 'library',
             'install_path' => __DIR__ . '/../paragonie/constant_time_encoding',
             'aliases' => array(),
             ),
         ),
         'psr/http-factory' => array(
-            'pretty_version' => '1.0.2',
-            'version' => '1.0.2.0',
-            'reference' => 'e616d01114759c4c489f93b099585439f795fe35',
+            'pretty_version' => '1.1.0',
+            'version' => '1.1.0.0',
+            'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
             'type' => 'library',
             'install_path' => __DIR__ . '/../psr/http-factory',
             'aliases' => array(),
             'dev_requirement' => false,
         ),
         'sabberworm/php-css-parser' => array(
-            'pretty_version' => '8.4.0',
-            'version' => '8.4.0.0',
-            'reference' => 'e41d2140031d533348b2192a83f02d8dd8a71d30',
+            'pretty_version' => 'v8.5.1',
+            'version' => '8.5.1.0',
+            'reference' => '4a3d572b0f8b28bb6fd016ae8bbfc445facef152',
             'type' => 'library',
             'install_path' => __DIR__ . '/../sabberworm/php-css-parser',
             'aliases' => array(),
             'dev_requirement' => false,
         ),
         'sebastian/diff' => array(
-            'pretty_version' => '5.1.0',
-            'version' => '5.1.0.0',
-            'reference' => 'fbf413a49e54f6b9b17e12d900ac7f6101591b7f',
+            'pretty_version' => '5.1.1',
+            'version' => '5.1.1.0',
+            'reference' => 'c41e007b4b62af48218231d6c2275e4c9b975b2e',
             'type' => 'library',
             'install_path' => __DIR__ . '/../sebastian/diff',
             'aliases' => array(),
             'dev_requirement' => false,
         ),
         'symfony/css-selector' => array(
-            'pretty_version' => 'v6.4.0',
-            'version' => '6.4.0.0',
-            'reference' => 'd036c6c0d0b09e24a14a35f8292146a658f986e4',
+            'pretty_version' => 'v6.4.8',
+            'version' => '6.4.8.0',
+            'reference' => '4b61b02fe15db48e3687ce1c45ea385d1780fe08',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/css-selector',
             'aliases' => array(),
             'dev_requirement' => false,
         ),
         'symfony/deprecation-contracts' => array(
-            'pretty_version' => 'v3.4.0',
-            'version' => '3.4.0.0',
-            'reference' => '7c3aff79d10325257a001fcf92d991f24fc967cf',
+            'pretty_version' => 'v3.5.0',
+            'version' => '3.5.0.0',
+            'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
             'aliases' => array(),
             ),
         ),
         'symfony/polyfill-php82' => array(
-            'pretty_version' => 'v1.28.0',
-            'version' => '1.28.0.0',
-            'reference' => '7716bea9c86776fb3362d6b52fe1fc9471056a49',
+            'pretty_version' => 'v1.30.0',
+            'version' => '1.30.0.0',
+            'reference' => '77ff49780f56906788a88974867ed68bc49fae5b',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/polyfill-php82',
             'aliases' => array(),
             'dev_requirement' => false,
         ),
         'symfony/polyfill-php83' => array(
-            'pretty_version' => 'v1.28.0',
-            'version' => '1.28.0.0',
-            'reference' => 'b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11',
+            'pretty_version' => 'v1.30.0',
+            'version' => '1.30.0.0',
+            'reference' => 'dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/polyfill-php83',
             'aliases' => array(),
index e1d6b495bc42eeaf23189c95cfa4ecc22f9dba7b..e9a9226bd006cceaf04e6e1b167c955662b01cf5 100644 (file)
         }
     ],
     "require": {
-        "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
+        "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
         "composer-runtime-api": "^2.0",
         "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
     },
     "require-dev": {
-        "phpunit/phpunit": "^9.5",
-        "infection/infection": "^0.26",
+        "phpunit/phpunit": "^10.5",
+        "infection/infection": "^0.27",
         "phpstan/phpstan": "^1.3",
         "phpstan/phpstan-strict-rules": "^1.0",
         "phpstan/phpstan-phpunit": "^1.0",
@@ -29,7 +29,7 @@
         "marcocesarato/php-conventional-changelog": "^1.12",
         "vimeo/psalm": "^5.0",
         "mikey179/vfsstream": "^1.6.10",
-        "rector/rector": "~0.17.0"
+        "rector/rector": "^1.0"
     },
     "autoload": {
         "psr-4": {
@@ -64,7 +64,6 @@
             "rector"
         ],
         "mutation": [
-            "@putenv XDEBUG_MODE=off",
             "infection --threads=max --git-diff-lines"
         ],
         "doc": [
index c05891c3f946bfbbd0b3f54db17b93e0f6d3890b..632cb4bc6301160746788cf76e35eeafeaaed6b7 100644 (file)
@@ -10,6 +10,7 @@ use PHPStan\Node\InClassNode;
 use PHPStan\Rules\Rule;
 use PHPStan\Rules\RuleErrorBuilder;
 
+use function str_contains;
 use function str_starts_with;
 
 /**
@@ -34,9 +35,7 @@ final class ApiAndInternalAnnotationCheck implements Rule
             return [];
         }
 
-        if (str_starts_with($reflection->getName(), 'CuyZ\Valinor\Tests')
-            || str_starts_with($reflection->getName(), 'SimpleNamespace')
-        ) {
+        if (str_contains($reflection->getFileName() ?? '', '/tests/')) {
             return [];
         }
 
index ac447d9db4dcc46e75014c9710be149d3a4e5720..2b2f73431bda8a2f0b26b4b738c7d5421cfccd7e 100644 (file)
@@ -8,6 +8,7 @@ use CuyZ\Valinor\Mapper\TreeMapper;
 use PhpParser\Node\Expr\MethodCall;
 use PHPStan\Analyser\Scope;
 use PHPStan\PhpDoc\TypeStringResolver;
+use PHPStan\PhpDocParser\Parser\ParserException;
 use PHPStan\Reflection\MethodReflection;
 use PHPStan\Type\ClassStringType;
 use PHPStan\Type\Constant\ConstantStringType;
@@ -46,7 +47,15 @@ final class TreeMapperPHPStanExtension implements DynamicMethodReturnTypeExtensi
             return $type->traverse(fn (Type $type) => $this->type($type));
         }
 
-        return $this->type($type);
+        try {
+            return $this->type($type);
+        } catch (ParserException) {
+            // Fallback to `mixed` type if the type cannot be resolved. This can
+            // occur with a type that is not understood/supported by PHPStan. If
+            // that happens, returning a mixed type is the safest option, as it
+            // will not make the analysis fail.
+            return new MixedType();
+        }
     }
 
     private function type(Type $type): Type
index fc21e816f5e30a95de36e70db1f68f2886d73ef7..bc97ab7b78a5dd35fb99eb806ec75f622da209f6 100644 (file)
@@ -58,13 +58,15 @@ final class ArgumentsMapperPsalmPlugin implements MethodReturnTypeProviderInterf
             return null;
         }
 
-        if (empty($type->params ?? [])) {
+        $typeParams = $type->params ?? [];
+
+        if ($typeParams === []) {
             return null;
         }
 
         $params = [];
 
-        foreach ($type->params as $param) {
+        foreach ($typeParams as $param) {
             $params[$param->name] = $param->type ?? new Union([new TMixed()]);
         }
 
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/CacheCompiler.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/CacheCompiler.php
deleted file mode 100644 (file)
index b6273b3..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Cache\Compiled;
-
-/** @internal */
-interface CacheCompiler
-{
-    public function compile(mixed $value): string;
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/CompiledPhpFileCache.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/CompiledPhpFileCache.php
deleted file mode 100644 (file)
index 39ce5d5..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Cache\Compiled;
-
-use CuyZ\Valinor\Cache\Exception\CacheDirectoryNotWritable;
-use CuyZ\Valinor\Cache\Exception\CompiledPhpCacheFileNotWritten;
-use CuyZ\Valinor\Cache\Exception\CorruptedCompiledPhpCacheFile;
-use CuyZ\Valinor\Cache\WarmupCache;
-use DateInterval;
-use DateTime;
-use Error;
-use FilesystemIterator;
-use Traversable;
-
-use function bin2hex;
-use function file_exists;
-use function file_put_contents;
-use function is_dir;
-use function mkdir;
-use function random_bytes;
-use function rename;
-use function sha1;
-use function str_contains;
-use function time;
-use function unlink;
-
-/**
- * @internal
- *
- * @template EntryType
- * @implements WarmupCache<EntryType>
- */
-final class CompiledPhpFileCache implements WarmupCache
-{
-    private const TEMPORARY_DIR_PERMISSION = 510;
-
-    private const GENERATED_MESSAGE = 'Generated by ' . self::class;
-
-    /** @var array<PhpCacheFile<EntryType>> */
-    private array $files = [];
-
-    public function __construct(
-        private string $cacheDir,
-        private CacheCompiler $compiler
-    ) {}
-
-    public function warmup(): void
-    {
-        $this->createTemporaryDir();
-    }
-
-    public function has($key): bool
-    {
-        $filename = $this->path($key);
-
-        if (! file_exists($filename)) {
-            return false;
-        }
-
-        return $this->getFile($filename)->isValid();
-    }
-
-    public function get($key, $default = null): mixed
-    {
-        if (! $this->has($key)) {
-            return $default;
-        }
-
-        $filename = $this->path($key);
-
-        return $this->getFile($filename)->value();
-    }
-
-    public function set($key, $value, $ttl = null): bool
-    {
-        $filename = $this->path($key);
-
-        $code = $this->compile($value, $ttl);
-
-        $tmpDir = $this->createTemporaryDir();
-
-        /** @infection-ignore-all */
-        $tmpFilename = $tmpDir . DIRECTORY_SEPARATOR . bin2hex(random_bytes(16));
-
-        try {
-            if (! @file_put_contents($tmpFilename, $code)) {
-                throw new CompiledPhpCacheFileNotWritten($tmpFilename);
-            }
-
-            if (! file_exists($filename) && ! @rename($tmpFilename, $filename)) {
-                throw new CompiledPhpCacheFileNotWritten($filename);
-            }
-        } finally {
-            if (file_exists($tmpFilename)) {
-                unlink($tmpFilename);
-            }
-        }
-
-        return true;
-    }
-
-    public function delete($key): bool
-    {
-        $filename = $this->path($key);
-
-        if (file_exists($filename)) {
-            return @unlink($filename);
-        }
-
-        return true;
-    }
-
-    public function clear(): bool
-    {
-        if (! is_dir($this->cacheDir)) {
-            return true;
-        }
-
-        $success = true;
-
-        /** @var FilesystemIterator $file */
-        foreach (new FilesystemIterator($this->cacheDir) as $file) {
-            if (! $file->isFile()) {
-                continue;
-            }
-
-            $line = $file->openFile()->getCurrentLine();
-
-            if (! $line || ! str_contains($line, self::GENERATED_MESSAGE)) {
-                continue;
-            }
-
-            $success = @unlink($this->cacheDir . DIRECTORY_SEPARATOR . $file->getFilename()) && $success;
-        }
-
-        return $success;
-    }
-
-    /**
-     * @return Traversable<string, EntryType|null>
-     */
-    public function getMultiple($keys, $default = null): Traversable
-    {
-        foreach ($keys as $key) {
-            yield $key => $this->get($key, $default);
-        }
-    }
-
-    public function setMultiple($values, $ttl = null): bool
-    {
-        foreach ($values as $key => $value) {
-            $this->set($key, $value, $ttl);
-        }
-
-        return true;
-    }
-
-    public function deleteMultiple($keys): bool
-    {
-        $deleted = true;
-
-        foreach ($keys as $key) {
-            $deleted = $this->delete($key) && $deleted;
-        }
-
-        return $deleted;
-    }
-
-    private function compile(mixed $value, int|DateInterval|null $ttl = null): string
-    {
-        $validationCode = 'true';
-
-        if ($ttl) {
-            $time = $ttl instanceof DateInterval
-                ? (new DateTime())->add($ttl)->getTimestamp()
-                : time() + $ttl;
-
-            $validationCode = "time() < $time";
-        }
-
-        $generatedMessage = self::GENERATED_MESSAGE;
-
-        $code = $this->compiler->compile($value);
-
-        return <<<PHP
-        <?php // $generatedMessage
-        return new class(\$this->compiler instanceof \CuyZ\Valinor\Cache\Compiled\HasArguments ? \$this->compiler->arguments() : []) implements \CuyZ\Valinor\Cache\Compiled\PhpCacheFile {
-            /** @var array<string, mixed> */
-            private array \$arguments;
-            
-            public function __construct(array \$arguments)
-            {
-                \$this->arguments = \$arguments;
-            }
-
-            public function value()
-            {
-                return $code;
-            }
-
-            public function isValid(): bool
-            {
-                return $validationCode;
-            }
-        };
-        PHP;
-    }
-
-    /**
-     * @return PhpCacheFile<EntryType>
-     */
-    private function getFile(string $filename): PhpCacheFile
-    {
-        if (! isset($this->files[$filename])) {
-            try {
-                $object = include $filename;
-            } catch (Error) {
-            }
-
-            if (! isset($object) || ! $object instanceof PhpCacheFile) {
-                throw new CorruptedCompiledPhpCacheFile($filename);
-            }
-
-            $this->files[$filename] = $object;
-        }
-
-        return $this->files[$filename];
-    }
-
-    private function createTemporaryDir(): string
-    {
-        $tmpDir = $this->cacheDir . DIRECTORY_SEPARATOR . '.valinor.tmp';
-
-        if (! is_dir($tmpDir) && ! @mkdir($tmpDir, self::TEMPORARY_DIR_PERMISSION, true)) {
-            throw new CacheDirectoryNotWritable($this->cacheDir);
-        }
-
-        return $tmpDir;
-    }
-
-    private function path(string $key): string
-    {
-        /** @infection-ignore-all */
-        return $this->cacheDir . DIRECTORY_SEPARATOR . sha1($key) . '.php';
-    }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/HasArguments.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/HasArguments.php
deleted file mode 100644 (file)
index eb9bfa1..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Cache\Compiled;
-
-/** @internal */
-interface HasArguments extends CacheCompiler
-{
-    /**
-     * @return array<string, mixed>
-     */
-    public function arguments(): array;
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/MixedValueCacheCompiler.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/MixedValueCacheCompiler.php
deleted file mode 100644 (file)
index 08bfffe..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Cache\Compiled;
-
-use function var_export;
-
-/** @internal */
-final class MixedValueCacheCompiler implements CacheCompiler
-{
-    public function compile(mixed $value): string
-    {
-        return var_export($value, true);
-    }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/PhpCacheFile.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Compiled/PhpCacheFile.php
deleted file mode 100644 (file)
index 9d4d578..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Cache\Compiled;
-
-/**
- * @internal
- *
- * @template ValueType
- */
-interface PhpCacheFile
-{
-    /**
-     * @return ValueType
-     */
-    public function value();
-
-    public function isValid(): bool;
-}
index ded11aa1172ce5b63f95ae4161e1411135834746..b8b40f54927f84b322867ef6194c089701b0c5c2 100644 (file)
@@ -4,17 +4,28 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Cache;
 
-use CuyZ\Valinor\Cache\Compiled\CompiledPhpFileCache;
-use CuyZ\Valinor\Cache\Compiled\MixedValueCacheCompiler;
+use CuyZ\Valinor\Cache\Exception\CacheDirectoryNotWritable;
+use CuyZ\Valinor\Cache\Exception\CompiledPhpCacheFileNotWritten;
+use CuyZ\Valinor\Cache\Exception\CorruptedCompiledPhpCacheFile;
 use CuyZ\Valinor\Definition\ClassDefinition;
 use CuyZ\Valinor\Definition\FunctionDefinition;
 use CuyZ\Valinor\Definition\Repository\Cache\Compiler\ClassDefinitionCompiler;
 use CuyZ\Valinor\Definition\Repository\Cache\Compiler\FunctionDefinitionCompiler;
-use Psr\SimpleCache\CacheInterface;
+use Error;
+use FilesystemIterator;
 use Traversable;
 
-use function is_object;
-use function sys_get_temp_dir;
+use function bin2hex;
+use function file_exists;
+use function file_put_contents;
+use function is_dir;
+use function mkdir;
+use function random_bytes;
+use function rename;
+use function rmdir;
+use function str_contains;
+use function unlink;
+use function var_export;
 
 /**
  * @api
@@ -24,83 +35,125 @@ use function sys_get_temp_dir;
  */
 final class FileSystemCache implements WarmupCache
 {
-    /** @var array<string, CacheInterface<EntryType>> */
-    private array $delegates;
+    private const TEMPORARY_DIR_PERMISSION = 510;
 
-    public function __construct(string $cacheDir = null)
+    private const GENERATED_MESSAGE = 'Generated by ' . self::class;
+
+    private string $cacheDir;
+
+    private ClassDefinitionCompiler $classDefinitionCompiler;
+
+    private FunctionDefinitionCompiler $functionDefinitionCompiler;
+
+    public function __construct(string $cacheDir)
     {
-        $cacheDir ??= sys_get_temp_dir();
-
-        // @infection-ignore-all
-        $this->delegates = [
-            '*' => new CompiledPhpFileCache($cacheDir . DIRECTORY_SEPARATOR . 'mixed', new MixedValueCacheCompiler()),
-            ClassDefinition::class => new CompiledPhpFileCache($cacheDir . DIRECTORY_SEPARATOR . 'classes', new ClassDefinitionCompiler()),
-            FunctionDefinition::class => new CompiledPhpFileCache($cacheDir . DIRECTORY_SEPARATOR . 'functions', new FunctionDefinitionCompiler()),
-        ];
+        $this->cacheDir = $cacheDir;
+        $this->classDefinitionCompiler = new ClassDefinitionCompiler();
+        $this->functionDefinitionCompiler = new FunctionDefinitionCompiler();
     }
 
     public function warmup(): void
     {
-        foreach ($this->delegates as $delegate) {
-            if ($delegate instanceof WarmupCache) {
-                $delegate->warmup();
-            }
-        }
+        $this->createTemporaryDir();
     }
 
     public function has($key): bool
     {
-        foreach ($this->delegates as $delegate) {
-            if ($delegate->has($key)) {
-                return true;
-            }
-        }
+        $filename = $this->path($key);
 
-        return false;
+        return file_exists($filename);
     }
 
     public function get($key, $default = null): mixed
     {
-        foreach ($this->delegates as $delegate) {
-            if ($delegate->has($key)) {
-                return $delegate->get($key, $default);
-            }
+        $filename = $this->path($key);
+
+        if (! file_exists($filename)) {
+            return $default;
         }
 
-        return $default;
+        try {
+            return include $filename;
+        } catch (Error) {
+            throw new CorruptedCompiledPhpCacheFile($filename);
+        }
     }
 
     public function set($key, $value, $ttl = null): bool
     {
-        $delegate = $this->delegates['*'];
+        $filename = $this->path($key);
 
-        if (is_object($value) && isset($this->delegates[$value::class])) {
-            $delegate = $this->delegates[$value::class];
+        $code = $this->compile($value);
+
+        $tmpDir = $this->createTemporaryDir();
+
+        /** @infection-ignore-all */
+        $tmpFilename = $tmpDir . DIRECTORY_SEPARATOR . bin2hex(random_bytes(16));
+
+        try {
+            if (! @file_put_contents($tmpFilename, $code)) {
+                throw new CompiledPhpCacheFileNotWritten($tmpFilename);
+            }
+
+            if (! file_exists($filename) && ! @rename($tmpFilename, $filename)) {
+                throw new CompiledPhpCacheFileNotWritten($filename);
+            }
+        } finally {
+            if (file_exists($tmpFilename)) {
+                unlink($tmpFilename);
+            }
         }
 
-        return $delegate->set($key, $value, $ttl);
+        return true;
     }
 
     public function delete($key): bool
     {
-        $deleted = true;
+        $filename = $this->path($key);
 
-        foreach ($this->delegates as $delegate) {
-            $deleted = $delegate->delete($key) && $deleted;
+        if (file_exists($filename)) {
+            return @unlink($filename);
         }
 
-        return $deleted;
+        return true;
     }
 
     public function clear(): bool
     {
-        $cleared = true;
+        if (! is_dir($this->cacheDir)) {
+            return true;
+        }
+
+        $success = true;
+        $shouldDeleteRootDir = true;
 
-        foreach ($this->delegates as $delegate) {
-            $cleared = $delegate->clear() && $cleared;
+        /** @var FilesystemIterator $file */
+        foreach (new FilesystemIterator($this->cacheDir) as $file) {
+            if ($file->getFilename() === '.valinor.tmp') {
+                $success = @rmdir($this->cacheDir . DIRECTORY_SEPARATOR . $file->getFilename()) && $success;
+                continue;
+            }
+
+            if (! $file->isFile()) {
+                $shouldDeleteRootDir = false;
+                continue;
+            }
+
+            $line = $file->openFile()->getCurrentLine();
+
+            if (! $line || ! str_contains($line, self::GENERATED_MESSAGE)) {
+                $shouldDeleteRootDir = false;
+                continue;
+            }
+
+            $success = @unlink($this->cacheDir . DIRECTORY_SEPARATOR . $file->getFilename()) && $success;
+        }
+
+        if ($shouldDeleteRootDir) {
+            $success = @rmdir($this->cacheDir) && $success;
         }
 
-        return $cleared;
+        return $success;
     }
 
     /**
@@ -115,13 +168,11 @@ final class FileSystemCache implements WarmupCache
 
     public function setMultiple($values, $ttl = null): bool
     {
-        $set = true;
-
         foreach ($values as $key => $value) {
-            $set = $this->set($key, $value, $ttl) && $set;
+            $this->set($key, $value, $ttl);
         }
 
-        return $set;
+        return true;
     }
 
     public function deleteMultiple($keys): bool
@@ -134,4 +185,37 @@ final class FileSystemCache implements WarmupCache
 
         return $deleted;
     }
+
+    private function compile(mixed $value): string
+    {
+        $generatedMessage = self::GENERATED_MESSAGE;
+
+        $code = match (true) {
+            $value instanceof ClassDefinition => $this->classDefinitionCompiler->compile($value),
+            $value instanceof FunctionDefinition => $this->functionDefinitionCompiler->compile($value),
+            default => var_export($value, true),
+        };
+
+        return <<<PHP
+        <?php // $generatedMessage
+        return $code;
+        PHP;
+    }
+
+    private function createTemporaryDir(): string
+    {
+        $tmpDir = $this->cacheDir . DIRECTORY_SEPARATOR . '.valinor.tmp';
+
+        if (! is_dir($tmpDir) && ! @mkdir($tmpDir, self::TEMPORARY_DIR_PERMISSION, true)) {
+            throw new CacheDirectoryNotWritable($this->cacheDir);
+        }
+
+        return $tmpDir;
+    }
+
+    private function path(string $key): string
+    {
+        /** @infection-ignore-all */
+        return $this->cacheDir . DIRECTORY_SEPARATOR . $key . '.php';
+    }
 }
index 417762bb088c459949e294c8a0ef50ee104cbe24..2ec5fb52aaf2768b5a3f0f61fa0eb3294a092a2b 100644 (file)
@@ -125,7 +125,7 @@ final class FileWatchingCache implements WarmupCache
         $fileNames = [];
 
         if ($value instanceof ClassDefinition) {
-            $reflection = Reflection::class($value->name());
+            $reflection = Reflection::class($value->name);
 
             do {
                 $fileNames[] = $reflection->getFileName();
@@ -133,7 +133,7 @@ final class FileWatchingCache implements WarmupCache
         }
 
         if ($value instanceof FunctionDefinition) {
-            $fileNames[] = $value->fileName();
+            $fileNames[] = $value->fileName;
         }
 
         foreach ($fileNames as $fileName) {
index 6950e44627daf90e8ba007de393175be1a4929c6..d0518e983e148c77d9890570af1e3fcd08d5f97c 100644 (file)
@@ -35,7 +35,7 @@ final class KeySanitizerCache implements WarmupCache
         // 2. The key is sha1'd so that it does not contain illegal characters.
         //    @see https://www.php-fig.org/psr/psr-16/#12-definitions
         // @infection-ignore-all
-        $this->sanitize = static fn (string $key) => sha1("$key." . self::$version ??= PHP_VERSION . '/' . Package::version());
+        $this->sanitize = static fn (string $key) => $key . sha1(self::$version ??= PHP_VERSION . '/' . Package::version());
     }
 
     public function warmup(): void
index 4648fc681db8584df7e28a0f98f70dfb7df05173..568ec4ef4f21a16f7ef517994fc8863eb9b0f0a9 100644 (file)
@@ -82,10 +82,10 @@ final class RecursiveCacheWarmupService
 
         $function = $this->implementations->function($interfaceName);
 
-        $this->warmupType($function->returnType());
+        $this->warmupType($function->returnType);
 
-        foreach ($function->parameters() as $parameter) {
-            $this->warmupType($parameter->type());
+        foreach ($function->parameters as $parameter) {
+            $this->warmupType($parameter->type);
         }
     }
 
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/AttributeDefinition.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/AttributeDefinition.php
new file mode 100644 (file)
index 0000000..1780935
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Definition;
+
+/** @internal */
+final class AttributeDefinition
+{
+    public function __construct(
+        public readonly ClassDefinition $class,
+        /** @var list<mixed> */
+        public readonly array $arguments,
+    ) {}
+
+    public function instantiate(): object
+    {
+        return new ($this->class->type->className())(...$this->arguments);
+    }
+}
index 3a08a2d06eda7618dd7743f49946ad002451faf1..7cda965ff51e58cd01ed576659c23a3ed5de53d7 100644 (file)
@@ -6,24 +6,76 @@ namespace CuyZ\Valinor\Definition;
 
 use Countable;
 use IteratorAggregate;
+use Traversable;
+
+use function array_filter;
+use function count;
+use function is_a;
 
 /**
  * @internal
  *
- * @extends IteratorAggregate<object>
+ * @implements IteratorAggregate<AttributeDefinition>
  */
-interface Attributes extends IteratorAggregate, Countable
+final class Attributes implements IteratorAggregate, Countable
 {
+    private static self $empty;
+
+    /** @var list<AttributeDefinition> */
+    private array $attributes;
+
+    /**
+     * @no-named-arguments
+     */
+    public function __construct(AttributeDefinition ...$attributes)
+    {
+        $this->attributes = $attributes;
+    }
+
+    public static function empty(): self
+    {
+        return self::$empty ??= new self();
+    }
+
+    public function has(string $className): bool
+    {
+        foreach ($this->attributes as $attribute) {
+            if (is_a($attribute->class->type->className(), $className, true)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @param callable(AttributeDefinition): bool $callback
+     */
+    public function filter(callable $callback): self
+    {
+        return new self(
+            ...array_filter($this->attributes, $callback)
+        );
+    }
+
+    public function count(): int
+    {
+        return count($this->attributes);
+    }
+
     /**
-     * @param class-string $className
+     * @return list<AttributeDefinition>
      */
-    public function has(string $className): bool;
+    public function toArray(): array
+    {
+        return $this->attributes;
+    }
 
     /**
-     * @template T of object
-     *
-     * @param class-string<T> $className
-     * @return list<T>
+     * @return Traversable<AttributeDefinition>
      */
-    public function ofType(string $className): array;
+    public function getIterator(): Traversable
+    {
+        yield from $this->attributes;
+    }
 }
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/AttributesContainer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/AttributesContainer.php
deleted file mode 100644 (file)
index f60353c..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Definition;
-
-use Traversable;
-
-use function array_filter;
-use function array_map;
-use function array_values;
-use function count;
-use function is_a;
-
-/**
- * @phpstan-type AttributeParam = array{class: class-string, callback: callable(): object}
- *
- * @internal
- */
-final class AttributesContainer implements Attributes
-{
-    private static self $empty;
-
-    /** @var list<AttributeParam> */
-    private array $attributes;
-
-    /**
-     * @no-named-arguments
-     * @param AttributeParam ...$attributes
-     */
-    public function __construct(array ...$attributes)
-    {
-        $this->attributes = $attributes;
-    }
-
-    public static function empty(): self
-    {
-        return self::$empty ??= new self();
-    }
-
-    public function has(string $className): bool
-    {
-        foreach ($this->attributes as $attribute) {
-            if (is_a($attribute['class'], $className, true)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    public function ofType(string $className): array
-    {
-        $attributes = array_filter(
-            $this->attributes,
-            static fn (array $attribute): bool => is_a($attribute['class'], $className, true)
-        );
-
-        /** @phpstan-ignore-next-line  */
-        return array_values(array_map(
-            fn (array $attribute) => $attribute['callback'](),
-            $attributes
-        ));
-    }
-
-    public function count(): int
-    {
-        return count($this->attributes);
-    }
-
-    /**
-     * @return Traversable<object>
-     */
-    public function getIterator(): Traversable
-    {
-        foreach ($this->attributes as $attribute) {
-            yield $attribute['callback']();
-        }
-    }
-}
index 74b11d49e755f1e0395f604fdb1f885db48a41fe..e3bd41ce8fce6af31ba4da0000279d85ef021e9f 100644 (file)
@@ -4,55 +4,19 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition;
 
-use CuyZ\Valinor\Type\ClassType;
+use CuyZ\Valinor\Type\ObjectType;
 
 /** @internal */
 final class ClassDefinition
 {
     public function __construct(
-        private ClassType $type,
-        private Attributes $attributes,
-        private Properties $properties,
-        private Methods $methods,
-        private bool $isFinal,
-        private bool $isAbstract,
+        /** @var class-string */
+        public readonly string $name,
+        public readonly ObjectType $type,
+        public readonly Attributes $attributes,
+        public readonly Properties $properties,
+        public readonly Methods $methods,
+        public readonly bool $isFinal,
+        public readonly bool $isAbstract,
     ) {}
-
-    /**
-     * @return class-string
-     */
-    public function name(): string
-    {
-        return $this->type->className();
-    }
-
-    public function type(): ClassType
-    {
-        return $this->type;
-    }
-
-    public function attributes(): Attributes
-    {
-        return $this->attributes;
-    }
-
-    public function properties(): Properties
-    {
-        return $this->properties;
-    }
-
-    public function methods(): Methods
-    {
-        return $this->methods;
-    }
-
-    public function isFinal(): bool
-    {
-        return $this->isFinal;
-    }
-
-    public function isAbstract(): bool
-    {
-        return $this->isAbstract;
-    }
 }
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/ExtendTagTypeError.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/ExtendTagTypeError.php
new file mode 100644 (file)
index 0000000..1ec0424
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Definition\Exception;
+
+use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
+use ReflectionClass;
+use RuntimeException;
+
+/** @internal */
+final class ExtendTagTypeError extends RuntimeException
+{
+    /**
+     * @param ReflectionClass<object> $reflection
+     */
+    public function __construct(ReflectionClass $reflection, InvalidType $previous)
+    {
+        parent::__construct(
+            "The `@extends` tag of the class `$reflection->name` is not valid: {$previous->getMessage()}",
+            1670193574,
+            $previous,
+        );
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/InvalidExtendTagClassName.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/InvalidExtendTagClassName.php
new file mode 100644 (file)
index 0000000..4e7066b
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Definition\Exception;
+
+use CuyZ\Valinor\Type\Type;
+use ReflectionClass;
+use RuntimeException;
+
+/** @internal */
+final class InvalidExtendTagClassName extends RuntimeException
+{
+    /**
+     * @param ReflectionClass<object> $reflection
+     */
+    public function __construct(ReflectionClass $reflection, Type $invalidExtendTag)
+    {
+        /** @var ReflectionClass<object> $parentClass */
+        $parentClass = $reflection->getParentClass();
+
+        parent::__construct(
+            "The `@extends` tag of the class `$reflection->name` has invalid class `{$invalidExtendTag->toString()}`, it should be `$parentClass->name`.",
+            1670183564,
+        );
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/InvalidExtendTagType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/InvalidExtendTagType.php
new file mode 100644 (file)
index 0000000..dd7a90f
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Definition\Exception;
+
+use CuyZ\Valinor\Type\Type;
+use ReflectionClass;
+use RuntimeException;
+
+/** @internal */
+final class InvalidExtendTagType extends RuntimeException
+{
+    /**
+     * @param ReflectionClass<object> $reflection
+     */
+    public function __construct(ReflectionClass $reflection, Type $invalidExtendTag)
+    {
+        /** @var ReflectionClass<object> $parentClass */
+        $parentClass = $reflection->getParentClass();
+
+        parent::__construct(
+            "The `@extends` tag of the class `$reflection->name` has invalid type `{$invalidExtendTag->toString()}`, it should be `{$parentClass->name}`.",
+            1670181134,
+        );
+    }
+}
index 6175086ea6da8693cdd1ff080dd3f27417499526..8831bce24ad029baf8b4929db93b23459f54193c 100644 (file)
@@ -4,13 +4,13 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Exception;
 
-use CuyZ\Valinor\Type\ClassType;
+use CuyZ\Valinor\Type\ObjectType;
 use LogicException;
 
 /** @internal */
 final class InvalidTypeAliasImportClass extends LogicException
 {
-    public function __construct(ClassType $type, string $className)
+    public function __construct(ObjectType $type, string $className)
     {
         parent::__construct(
             "Cannot import a type alias from unknown class `$className` in class `{$type->className()}`.",
index e2684323d53dd2c46c493cfd516a5d77a9b6e853..f5f91cca87c135d60c2a60d8c8d50d2485fbc71e 100644 (file)
@@ -4,14 +4,14 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Exception;
 
+use CuyZ\Valinor\Type\ObjectType;
 use CuyZ\Valinor\Type\Type;
-use CuyZ\Valinor\Type\ClassType;
 use LogicException;
 
 /** @internal */
 final class InvalidTypeAliasImportClassType extends LogicException
 {
-    public function __construct(ClassType $classType, Type $type)
+    public function __construct(ObjectType $classType, Type $type)
     {
         parent::__construct(
             "Importing a type alias can only be done with classes, `{$type->toString()}` was given in class `{$classType->className()}`.",
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/SeveralExtendTagsFound.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/SeveralExtendTagsFound.php
new file mode 100644 (file)
index 0000000..231141f
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Definition\Exception;
+
+use ReflectionClass;
+use RuntimeException;
+
+/** @internal */
+final class SeveralExtendTagsFound extends RuntimeException
+{
+    /**
+     * @param ReflectionClass<object> $reflection
+     */
+    public function __construct(ReflectionClass $reflection)
+    {
+        parent::__construct(
+            "Only one `@extends` tag should be set for the class `$reflection->name`.",
+            1670195494,
+        );
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/TypesDoNotMatch.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Exception/TypesDoNotMatch.php
deleted file mode 100644 (file)
index 603b949..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Definition\Exception;
-
-use CuyZ\Valinor\Type\Type;
-use CuyZ\Valinor\Utility\Reflection\Reflection;
-use LogicException;
-use ReflectionFunctionAbstract;
-use ReflectionParameter;
-use ReflectionProperty;
-
-/** @internal */
-final class TypesDoNotMatch extends LogicException
-{
-    public function __construct(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection, Type $typeFromDocBlock, Type $typeFromReflection)
-    {
-        $signature = Reflection::signature($reflection);
-
-        if ($reflection instanceof ReflectionProperty) {
-            $message = "Types for property `$signature` do not match: `{$typeFromDocBlock->toString()}` (docblock) does not accept `{$typeFromReflection->toString()}` (native).";
-        } elseif ($reflection instanceof ReflectionParameter) {
-            $message = "Types for parameter `$signature` do not match: `{$typeFromDocBlock->toString()}` (docblock) does not accept `{$typeFromReflection->toString()}` (native).";
-        } else {
-            $message = "Return types for method `$signature` do not match: `{$typeFromDocBlock->toString()}` (docblock) does not accept `{$typeFromReflection->toString()}` (native).";
-        }
-
-        parent::__construct($message, 1638471381);
-    }
-}
index 5f304b1d657f5efa98383ffa7e162bad92976769..def92da0e86ade6e908d07cfd067bc05c562a246 100644 (file)
@@ -4,7 +4,7 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Exception;
 
-use CuyZ\Valinor\Type\ClassType;
+use CuyZ\Valinor\Type\ObjectType;
 use LogicException;
 
 /** @internal */
@@ -13,7 +13,7 @@ final class UnknownTypeAliasImport extends LogicException
     /**
      * @param class-string $importClassName
      */
-    public function __construct(ClassType $type, string $importClassName, string $alias)
+    public function __construct(ObjectType $type, string $importClassName, string $alias)
     {
         parent::__construct(
             "Type alias `$alias` imported in `{$type->className()}` could not be found in `$importClassName`",
index 77b6ddfb4ab6fafe800d4087d199b6f9d5482eab..6aa45d44fc8d603c98d71f4654380741f2682360 100644 (file)
@@ -10,63 +10,18 @@ use CuyZ\Valinor\Type\Type;
 final class FunctionDefinition
 {
     public function __construct(
-        private string $name,
-        private string $signature,
-        private Attributes $attributes,
-        private ?string $fileName,
+        /** @var non-empty-string */
+        public readonly string $name,
+        /** @var non-empty-string */
+        public readonly string $signature,
+        public readonly Attributes $attributes,
+        /** @var non-empty-string|null */
+        public readonly ?string $fileName,
         /** @var class-string|null */
-        private ?string $class,
-        private bool $isStatic,
-        private bool $isClosure,
-        private Parameters $parameters,
-        private Type $returnType
+        public readonly ?string $class,
+        public readonly bool $isStatic,
+        public readonly bool $isClosure,
+        public readonly Parameters $parameters,
+        public readonly Type $returnType
     ) {}
-
-    public function name(): string
-    {
-        return $this->name;
-    }
-
-    public function signature(): string
-    {
-        return $this->signature;
-    }
-
-    public function attributes(): Attributes
-    {
-        return $this->attributes;
-    }
-
-    public function fileName(): ?string
-    {
-        return $this->fileName;
-    }
-
-    /**
-     * @return class-string|null
-     */
-    public function class(): ?string
-    {
-        return $this->class;
-    }
-
-    public function isStatic(): bool
-    {
-        return $this->isStatic;
-    }
-
-    public function isClosure(): bool
-    {
-        return $this->isClosure;
-    }
-
-    public function parameters(): Parameters
-    {
-        return $this->parameters;
-    }
-
-    public function returnType(): Type
-    {
-        return $this->returnType;
-    }
 }
index 59b811cd4b7bfe3996fe967e71d9f3834caeec82..e3777d042dc6e383a87b72a5e97351c8a7f24536 100644 (file)
@@ -7,24 +7,14 @@ namespace CuyZ\Valinor\Definition;
 /** @internal */
 final class FunctionObject
 {
-    private FunctionDefinition $definition;
+    public readonly FunctionDefinition $definition;
 
     /** @var callable */
-    private $callback;
+    public readonly mixed $callback;
 
     public function __construct(FunctionDefinition $definition, callable $callback)
     {
         $this->definition = $definition;
         $this->callback = $callback;
     }
-
-    public function definition(): FunctionDefinition
-    {
-        return $this->definition;
-    }
-
-    public function callback(): callable
-    {
-        return $this->callback;
-    }
 }
index a04e8928d579a01405647bb014571726d618c3d1..ce043a8ed82370f61cbd8c0c76b636696e019d1d 100644 (file)
@@ -10,41 +10,14 @@ use CuyZ\Valinor\Type\Type;
 final class MethodDefinition
 {
     public function __construct(
-        private string $name,
-        private string $signature,
-        private Parameters $parameters,
-        private bool $isStatic,
-        private bool $isPublic,
-        private Type $returnType
+        /** @var non-empty-string */
+        public readonly string $name,
+        /** @var non-empty-string */
+        public readonly string $signature,
+        public readonly Attributes $attributes,
+        public readonly Parameters $parameters,
+        public readonly bool $isStatic,
+        public readonly bool $isPublic,
+        public readonly Type $returnType
     ) {}
-
-    public function name(): string
-    {
-        return $this->name;
-    }
-
-    public function signature(): string
-    {
-        return $this->signature;
-    }
-
-    public function parameters(): Parameters
-    {
-        return $this->parameters;
-    }
-
-    public function isStatic(): bool
-    {
-        return $this->isStatic;
-    }
-
-    public function isPublic(): bool
-    {
-        return $this->isPublic;
-    }
-
-    public function returnType(): Type
-    {
-        return $this->returnType;
-    }
 }
index 4c348660fb051ee2620333d4e8eb7661748d933a..b572c2e519bd06b3336660130429cfd3173811a8 100644 (file)
@@ -21,7 +21,7 @@ final class Methods implements IteratorAggregate, Countable
     public function __construct(MethodDefinition ...$methods)
     {
         foreach ($methods as $method) {
-            $this->methods[$method->name()] = $method;
+            $this->methods[$method->name] = $method;
         }
     }
 
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/NativeAttributes.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/NativeAttributes.php
deleted file mode 100644 (file)
index 064702a..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Definition;
-
-use Error;
-use ReflectionAttribute;
-use ReflectionClass;
-use ReflectionFunction;
-use ReflectionMethod;
-use ReflectionParameter;
-use ReflectionProperty;
-use Traversable;
-
-use function array_map;
-
-/** @internal */
-final class NativeAttributes implements Attributes
-{
-    private AttributesContainer $delegate;
-
-    /** @var array<class-string, array<mixed>> */
-    private array $definition = [];
-
-    /**
-     * @param ReflectionClass<object>|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflection
-     */
-    public function __construct(ReflectionClass|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflection)
-    {
-        $attributes = array_filter(
-            array_map(
-                static function (ReflectionAttribute $attribute) {
-                    try {
-                        $instance = $attribute->newInstance();
-
-                        return [
-                            'class' => $attribute->getName(),
-                            'callback' => fn () => $instance
-                        ];
-                    } catch (Error) {
-                        // Race condition when the attribute is affected to a property/parameter
-                        // that was PROMOTED, in this case the attribute will be applied to both
-                        // ParameterReflection AND PropertyReflection, BUT the target arg inside the attribute
-                        // class is configured to support only ONE of them (parameter OR property)
-                        // https://wiki.php.net/rfc/constructor_promotion#attributes for more details.
-                        // Ignore attribute if the instantiation failed.
-                        return null;
-                    }
-                },
-                $reflection->getAttributes(),
-            ),
-        );
-
-        foreach ($reflection->getAttributes() as $attribute) {
-            $this->definition[$attribute->getName()] = $attribute->getArguments();
-        }
-
-        $this->delegate = new AttributesContainer(...$attributes);
-    }
-
-    public function has(string $className): bool
-    {
-        return $this->delegate->has($className);
-    }
-
-    public function ofType(string $className): array
-    {
-        return $this->delegate->ofType($className);
-    }
-
-    public function getIterator(): Traversable
-    {
-        yield from $this->delegate;
-    }
-
-    public function count(): int
-    {
-        return count($this->delegate);
-    }
-
-    /**
-     * @return array<class-string, array<mixed>>
-     */
-    public function definition(): array
-    {
-        return $this->definition;
-    }
-}
index 6ea27a6c67546be9dc548791b6cb657ccf7ea4b3..7d4e1629e029c1ffea91e1a8d2de652109c3ad0c 100644 (file)
@@ -10,47 +10,15 @@ use CuyZ\Valinor\Type\Type;
 final class ParameterDefinition
 {
     public function __construct(
-        private string $name,
-        private string $signature,
-        private Type $type,
-        private bool $isOptional,
-        private bool $isVariadic,
-        private mixed $defaultValue,
-        private Attributes $attributes
+        /** @var non-empty-string */
+        public readonly string $name,
+        /** @var non-empty-string */
+        public readonly string $signature,
+        public readonly Type $type,
+        public readonly Type $nativeType,
+        public readonly bool $isOptional,
+        public readonly bool $isVariadic,
+        public readonly mixed $defaultValue,
+        public readonly Attributes $attributes
     ) {}
-
-    public function name(): string
-    {
-        return $this->name;
-    }
-
-    public function signature(): string
-    {
-        return $this->signature;
-    }
-
-    public function type(): Type
-    {
-        return $this->type;
-    }
-
-    public function isOptional(): bool
-    {
-        return $this->isOptional;
-    }
-
-    public function isVariadic(): bool
-    {
-        return $this->isVariadic;
-    }
-
-    public function defaultValue(): mixed
-    {
-        return $this->defaultValue;
-    }
-
-    public function attributes(): Attributes
-    {
-        return $this->attributes;
-    }
 }
index 7177a0b3d7b5a933bf5d41507bca412f81433790..c659b5be4448d47fa8fe6a6e9af94c9e5c14f438 100644 (file)
@@ -23,7 +23,7 @@ final class Parameters implements IteratorAggregate, Countable
     public function __construct(ParameterDefinition ...$parameters)
     {
         foreach ($parameters as $parameter) {
-            $this->parameters[$parameter->name()] = $parameter;
+            $this->parameters[$parameter->name] = $parameter;
         }
     }
 
index f15c6b3b33260c205039b7ecc5549e8b71ec2db0..5472cfca3c6fbb0db1929701e50a7764d3ecf1b0 100644 (file)
@@ -21,7 +21,7 @@ final class Properties implements IteratorAggregate, Countable
     public function __construct(PropertyDefinition ...$properties)
     {
         foreach ($properties as $property) {
-            $this->properties[$property->name()] = $property;
+            $this->properties[$property->name] = $property;
         }
     }
 
index ac0aaaee1622ca96d1db873ba9df574e65220a75..0e249319e6eac12bdb1fcf9a1fb54dd7bb4e07ff 100644 (file)
@@ -10,47 +10,15 @@ use CuyZ\Valinor\Type\Type;
 final class PropertyDefinition
 {
     public function __construct(
-        private string $name,
-        private string $signature,
-        private Type $type,
-        private bool $hasDefaultValue,
-        private mixed $defaultValue,
-        private bool $isPublic,
-        private Attributes $attributes
+        /** @var non-empty-string */
+        public readonly string $name,
+        /** @var non-empty-string */
+        public readonly string $signature,
+        public readonly Type $type,
+        public readonly Type $nativeType,
+        public readonly bool $hasDefaultValue,
+        public readonly mixed $defaultValue,
+        public readonly bool $isPublic,
+        public readonly Attributes $attributes
     ) {}
-
-    public function name(): string
-    {
-        return $this->name;
-    }
-
-    public function signature(): string
-    {
-        return $this->signature;
-    }
-
-    public function type(): Type
-    {
-        return $this->type;
-    }
-
-    public function hasDefaultValue(): bool
-    {
-        return $this->hasDefaultValue;
-    }
-
-    public function defaultValue(): mixed
-    {
-        return $this->defaultValue;
-    }
-
-    public function isPublic(): bool
-    {
-        return $this->isPublic;
-    }
-
-    public function attributes(): Attributes
-    {
-        return $this->attributes;
-    }
 }
index 9eff034d4a76bd73e0888fefff00a3dcaee23025..a5291373ed8a42d5d24476dd4b357de22e1d0fa0 100644 (file)
@@ -4,18 +4,14 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Repository;
 
-use CuyZ\Valinor\Definition\Attributes;
-use ReflectionClass;
-use ReflectionFunction;
-use ReflectionMethod;
-use ReflectionParameter;
-use ReflectionProperty;
+use CuyZ\Valinor\Definition\AttributeDefinition;
+use ReflectionAttribute;
 
 /** @internal */
 interface AttributesRepository
 {
     /**
-     * @param ReflectionClass<object>|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflector
+     * @param ReflectionAttribute<object> $reflection
      */
-    public function for(ReflectionClass|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflector): Attributes;
+    public function for(ReflectionAttribute $reflection): AttributeDefinition;
 }
index e1307d330d74f6ac0e9047229b94f0c93f0c440b..c23cdfa8e23c468b06377a48986ea7d77de768a2 100644 (file)
@@ -6,9 +6,11 @@ namespace CuyZ\Valinor\Definition\Repository\Cache;
 
 use CuyZ\Valinor\Definition\ClassDefinition;
 use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
-use CuyZ\Valinor\Type\ClassType;
+use CuyZ\Valinor\Type\ObjectType;
 use Psr\SimpleCache\CacheInterface;
 
+use function sha1;
+
 /** @internal */
 final class CacheClassDefinitionRepository implements ClassDefinitionRepository
 {
@@ -18,9 +20,10 @@ final class CacheClassDefinitionRepository implements ClassDefinitionRepository
         private CacheInterface $cache
     ) {}
 
-    public function for(ClassType $type): ClassDefinition
+    public function for(ObjectType $type): ClassDefinition
     {
-        $key = "class-definition-{$type->toString()}";
+        // @infection-ignore-all
+        $key = 'class-definition' . sha1($type->toString());
 
         $entry = $this->cache->get($key);
 
index 65dec14647e03215401de14ab3e2feee739e3397..93a24be2715f29f2124da568482d4858f80d9cba 100644 (file)
@@ -9,6 +9,8 @@ use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
 use CuyZ\Valinor\Utility\Reflection\Reflection;
 use Psr\SimpleCache\CacheInterface;
 
+use function sha1;
+
 /** @internal */
 final class CacheFunctionDefinitionRepository implements FunctionDefinitionRepository
 {
@@ -21,7 +23,9 @@ final class CacheFunctionDefinitionRepository implements FunctionDefinitionRepos
     public function for(callable $function): FunctionDefinition
     {
         $reflection = Reflection::function($function);
-        $key = "function-definition-{$reflection->getFileName()}-{$reflection->getStartLine()}-{$reflection->getEndLine()}";
+
+        // @infection-ignore-all
+        $key = 'function-definition-' . sha1($reflection->getFileName() . $reflection->getStartLine() . $reflection->getEndLine());
 
         $entry = $this->cache->get($key);
 
index 8870630787ca343b1059937b0f2e7f4944bef8c7..990c26203644722698b117c1913d1aa2055e65ab 100644 (file)
@@ -5,8 +5,6 @@ declare(strict_types=1);
 namespace CuyZ\Valinor\Definition\Repository\Cache\Compiler;
 
 use CuyZ\Valinor\Definition\Attributes;
-use CuyZ\Valinor\Definition\AttributesContainer;
-use CuyZ\Valinor\Definition\NativeAttributes;
 
 use function count;
 use function implode;
@@ -17,29 +15,35 @@ use function var_export;
 /** @internal */
 final class AttributesCompiler
 {
+    public function __construct(private ClassDefinitionCompiler $classDefinitionCompiler) {}
+
     public function compile(Attributes $attributes): string
     {
         if (count($attributes) === 0) {
-            return AttributesContainer::class . '::empty()';
+            return Attributes::class . '::empty()';
         }
 
-        assert($attributes instanceof NativeAttributes);
-
-        $attributesListCode = $this->compileNativeAttributes($attributes);
+        $attributesListCode = $this->compileAttributes($attributes);
 
         return <<<PHP
-            new \CuyZ\Valinor\Definition\AttributesContainer($attributesListCode)
+            new \CuyZ\Valinor\Definition\Attributes($attributesListCode)
             PHP;
     }
 
-    private function compileNativeAttributes(NativeAttributes $attributes): string
+    private function compileAttributes(Attributes $attributes): string
     {
         $attributesListCode = [];
 
-        foreach ($attributes->definition() as $className => $arguments) {
-            $argumentsCode = $this->compileAttributeArguments($arguments);
+        foreach ($attributes as $attribute) {
+            $class = $this->classDefinitionCompiler->compile($attribute->class);
+            $arguments = $this->compileAttributeArguments($attribute->arguments);
 
-            $attributesListCode[] = "['class' => '$className', 'callback' => fn () => new $className($argumentsCode)]";
+            $attributesListCode[] = <<<PHP
+            new \CuyZ\Valinor\Definition\AttributeDefinition(
+                $class,
+                [$arguments],
+            )
+            PHP;
         }
 
         return implode(', ', $attributesListCode);
index ef24df55fa5e4bf71f850270c44661750cac479d..7616ee46867961e7a45311fd121e909eba1c59f7 100644 (file)
@@ -4,7 +4,6 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Repository\Cache\Compiler;
 
-use CuyZ\Valinor\Cache\Compiled\CacheCompiler;
 use CuyZ\Valinor\Definition\ClassDefinition;
 use CuyZ\Valinor\Definition\MethodDefinition;
 use CuyZ\Valinor\Definition\PropertyDefinition;
@@ -13,9 +12,10 @@ use function array_map;
 use function assert;
 use function implode;
 use function iterator_to_array;
+use function var_export;
 
 /** @internal */
-final class ClassDefinitionCompiler implements CacheCompiler
+final class ClassDefinitionCompiler
 {
     private TypeCompiler $typeCompiler;
 
@@ -28,7 +28,7 @@ final class ClassDefinitionCompiler implements CacheCompiler
     public function __construct()
     {
         $this->typeCompiler = new TypeCompiler();
-        $this->attributesCompiler = new AttributesCompiler();
+        $this->attributesCompiler = new AttributesCompiler($this);
 
         $this->methodCompiler = new MethodDefinitionCompiler($this->typeCompiler, $this->attributesCompiler);
         $this->propertyCompiler = new PropertyDefinitionCompiler($this->typeCompiler, $this->attributesCompiler);
@@ -38,28 +38,30 @@ final class ClassDefinitionCompiler implements CacheCompiler
     {
         assert($value instanceof ClassDefinition);
 
-        $type = $this->typeCompiler->compile($value->type());
+        $name = var_export($value->name, true);
+        $type = $this->typeCompiler->compile($value->type);
 
         $properties = array_map(
             fn (PropertyDefinition $property) => $this->propertyCompiler->compile($property),
-            iterator_to_array($value->properties())
+            iterator_to_array($value->properties)
         );
 
         $properties = implode(', ', $properties);
 
         $methods = array_map(
             fn (MethodDefinition $method) => $this->methodCompiler->compile($method),
-            iterator_to_array($value->methods())
+            iterator_to_array($value->methods)
         );
 
         $methods = implode(', ', $methods);
-        $attributes = $this->attributesCompiler->compile($value->attributes());
+        $attributes = $this->attributesCompiler->compile($value->attributes);
 
-        $isFinal = var_export($value->isFinal(), true);
-        $isAbstract = var_export($value->isAbstract(), true);
+        $isFinal = var_export($value->isFinal, true);
+        $isAbstract = var_export($value->isAbstract, true);
 
         return <<<PHP
         new \CuyZ\Valinor\Definition\ClassDefinition(
+            $name,
             $type,
             $attributes,
             new \CuyZ\Valinor\Definition\Properties($properties),
index f885653eccb8bda7a26e29d1347c0c915dd9f129..9235f686f2d581e65d13d3d53a842ce0a263bd91 100644 (file)
@@ -4,14 +4,13 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Repository\Cache\Compiler;
 
-use CuyZ\Valinor\Cache\Compiled\CacheCompiler;
 use CuyZ\Valinor\Definition\FunctionDefinition;
 use CuyZ\Valinor\Definition\ParameterDefinition;
 
 use function var_export;
 
 /** @internal */
-final class FunctionDefinitionCompiler implements CacheCompiler
+final class FunctionDefinitionCompiler
 {
     private TypeCompiler $typeCompiler;
 
@@ -22,9 +21,9 @@ final class FunctionDefinitionCompiler implements CacheCompiler
     public function __construct()
     {
         $this->typeCompiler = new TypeCompiler();
-        $this->attributesCompiler = new AttributesCompiler();
+        $this->attributesCompiler = new AttributesCompiler(new ClassDefinitionCompiler());
 
-        $this->parameterCompiler = new ParameterDefinitionCompiler($this->typeCompiler, new AttributesCompiler());
+        $this->parameterCompiler = new ParameterDefinitionCompiler($this->typeCompiler, $this->attributesCompiler);
     }
 
     public function compile(mixed $value): string
@@ -33,21 +32,21 @@ final class FunctionDefinitionCompiler implements CacheCompiler
 
         $parameters = array_map(
             fn (ParameterDefinition $parameter) => $this->parameterCompiler->compile($parameter),
-            iterator_to_array($value->parameters())
+            iterator_to_array($value->parameters)
         );
 
-        $attributes = $this->attributesCompiler->compile($value->attributes());
-        $fileName = var_export($value->fileName(), true);
-        $class = var_export($value->class(), true);
-        $isStatic = var_export($value->isStatic(), true);
-        $isClosure = var_export($value->isClosure(), true);
+        $attributes = $this->attributesCompiler->compile($value->attributes);
+        $fileName = var_export($value->fileName, true);
+        $class = var_export($value->class, true);
+        $isStatic = var_export($value->isStatic, true);
+        $isClosure = var_export($value->isClosure, true);
         $parameters = implode(', ', $parameters);
-        $returnType = $this->typeCompiler->compile($value->returnType());
+        $returnType = $this->typeCompiler->compile($value->returnType);
 
         return <<<PHP
             new \CuyZ\Valinor\Definition\FunctionDefinition(
-                '{$value->name()}',
-                '{$value->signature()}',
+                '{$value->name}',
+                '{$value->signature}',
                 $attributes,
                 $fileName,
                 $class,
index 282fa1bc5092a422bf2aad41191e320265965c59..1176cca01c7b2d153dc0fae491c64f63222371cc 100644 (file)
@@ -14,30 +14,36 @@ final class MethodDefinitionCompiler
 {
     private TypeCompiler $typeCompiler;
 
+    private AttributesCompiler $attributesCompiler;
+
     private ParameterDefinitionCompiler $parameterCompiler;
 
     public function __construct(TypeCompiler $typeCompiler, AttributesCompiler $attributesCompiler)
     {
         $this->typeCompiler = $typeCompiler;
+        $this->attributesCompiler = $attributesCompiler;
         $this->parameterCompiler = new ParameterDefinitionCompiler($typeCompiler, $attributesCompiler);
     }
 
     public function compile(MethodDefinition $method): string
     {
+        $attributes = $this->attributesCompiler->compile($method->attributes);
+
         $parameters = array_map(
             fn (ParameterDefinition $parameter) => $this->parameterCompiler->compile($parameter),
-            iterator_to_array($method->parameters())
+            iterator_to_array($method->parameters)
         );
 
         $parameters = implode(', ', $parameters);
-        $isStatic = var_export($method->isStatic(), true);
-        $isPublic = var_export($method->isPublic(), true);
-        $returnType = $this->typeCompiler->compile($method->returnType());
+        $isStatic = var_export($method->isStatic, true);
+        $isPublic = var_export($method->isPublic, true);
+        $returnType = $this->typeCompiler->compile($method->returnType);
 
         return <<<PHP
             new \CuyZ\Valinor\Definition\MethodDefinition(
-                '{$method->name()}',
-                '{$method->signature()}',
+                '{$method->name}',
+                '{$method->signature}',
+                $attributes,
                 new \CuyZ\Valinor\Definition\Parameters($parameters),
                 $isStatic,
                 $isPublic,
index be0a590fdd3e24c39d960cf97c25d4bec85241d9..e0b84ab649fea2ad9be6f8c879e68fc08921eaef 100644 (file)
@@ -16,17 +16,19 @@ final class ParameterDefinitionCompiler
 
     public function compile(ParameterDefinition $parameter): string
     {
-        $isOptional = var_export($parameter->isOptional(), true);
-        $isVariadic = var_export($parameter->isVariadic(), true);
+        $isOptional = var_export($parameter->isOptional, true);
+        $isVariadic = var_export($parameter->isVariadic, true);
         $defaultValue = $this->defaultValue($parameter);
-        $type = $this->typeCompiler->compile($parameter->type());
-        $attributes = $this->attributesCompiler->compile($parameter->attributes());
+        $type = $this->typeCompiler->compile($parameter->type);
+        $nativeType = $this->typeCompiler->compile($parameter->nativeType);
+        $attributes = $this->attributesCompiler->compile($parameter->attributes);
 
         return <<<PHP
             new \CuyZ\Valinor\Definition\ParameterDefinition(
-                '{$parameter->name()}',
-                '{$parameter->signature()}',
+                '{$parameter->name}',
+                '{$parameter->signature}',
                 $type,
+                $nativeType,
                 $isOptional,
                 $isVariadic,
                 $defaultValue,
@@ -37,7 +39,7 @@ final class ParameterDefinitionCompiler
 
     private function defaultValue(ParameterDefinition $parameter): string
     {
-        $defaultValue = $parameter->defaultValue();
+        $defaultValue = $parameter->defaultValue;
 
         return is_object($defaultValue)
             ? 'unserialize(' . var_export(serialize($defaultValue), true) . ')'
index 3a6fb9f08766f06e9d6de4ecb2ea3bbc6e992e03..070c8b20385d067ba217b8e864eccae20b426422 100644 (file)
@@ -16,17 +16,19 @@ final class PropertyDefinitionCompiler
 
     public function compile(PropertyDefinition $property): string
     {
-        $type = $this->typeCompiler->compile($property->type());
-        $hasDefaultValue = var_export($property->hasDefaultValue(), true);
-        $defaultValue = var_export($property->defaultValue(), true);
-        $isPublic = var_export($property->isPublic(), true);
-        $attributes = $this->attributesCompiler->compile($property->attributes());
+        $type = $this->typeCompiler->compile($property->type);
+        $nativeType = $this->typeCompiler->compile($property->nativeType);
+        $hasDefaultValue = var_export($property->hasDefaultValue, true);
+        $defaultValue = var_export($property->defaultValue, true);
+        $isPublic = var_export($property->isPublic, true);
+        $attributes = $this->attributesCompiler->compile($property->attributes);
 
         return <<<PHP
             new \CuyZ\Valinor\Definition\PropertyDefinition(
-                '{$property->name()}',
-                '{$property->signature()}',
+                '{$property->name}',
+                '{$property->signature}',
                 $type,
+                $nativeType,
                 $hasDefaultValue,
                 $defaultValue,
                 $isPublic,
index 1b05675e27a001ff371a3123f07a9174357f2d27..adebebd901dc7c8a2612d8fe308eda36dcb692b9 100644 (file)
@@ -97,13 +97,20 @@ final class TypeCompiler
                     default => "$class::default()",
                 };
             case $type instanceof ShapedArrayType:
-                $shapes = array_map(
+                $elements = implode(', ', array_map(
                     fn (ShapedArrayElement $element) => $this->compileArrayShapeElement($element),
                     $type->elements()
-                );
-                $shapes = implode(', ', $shapes);
+                ));
+
+                if ($type->hasUnsealedType()) {
+                    $unsealedType = $this->compile($type->unsealedType());
+
+                    return "$class::unsealed($unsealedType, $elements)";
+                } elseif ($type->isUnsealed()) {
+                    return "$class::unsealedWithoutType($elements)";
+                }
 
-                return "new $class(...[$shapes])";
+                return "new $class($elements)";
             case $type instanceof ArrayType:
             case $type instanceof NonEmptyArrayType:
                 if ($type->toString() === 'array' || $type->toString() === 'non-empty-array') {
@@ -138,15 +145,7 @@ final class TypeCompiler
 
                 $generics = implode(', ', $generics);
 
-                if ($type instanceof InterfaceType) {
-                    return "new $class('{$type->className()}', [$generics])";
-                }
-
-                $parent = $type->hasParent()
-                    ? $this->compile($type->parent())
-                    : 'null';
-
-                return "new $class('{$type->className()}', [$generics], $parent)";
+                return "new $class('{$type->className()}', [$generics])";
             case $type instanceof ClassStringType:
                 if (null === $type->subType()) {
                     return "new $class()";
index 59517cb8afa9debcb6d4831fffdb5be8f6866425..7d742843f13f88df8b8f453ce20b4ac2c325d3d5 100644 (file)
@@ -5,10 +5,10 @@ declare(strict_types=1);
 namespace CuyZ\Valinor\Definition\Repository;
 
 use CuyZ\Valinor\Definition\ClassDefinition;
-use CuyZ\Valinor\Type\ClassType;
+use CuyZ\Valinor\Type\ObjectType;
 
 /** @internal */
 interface ClassDefinitionRepository
 {
-    public function for(ClassType $type): ClassDefinition;
+    public function for(ObjectType $type): ClassDefinition;
 }
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/NativeAttributesRepository.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/NativeAttributesRepository.php
deleted file mode 100644 (file)
index 9da982e..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Definition\Repository\Reflection;
-
-use CuyZ\Valinor\Definition\NativeAttributes;
-use CuyZ\Valinor\Definition\Repository\AttributesRepository;
-use Reflector;
-
-/** @internal */
-final class NativeAttributesRepository implements AttributesRepository
-{
-    public function for(Reflector $reflector): NativeAttributes
-    {
-        return new NativeAttributes($reflector);
-    }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php
new file mode 100644 (file)
index 0000000..f89dd4a
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Definition\Repository\Reflection;
+
+use CuyZ\Valinor\Definition\AttributeDefinition;
+use CuyZ\Valinor\Definition\Repository\AttributesRepository;
+use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
+use CuyZ\Valinor\Type\Types\NativeClassType;
+use ReflectionAttribute;
+
+/** @internal */
+final class ReflectionAttributesRepository implements AttributesRepository
+{
+    public function __construct(private ClassDefinitionRepository $classDefinitionRepository) {}
+
+    public function for(ReflectionAttribute $reflection): AttributeDefinition
+    {
+        $class = $this->classDefinitionRepository->for(new NativeClassType($reflection->getName()));
+
+        return new AttributeDefinition(
+            $class,
+            $reflection->getArguments(),
+        );
+    }
+}
index 7824749bd1b7ebc56eeb78305f0aaed9a0500b09..1d034aba5a3ea601351a652575c2f4fd47c158c3 100644 (file)
@@ -4,10 +4,16 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Repository\Reflection;
 
+use CuyZ\Valinor\Definition\AttributeDefinition;
+use CuyZ\Valinor\Definition\Attributes;
 use CuyZ\Valinor\Definition\ClassDefinition;
 use CuyZ\Valinor\Definition\Exception\ClassTypeAliasesDuplication;
+use CuyZ\Valinor\Definition\Exception\ExtendTagTypeError;
+use CuyZ\Valinor\Definition\Exception\InvalidExtendTagClassName;
+use CuyZ\Valinor\Definition\Exception\InvalidExtendTagType;
 use CuyZ\Valinor\Definition\Exception\InvalidTypeAliasImportClass;
 use CuyZ\Valinor\Definition\Exception\InvalidTypeAliasImportClassType;
+use CuyZ\Valinor\Definition\Exception\SeveralExtendTagsFound;
 use CuyZ\Valinor\Definition\Exception\UnknownTypeAliasImport;
 use CuyZ\Valinor\Definition\MethodDefinition;
 use CuyZ\Valinor\Definition\Methods;
@@ -15,21 +21,24 @@ use CuyZ\Valinor\Definition\Properties;
 use CuyZ\Valinor\Definition\PropertyDefinition;
 use CuyZ\Valinor\Definition\Repository\AttributesRepository;
 use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
-use CuyZ\Valinor\Type\ClassType;
 use CuyZ\Valinor\Type\GenericType;
+use CuyZ\Valinor\Type\ObjectType;
 use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
 use CuyZ\Valinor\Type\Parser\Factory\Specifications\AliasSpecification;
 use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
+use CuyZ\Valinor\Type\Parser\Factory\Specifications\GenericCheckerSpecification;
 use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
 use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
 use CuyZ\Valinor\Type\Parser\TypeParser;
 use CuyZ\Valinor\Type\Type;
+use CuyZ\Valinor\Type\Types\NativeClassType;
 use CuyZ\Valinor\Type\Types\UnresolvableType;
+use CuyZ\Valinor\Utility\Reflection\DocParser;
 use CuyZ\Valinor\Utility\Reflection\Reflection;
+use ReflectionAttribute;
 use ReflectionClass;
 use ReflectionMethod;
 use ReflectionProperty;
-use CuyZ\Valinor\Utility\Reflection\DocParser;
 
 use function array_filter;
 use function array_keys;
@@ -40,7 +49,7 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
 {
     private TypeParserFactory $typeParserFactory;
 
-    private AttributesRepository $attributesFactory;
+    private AttributesRepository $attributesRepository;
 
     private ReflectionPropertyDefinitionBuilder $propertyBuilder;
 
@@ -49,21 +58,22 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
     /** @var array<string, ReflectionTypeResolver> */
     private array $typeResolver = [];
 
-    public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesFactory)
+    public function __construct(TypeParserFactory $typeParserFactory)
     {
         $this->typeParserFactory = $typeParserFactory;
-        $this->attributesFactory = $attributesFactory;
-        $this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($attributesFactory);
-        $this->methodBuilder = new ReflectionMethodDefinitionBuilder($attributesFactory);
+        $this->attributesRepository = new ReflectionAttributesRepository($this);
+        $this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($this->attributesRepository);
+        $this->methodBuilder = new ReflectionMethodDefinitionBuilder($this->attributesRepository);
     }
 
-    public function for(ClassType $type): ClassDefinition
+    public function for(ObjectType $type): ClassDefinition
     {
         $reflection = Reflection::class($type->className());
 
         return new ClassDefinition(
+            $reflection->name,
             $type,
-            $this->attributesFactory->for($reflection),
+            new Attributes(...$this->attributes($reflection)),
             new Properties(...$this->properties($type)),
             new Methods(...$this->methods($type)),
             $reflection->isFinal(),
@@ -71,10 +81,22 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
         );
     }
 
+    /**
+     * @param ReflectionClass<object> $reflection
+     * @return list<AttributeDefinition>
+     */
+    private function attributes(ReflectionClass $reflection): array
+    {
+        return array_map(
+            fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
+            Reflection::attributes($reflection)
+        );
+    }
+
     /**
      * @return list<PropertyDefinition>
      */
-    private function properties(ClassType $type): array
+    private function properties(ObjectType $type): array
     {
         return array_map(
             function (ReflectionProperty $property) use ($type) {
@@ -82,14 +104,14 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
 
                 return $this->propertyBuilder->for($property, $typeResolver);
             },
-            Reflection::class($type->className())->getProperties()
+            Reflection::class($type->className())->getProperties(),
         );
     }
 
     /**
      * @return list<MethodDefinition>
      */
-    private function methods(ClassType $type): array
+    private function methods(ObjectType $type): array
     {
         $reflection = Reflection::class($type->className());
         $methods = $reflection->getMethods();
@@ -111,7 +133,7 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
     /**
      * @param ReflectionClass<object> $target
      */
-    private function typeResolver(ClassType $type, ReflectionClass $target): ReflectionTypeResolver
+    private function typeResolver(ObjectType $type, ReflectionClass $target): ReflectionTypeResolver
     {
         $typeKey = $target->isInterface()
             ? "{$type->toString()}/{$type->className()}"
@@ -122,7 +144,7 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
         }
 
         while ($type->className() !== $target->name) {
-            $type = $type->parent();
+            $type = $this->parentType($type);
         }
 
         $generics = $type instanceof GenericType ? $type->generics() : [];
@@ -147,11 +169,12 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
         $advancedParser = $this->typeParserFactory->get(
             new ClassContextSpecification($type->className()),
             new AliasSpecification(Reflection::class($type->className())),
-            new TypeAliasAssignerSpecification($generics + $localAliases + $importedAliases)
+            new TypeAliasAssignerSpecification($generics + $localAliases + $importedAliases),
+            new GenericCheckerSpecification(),
         );
 
         $nativeParser = $this->typeParserFactory->get(
-            new ClassContextSpecification($type->className())
+            new ClassContextSpecification($type->className()),
         );
 
         return $this->typeResolver[$typeKey] = new ReflectionTypeResolver($nativeParser, $advancedParser);
@@ -160,7 +183,7 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
     /**
      * @return array<string, Type>
      */
-    private function localTypeAliases(ClassType $type): array
+    private function localTypeAliases(ObjectType $type): array
     {
         $reflection = Reflection::class($type->className());
         $rawTypes = DocParser::localTypeAliases($reflection);
@@ -185,7 +208,7 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
     /**
      * @return array<string, Type>
      */
-    private function importedTypeAliases(ClassType $type): array
+    private function importedTypeAliases(ObjectType $type): array
     {
         $reflection = Reflection::class($type->className());
         $importedTypesRaw = DocParser::importedTypeAliases($reflection);
@@ -201,7 +224,7 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
                 throw new InvalidTypeAliasImportClass($type, $class);
             }
 
-            if (! $classType instanceof ClassType) {
+            if (! $classType instanceof ObjectType) {
                 throw new InvalidTypeAliasImportClassType($type, $classType);
             }
 
@@ -219,11 +242,12 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
         return $importedTypes;
     }
 
-    private function typeParser(ClassType $type): TypeParser
+    private function typeParser(ObjectType $type): TypeParser
     {
         $specs = [
             new ClassContextSpecification($type->className()),
             new AliasSpecification(Reflection::class($type->className())),
+            new GenericCheckerSpecification(),
         ];
 
         if ($type instanceof GenericType) {
@@ -232,4 +256,38 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
 
         return $this->typeParserFactory->get(...$specs);
     }
+
+    private function parentType(ObjectType $type): NativeClassType
+    {
+        $reflection = Reflection::class($type->className());
+
+        /** @var ReflectionClass<object> $parentReflection */
+        $parentReflection = $reflection->getParentClass();
+
+        $extendedClass = DocParser::classExtendsTypes($reflection);
+
+        if (count($extendedClass) > 1) {
+            throw new SeveralExtendTagsFound($reflection);
+        } elseif (count($extendedClass) === 0) {
+            $extendedClass = $parentReflection->name;
+        } else {
+            $extendedClass = $extendedClass[0];
+        }
+
+        try {
+            $parentType = $this->typeParser($type)->parse($extendedClass);
+        } catch (InvalidType $exception) {
+            throw new ExtendTagTypeError($reflection, $exception);
+        }
+
+        if (! $parentType instanceof NativeClassType) {
+            throw new InvalidExtendTagType($reflection, $parentType);
+        }
+
+        if ($parentType->className() !== $parentReflection->name) {
+            throw new InvalidExtendTagClassName($reflection, $parentType);
+        }
+
+        return $parentType;
+    }
 }
index 4dc72a31c81977dc4b263c3685f996a852c1ab92..3566476969c22eabd9f4f49964a91361c273cd03 100644 (file)
@@ -4,17 +4,22 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Repository\Reflection;
 
+use CuyZ\Valinor\Definition\AttributeDefinition;
+use CuyZ\Valinor\Definition\Attributes;
 use CuyZ\Valinor\Definition\FunctionDefinition;
 use CuyZ\Valinor\Definition\Parameters;
 use CuyZ\Valinor\Definition\Repository\AttributesRepository;
 use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
 use CuyZ\Valinor\Type\Parser\Factory\Specifications\AliasSpecification;
 use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
+use CuyZ\Valinor\Type\Parser\Factory\Specifications\GenericCheckerSpecification;
 use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
 use CuyZ\Valinor\Utility\Reflection\Reflection;
+use ReflectionAttribute;
 use ReflectionFunction;
 use ReflectionParameter;
 
+use function array_map;
 use function str_ends_with;
 
 /** @internal */
@@ -52,7 +57,7 @@ final class ReflectionFunctionDefinitionRepository implements FunctionDefinition
         return new FunctionDefinition(
             $name,
             Reflection::signature($reflection),
-            $this->attributesRepository->for($reflection),
+            new Attributes(...$this->attributes($reflection)),
             $reflection->getFileName() ?: null,
             $class?->name,
             $reflection->getClosureThis() === null,
@@ -67,7 +72,10 @@ final class ReflectionFunctionDefinitionRepository implements FunctionDefinition
         $class = $reflection->getClosureScopeClass();
 
         $nativeSpecifications = [];
-        $advancedSpecification = [new AliasSpecification($reflection)];
+        $advancedSpecification = [
+            new AliasSpecification($reflection),
+            new GenericCheckerSpecification(),
+        ];
 
         if ($class !== null) {
             $nativeSpecifications[] = new ClassContextSpecification($class->name);
@@ -79,4 +87,15 @@ final class ReflectionFunctionDefinitionRepository implements FunctionDefinition
 
         return new ReflectionTypeResolver($nativeParser, $advancedParser);
     }
+
+    /**
+     * @return list<AttributeDefinition>
+     */
+    private function attributes(ReflectionFunction $reflection): array
+    {
+        return array_map(
+            fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
+            Reflection::attributes($reflection)
+        );
+    }
 }
index b0c62e233b9c42c188d2c1dd4702afdbafb5d689..919c9d34be20aaec8c1768643b8cc9be852a7f7a 100644 (file)
@@ -4,10 +4,12 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Repository\Reflection;
 
+use CuyZ\Valinor\Definition\Attributes;
 use CuyZ\Valinor\Definition\MethodDefinition;
 use CuyZ\Valinor\Definition\Parameters;
 use CuyZ\Valinor\Definition\Repository\AttributesRepository;
 use CuyZ\Valinor\Utility\Reflection\Reflection;
+use ReflectionAttribute;
 use ReflectionMethod;
 use ReflectionParameter;
 
@@ -16,15 +18,26 @@ use function array_map;
 /** @internal */
 final class ReflectionMethodDefinitionBuilder
 {
+    private AttributesRepository $attributesRepository;
+
     private ReflectionParameterDefinitionBuilder $parameterBuilder;
 
     public function __construct(AttributesRepository $attributesRepository)
     {
+        $this->attributesRepository = $attributesRepository;
         $this->parameterBuilder = new ReflectionParameterDefinitionBuilder($attributesRepository);
     }
 
     public function for(ReflectionMethod $reflection, ReflectionTypeResolver $typeResolver): MethodDefinition
     {
+        /** @var non-empty-string $name */
+        $name = $reflection->name;
+
+        $attributes = array_map(
+            fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
+            Reflection::attributes($reflection)
+        );
+
         $parameters = array_map(
             fn (ReflectionParameter $parameter) => $this->parameterBuilder->for($parameter, $typeResolver),
             $reflection->getParameters()
@@ -33,8 +46,9 @@ final class ReflectionMethodDefinitionBuilder
         $returnType = $typeResolver->resolveType($reflection);
 
         return new MethodDefinition(
-            $reflection->name,
+            $name,
             Reflection::signature($reflection),
+            new Attributes(...$attributes),
             new Parameters(...$parameters),
             $reflection->isStatic(),
             $reflection->isPublic(),
index cc0d212072902c7583fa556ef9f4752ef4d02561..37f75282d71a317aa6d1d53f190666be145c5d78 100644 (file)
@@ -4,25 +4,31 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Repository\Reflection;
 
+use CuyZ\Valinor\Definition\AttributeDefinition;
+use CuyZ\Valinor\Definition\Attributes;
 use CuyZ\Valinor\Definition\ParameterDefinition;
 use CuyZ\Valinor\Definition\Repository\AttributesRepository;
 use CuyZ\Valinor\Type\Types\UnresolvableType;
 use CuyZ\Valinor\Utility\Reflection\Reflection;
+use ReflectionAttribute;
 use ReflectionParameter;
 
+use function array_map;
+
 /** @internal */
 final class ReflectionParameterDefinitionBuilder
 {
-    public function __construct(private AttributesRepository $attributesFactory) {}
+    public function __construct(private AttributesRepository $attributesRepository) {}
 
     public function for(ReflectionParameter $reflection, ReflectionTypeResolver $typeResolver): ParameterDefinition
     {
+        /** @var non-empty-string $name */
         $name = $reflection->name;
         $signature = Reflection::signature($reflection);
         $type = $typeResolver->resolveType($reflection);
+        $nativeType = $typeResolver->resolveNativeType($reflection);
         $isOptional = $reflection->isOptional();
         $isVariadic = $reflection->isVariadic();
-        $attributes = $this->attributesFactory->for($reflection);
 
         if ($reflection->isDefaultValueAvailable()) {
             $defaultValue = $reflection->getDefaultValue();
@@ -39,6 +45,26 @@ final class ReflectionParameterDefinitionBuilder
             $type = UnresolvableType::forInvalidParameterDefaultValue($signature, $type, $defaultValue);
         }
 
-        return new ParameterDefinition($name, $signature, $type, $isOptional, $isVariadic, $defaultValue, $attributes);
+        return new ParameterDefinition(
+            $name,
+            $signature,
+            $type,
+            $nativeType,
+            $isOptional,
+            $isVariadic,
+            $defaultValue,
+            new Attributes(...$this->attributes($reflection)),
+        );
+    }
+
+    /**
+     * @return list<AttributeDefinition>
+     */
+    private function attributes(ReflectionParameter $reflection): array
+    {
+        return array_map(
+            fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
+            Reflection::attributes($reflection)
+        );
     }
 }
index 802f242c9ddd217a8c2ca481bc5bb11772b3c542..64936f33ddcb56010381f95420eb31953b7925ef 100644 (file)
@@ -4,14 +4,19 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Repository\Reflection;
 
+use CuyZ\Valinor\Definition\AttributeDefinition;
+use CuyZ\Valinor\Definition\Attributes;
 use CuyZ\Valinor\Definition\PropertyDefinition;
 use CuyZ\Valinor\Definition\Repository\AttributesRepository;
 use CuyZ\Valinor\Type\Type;
 use CuyZ\Valinor\Type\Types\NullType;
 use CuyZ\Valinor\Type\Types\UnresolvableType;
 use CuyZ\Valinor\Utility\Reflection\Reflection;
+use ReflectionAttribute;
 use ReflectionProperty;
 
+use function array_map;
+
 /** @internal */
 final class ReflectionPropertyDefinitionBuilder
 {
@@ -19,13 +24,14 @@ final class ReflectionPropertyDefinitionBuilder
 
     public function for(ReflectionProperty $reflection, ReflectionTypeResolver $typeResolver): PropertyDefinition
     {
+        /** @var non-empty-string $name */
         $name = $reflection->name;
         $signature = Reflection::signature($reflection);
         $type = $typeResolver->resolveType($reflection);
+        $nativeType = $typeResolver->resolveNativeType($reflection);
         $hasDefaultValue = $this->hasDefaultValue($reflection, $type);
         $defaultValue = $reflection->getDefaultValue();
         $isPublic = $reflection->isPublic();
-        $attributes = $this->attributesRepository->for($reflection);
 
         if ($hasDefaultValue
             && ! $type instanceof UnresolvableType
@@ -38,10 +44,11 @@ final class ReflectionPropertyDefinitionBuilder
             $name,
             $signature,
             $type,
+            $nativeType,
             $hasDefaultValue,
             $defaultValue,
             $isPublic,
-            $attributes
+            new Attributes(...$this->attributes($reflection)),
         );
     }
 
@@ -54,4 +61,15 @@ final class ReflectionPropertyDefinitionBuilder
         return $reflection->getDeclaringClass()->getDefaultProperties()[$reflection->name] !== null
             || NullType::get()->matches($type);
     }
+
+    /**
+     * @return list<AttributeDefinition>
+     */
+    private function attributes(ReflectionProperty $reflection): array
+    {
+        return array_map(
+            fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
+            Reflection::attributes($reflection)
+        );
+    }
 }
index ef8f3568ed294e998fcc48fa5cbd881d76dfd99f..4135f0ec82d8913705432293fdfd96a9f55845ca 100644 (file)
@@ -4,7 +4,7 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Definition\Repository\Reflection;
 
-use CuyZ\Valinor\Definition\Exception\TypesDoNotMatch;
+use CuyZ\Valinor\Type\GenericType;
 use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
 use CuyZ\Valinor\Type\Parser\TypeParser;
 use CuyZ\Valinor\Type\Type;
@@ -18,40 +18,58 @@ use ReflectionFunctionAbstract;
 use ReflectionParameter;
 use ReflectionProperty;
 
+use function trim;
+
 /** @internal */
 final class ReflectionTypeResolver
 {
     public function __construct(
         private TypeParser $nativeParser,
-        private TypeParser $advancedParser
+        private TypeParser $advancedParser,
     ) {}
 
     public function resolveType(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection): Type
     {
-        $nativeType = $this->nativeType($reflection);
+        $nativeType = $this->resolveNativeType($reflection);
         $typeFromDocBlock = $this->typeFromDocBlock($reflection);
 
-        if (! $nativeType && ! $typeFromDocBlock) {
-            return MixedType::get();
+        if (! $typeFromDocBlock) {
+            // When the type is a class, it may declare templates that must be
+            // filled with generics. PHP does not handle generics natively, so
+            // we need to make sure that no generics are left unassigned by
+            // parsing the type again using the advanced parser.
+            if ($nativeType instanceof GenericType) {
+                $nativeType = $this->parseType($nativeType->toString(), $reflection, $this->advancedParser);
+            }
+
+            return $nativeType;
         }
 
-        if (! $nativeType) {
-            /** @var Type $typeFromDocBlock */
+        if ($typeFromDocBlock instanceof UnresolvableType) {
             return $typeFromDocBlock;
         }
 
-        if (! $typeFromDocBlock) {
-            return $nativeType;
+        if (! $typeFromDocBlock->matches($nativeType)) {
+            return UnresolvableType::forDocBlockTypeNotMatchingNative($reflection, $typeFromDocBlock, $nativeType);
         }
 
-        if (! $typeFromDocBlock instanceof UnresolvableType
-            && ! $nativeType instanceof UnresolvableType
-            && ! $typeFromDocBlock->matches($nativeType)
-        ) {
-            throw new TypesDoNotMatch($reflection, $typeFromDocBlock, $nativeType);
+        return $typeFromDocBlock;
+    }
+
+    public function resolveNativeType(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection): Type
+    {
+        $reflectionType = $reflection instanceof ReflectionFunctionAbstract
+            ? $reflection->getReturnType()
+            : $reflection->getType();
+
+        if (! $reflectionType) {
+            return MixedType::get();
         }
 
-        return $typeFromDocBlock;
+        $type = Reflection::flattenType($reflectionType);
+        $type = $this->parseType($type, $reflection, $this->nativeParser);
+
+        return $this->handleVariadicType($reflection, $type);
     }
 
     private function typeFromDocBlock(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection): ?Type
@@ -82,22 +100,6 @@ final class ReflectionTypeResolver
         return $this->handleVariadicType($reflection, $type);
     }
 
-    private function nativeType(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection): ?Type
-    {
-        $reflectionType = $reflection instanceof ReflectionFunctionAbstract
-            ? $reflection->getReturnType()
-            : $reflection->getType();
-
-        if (! $reflectionType) {
-            return null;
-        }
-
-        $type = Reflection::flattenType($reflectionType);
-        $type = $this->parseType($type, $reflection, $this->nativeParser);
-
-        return $this->handleVariadicType($reflection, $type);
-    }
-
     private function parseType(string $raw, ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection, TypeParser $parser): Type
     {
         try {
index 35fda30213434625442680a26e50eee9709aed75..ea747098873a8537bf137823eb0af847fee2fb6f 100644 (file)
@@ -9,12 +9,11 @@ use CuyZ\Valinor\Cache\KeySanitizerCache;
 use CuyZ\Valinor\Cache\RuntimeCache;
 use CuyZ\Valinor\Cache\Warmup\RecursiveCacheWarmupService;
 use CuyZ\Valinor\Definition\FunctionsContainer;
-use CuyZ\Valinor\Definition\Repository\AttributesRepository;
 use CuyZ\Valinor\Definition\Repository\Cache\CacheClassDefinitionRepository;
 use CuyZ\Valinor\Definition\Repository\Cache\CacheFunctionDefinitionRepository;
 use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
 use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
-use CuyZ\Valinor\Definition\Repository\Reflection\NativeAttributesRepository;
+use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionAttributesRepository;
 use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository;
 use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionFunctionDefinitionRepository;
 use CuyZ\Valinor\Mapper\ArgumentsMapper;
@@ -34,10 +33,11 @@ use CuyZ\Valinor\Mapper\Tree\Builder\ErrorCatcherNodeBuilder;
 use CuyZ\Valinor\Mapper\Tree\Builder\InterfaceNodeBuilder;
 use CuyZ\Valinor\Mapper\Tree\Builder\IterableNodeBuilder;
 use CuyZ\Valinor\Mapper\Tree\Builder\ListNodeBuilder;
-use CuyZ\Valinor\Mapper\Tree\Builder\NativeClassNodeBuilder;
+use CuyZ\Valinor\Mapper\Tree\Builder\ObjectNodeBuilder;
 use CuyZ\Valinor\Mapper\Tree\Builder\NodeBuilder;
+use CuyZ\Valinor\Mapper\Tree\Builder\NullNodeBuilder;
 use CuyZ\Valinor\Mapper\Tree\Builder\ObjectImplementations;
-use CuyZ\Valinor\Mapper\Tree\Builder\ObjectNodeBuilder;
+use CuyZ\Valinor\Mapper\Tree\Builder\FilteredObjectNodeBuilder;
 use CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder;
 use CuyZ\Valinor\Mapper\Tree\Builder\ScalarNodeBuilder;
 use CuyZ\Valinor\Mapper\Tree\Builder\ShapedArrayNodeBuilder;
@@ -49,11 +49,12 @@ use CuyZ\Valinor\Mapper\TypeArgumentsMapper;
 use CuyZ\Valinor\Mapper\TypeTreeMapper;
 use CuyZ\Valinor\Normalizer\ArrayNormalizer;
 use CuyZ\Valinor\Normalizer\Format;
+use CuyZ\Valinor\Normalizer\JsonNormalizer;
 use CuyZ\Valinor\Normalizer\Normalizer;
 use CuyZ\Valinor\Normalizer\Transformer\KeyTransformersHandler;
 use CuyZ\Valinor\Normalizer\Transformer\RecursiveTransformer;
 use CuyZ\Valinor\Normalizer\Transformer\ValueTransformersHandler;
-use CuyZ\Valinor\Type\ClassType;
+use CuyZ\Valinor\Type\ObjectType;
 use CuyZ\Valinor\Type\Parser\Factory\LexingTypeParserFactory;
 use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
 use CuyZ\Valinor\Type\Parser\TypeParser;
@@ -63,6 +64,7 @@ use CuyZ\Valinor\Type\Types\IterableType;
 use CuyZ\Valinor\Type\Types\ListType;
 use CuyZ\Valinor\Type\Types\NonEmptyArrayType;
 use CuyZ\Valinor\Type\Types\NonEmptyListType;
+use CuyZ\Valinor\Type\Types\NullType;
 use CuyZ\Valinor\Type\Types\ShapedArrayType;
 use Psr\SimpleCache\CacheInterface;
 
@@ -108,29 +110,29 @@ final class Container
                     IterableType::class => $arrayNodeBuilder,
                     ShapedArrayType::class => new ShapedArrayNodeBuilder($settings->allowSuperfluousKeys),
                     ScalarType::class => new ScalarNodeBuilder($settings->enableFlexibleCasting),
-                    ClassType::class => new NativeClassNodeBuilder(
+                    NullType::class => new NullNodeBuilder(),
+                    ObjectType::class => new ObjectNodeBuilder(
                         $this->get(ClassDefinitionRepository::class),
                         $this->get(ObjectBuilderFactory::class),
-                        $this->get(ObjectNodeBuilder::class),
+                        $this->get(FilteredObjectNodeBuilder::class),
                         $settings->enableFlexibleCasting,
                     ),
                 ]);
 
-                $builder = new UnionNodeBuilder(
-                    $builder,
-                    $this->get(ClassDefinitionRepository::class),
-                    $this->get(ObjectBuilderFactory::class),
-                    $this->get(ObjectNodeBuilder::class),
-                    $settings->enableFlexibleCasting
-                );
+                $builder = new UnionNodeBuilder($builder);
 
                 $builder = new InterfaceNodeBuilder(
                     $builder,
                     $this->get(ObjectImplementations::class),
                     $this->get(ClassDefinitionRepository::class),
                     $this->get(ObjectBuilderFactory::class),
-                    $this->get(ObjectNodeBuilder::class),
-                    $settings->enableFlexibleCasting
+                    $this->get(FilteredObjectNodeBuilder::class),
+                    new FunctionsContainer(
+                        $this->get(FunctionDefinitionRepository::class),
+                        $settings->customConstructors
+                    ),
+                    $settings->enableFlexibleCasting,
+                    $settings->allowSuperfluousKeys,
                 );
 
                 $builder = new CasterProxyNodeBuilder($builder);
@@ -151,7 +153,7 @@ final class Container
                 return new ErrorCatcherNodeBuilder($builder, $settings->exceptionFilter);
             },
 
-            ObjectNodeBuilder::class => fn () => new ObjectNodeBuilder($settings->allowSuperfluousKeys),
+            FilteredObjectNodeBuilder::class => fn () => new FilteredObjectNodeBuilder($settings->allowSuperfluousKeys),
 
             ObjectImplementations::class => fn () => new ObjectImplementations(
                 new FunctionsContainer(
@@ -188,9 +190,7 @@ final class Container
                 new ValueTransformersHandler(
                     $this->get(FunctionDefinitionRepository::class),
                 ),
-                new KeyTransformersHandler(
-                    $this->get(FunctionDefinitionRepository::class),
-                ),
+                new KeyTransformersHandler(),
                 $settings->transformersSortedByPriority(),
                 array_keys($settings->transformerAttributes),
             ),
@@ -199,10 +199,13 @@ final class Container
                 $this->get(RecursiveTransformer::class),
             ),
 
+            JsonNormalizer::class => fn () => new JsonNormalizer(
+                $this->get(RecursiveTransformer::class),
+            ),
+
             ClassDefinitionRepository::class => fn () => new CacheClassDefinitionRepository(
                 new ReflectionClassDefinitionRepository(
                     $this->get(TypeParserFactory::class),
-                    $this->get(AttributesRepository::class),
                 ),
                 $this->get(CacheInterface::class),
             ),
@@ -210,13 +213,13 @@ final class Container
             FunctionDefinitionRepository::class => fn () => new CacheFunctionDefinitionRepository(
                 new ReflectionFunctionDefinitionRepository(
                     $this->get(TypeParserFactory::class),
-                    $this->get(AttributesRepository::class),
+                    new ReflectionAttributesRepository(
+                        $this->get(ClassDefinitionRepository::class),
+                    ),
                 ),
                 $this->get(CacheInterface::class)
             ),
 
-            AttributesRepository::class => fn () => new NativeAttributesRepository(),
-
             TypeParserFactory::class => fn () => new LexingTypeParserFactory(),
 
             TypeParser::class => fn () => $this->get(TypeParserFactory::class)->get(),
index b7bd58b9fc614c7aeafee9d338c726156422ca2d..0cfbf969f87c1d764f3ecd92545d4353f54dc58d 100644 (file)
@@ -25,11 +25,11 @@ final class ArgumentsMapperError extends RuntimeException implements MappingErro
         if ($errorsCount === 1) {
             $body = $errors
                 ->toArray()[0]
-                ->withBody("Could not map arguments of `{$function->signature()}`. An error occurred at path {node_path}: {original_message}")
+                ->withBody("Could not map arguments of `$function->signature`. An error occurred at path {node_path}: {original_message}")
                 ->toString();
         } else {
             $source = ValueDumper::dump($node->sourceValue());
-            $body = "Could not map arguments of `{$function->signature()}` with value $source. A total of $errorsCount errors were encountered.";
+            $body = "Could not map arguments of `$function->signature` with value $source. A total of $errorsCount errors were encountered.";
         }
 
         parent::__construct($body, 1671115362);
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringArgumentsMapping.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringArgumentsMapping.php
new file mode 100644 (file)
index 0000000..3ccbc02
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Mapper\Exception;
+
+use CuyZ\Valinor\Definition\FunctionDefinition;
+use CuyZ\Valinor\Mapper\Tree\Exception\UnresolvableShellType;
+use LogicException;
+
+/** @internal */
+final class TypeErrorDuringArgumentsMapping extends LogicException
+{
+    public function __construct(FunctionDefinition $function, UnresolvableShellType $exception)
+    {
+        parent::__construct(
+            "Could not map arguments of `$function->signature`: {$exception->getMessage()}",
+            1711534351,
+            $exception,
+        );
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringMapping.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringMapping.php
new file mode 100644 (file)
index 0000000..21b443c
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Mapper\Exception;
+
+use CuyZ\Valinor\Mapper\Tree\Exception\UnresolvableShellType;
+use CuyZ\Valinor\Type\Type;
+use LogicException;
+
+/** @internal */
+final class TypeErrorDuringMapping extends LogicException
+{
+    public function __construct(Type $type, UnresolvableShellType $exception)
+    {
+        parent::__construct(
+            "Error while trying to map to `{$type->toString()}`: {$exception->getMessage()}",
+            1711526329,
+            $exception,
+        );
+    }
+}
index f97470919c7437ab182f2d1fc3e87decb3130792..339b5631a92154661243236ce2029c16a07cf46d 100644 (file)
@@ -5,7 +5,6 @@ declare(strict_types=1);
 namespace CuyZ\Valinor\Mapper\Object;
 
 use CuyZ\Valinor\Definition\Attributes;
-use CuyZ\Valinor\Definition\AttributesContainer;
 use CuyZ\Valinor\Definition\ParameterDefinition;
 use CuyZ\Valinor\Definition\PropertyDefinition;
 use CuyZ\Valinor\Type\Type;
@@ -31,11 +30,11 @@ final class Argument
 
     public static function fromParameter(ParameterDefinition $parameter): self
     {
-        $instance = new self($parameter->name(), $parameter->type());
-        $instance->attributes = $parameter->attributes();
+        $instance = new self($parameter->name, $parameter->type);
+        $instance->attributes = $parameter->attributes;
 
-        if ($parameter->isOptional()) {
-            $instance->defaultValue = $parameter->defaultValue();
+        if ($parameter->isOptional) {
+            $instance->defaultValue = $parameter->defaultValue;
             $instance->isRequired = false;
         }
 
@@ -44,11 +43,11 @@ final class Argument
 
     public static function fromProperty(PropertyDefinition $property): self
     {
-        $instance = new self($property->name(), $property->type());
-        $instance->attributes = $property->attributes();
+        $instance = new self($property->name, $property->type);
+        $instance->attributes = $property->attributes;
 
-        if ($property->hasDefaultValue()) {
-            $instance->defaultValue = $property->defaultValue();
+        if ($property->hasDefaultValue) {
+            $instance->defaultValue = $property->defaultValue;
             $instance->isRequired = false;
         }
 
@@ -77,6 +76,6 @@ final class Argument
 
     public function attributes(): Attributes
     {
-        return $this->attributes ??= AttributesContainer::empty();
+        return $this->attributes ??= Attributes::empty();
     }
 }
index 3ff47c6a18b9cf163adeff37e10061a886df1628..023e390d0d39d19376fad85cdb86f13efb0affe7 100644 (file)
@@ -14,7 +14,6 @@ use Traversable;
 
 use function array_map;
 use function array_values;
-use function iterator_to_array;
 
 /**
  * @internal
@@ -35,7 +34,7 @@ final class Arguments implements IteratorAggregate, Countable
     {
         return new self(...array_map(
             fn (ParameterDefinition $parameter) => Argument::fromParameter($parameter),
-            array_values(iterator_to_array($parameters)) // PHP8.1 array unpacking
+            array_values([...$parameters])
         ));
     }
 
@@ -43,7 +42,7 @@ final class Arguments implements IteratorAggregate, Countable
     {
         return new self(...array_map(
             fn (PropertyDefinition $property) => Argument::fromProperty($property),
-            array_values(iterator_to_array($properties)) // PHP8.1 array unpacking
+            array_values([...$properties])
         ));
     }
 
index 4a3129ebb4c4fb987b0b2b01e3220c35c687fd7b..f2d7aee4c7ac0ab61ba35283c1973815f26937a5 100644 (file)
@@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Mapper\Object;
 
 use CuyZ\Valinor\Mapper\Object\Exception\InvalidSource;
 use CuyZ\Valinor\Type\CompositeTraversableType;
+use CuyZ\Valinor\Type\Types\ArrayKeyType;
 use IteratorAggregate;
 use Traversable;
 
@@ -35,22 +36,22 @@ final class ArgumentsValues implements IteratorAggregate
         $this->arguments = $arguments;
     }
 
-    public static function forInterface(Arguments $arguments, mixed $value): self
+    public static function forInterface(Arguments $arguments, mixed $value, bool $allowSuperfluousKeys): self
     {
         $self = new self($arguments);
         $self->forInterface = true;
 
         if (count($arguments) > 0) {
-            $self = $self->transform($value);
+            $self = $self->transform($value, $allowSuperfluousKeys);
         }
 
         return $self;
     }
 
-    public static function forClass(Arguments $arguments, mixed $value): self
+    public static function forClass(Arguments $arguments, mixed $value, bool $allowSuperfluousKeys): self
     {
         $self = new self($arguments);
-        $self = $self->transform($value);
+        $self = $self->transform($value, $allowSuperfluousKeys);
 
         return $self;
     }
@@ -81,11 +82,11 @@ final class ArgumentsValues implements IteratorAggregate
         return $this->hadSingleArgument;
     }
 
-    private function transform(mixed $value): self
+    private function transform(mixed $value, bool $allowSuperfluousKeys): self
     {
         $clone = clone $this;
 
-        $transformedValue = $this->transformValueForSingleArgument($value);
+        $transformedValue = $this->transformValueForSingleArgument($value, $allowSuperfluousKeys);
 
         if (! is_array($transformedValue)) {
             throw new InvalidSource($transformedValue, $this->arguments);
@@ -108,7 +109,7 @@ final class ArgumentsValues implements IteratorAggregate
         return $clone;
     }
 
-    private function transformValueForSingleArgument(mixed $value): mixed
+    private function transformValueForSingleArgument(mixed $value, bool $allowSuperfluousKeys): mixed
     {
         if (count($this->arguments) !== 1) {
             return $value;
@@ -116,15 +117,17 @@ final class ArgumentsValues implements IteratorAggregate
 
         $argument = $this->arguments->at(0);
         $name = $argument->name();
-        $isTraversable = $argument-> type() instanceof CompositeTraversableType;
+        $type = $argument->type();
+        $isTraversableAndAllowsStringKeys = $type instanceof CompositeTraversableType
+            && $type->keyType() !== ArrayKeyType::integer();
 
         if (is_array($value) && array_key_exists($name, $value)) {
-            if ($this->forInterface || ! $isTraversable || count($value) === 1) {
+            if ($this->forInterface || ! $isTraversableAndAllowsStringKeys || $allowSuperfluousKeys || count($value) === 1) {
                 return $value;
             }
         }
 
-        if ($value === [] && ! $isTraversable) {
+        if ($value === [] && ! $isTraversableAndAllowsStringKeys) {
             return $value;
         }
 
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Constructor.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Constructor.php
new file mode 100644 (file)
index 0000000..c39c5ad
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Mapper\Object;
+
+use Attribute;
+
+/**
+ * This attribute allows a static method inside a class to be marked as a
+ * constructor, that can be used by the mapper to instantiate the object. The
+ * method must be public, static and return an instance of the class it is part
+ * of.
+ *
+ * This attribute is a convenient replacement to the usage of the constructor
+ * registration method: @see \CuyZ\Valinor\MapperBuilder::registerConstructor()
+ *
+ * ```php
+ * final readonly class Email
+ * {
+ *     // When another constructor is registered for the class, the native
+ *     // constructor is disabled. To enable it again, it is mandatory to
+ *     // explicitly register it again.
+ *     #[\CuyZ\Valinor\Mapper\Object\Constructor]
+ *     public function __construct(public string $value) {}
+ *
+ *     #[\CuyZ\Valinor\Mapper\Object\Constructor]
+ *     public static function createFrom(string $user, string $domainName): self
+ *     {
+ *         return new self($user . '@' . $domainName);
+ *     }
+ * }
+ *
+ * (new \CuyZ\Valinor\MapperBuilder())
+ *     ->mapper()
+ *     ->map(Email::class, [
+ *         'userName' => 'john.doe',
+ *         'domainName' => 'example.com',
+ *     ]); // john.doe@example.com
+ * ```
+ *
+ * @api
+ */
+#[Attribute(Attribute::TARGET_METHOD)]
+final class Constructor {}
index cbecad81dd8361751ddd4560cbafcaf6025f9195..b60fae7c086756a07abbef0a1c4f1be3bbbf0cd5 100644 (file)
@@ -13,7 +13,7 @@ final class CannotInstantiateObject extends RuntimeException
     public function __construct(ClassDefinition $class)
     {
         parent::__construct(
-            "No available constructor found for class `{$class->name()}`.",
+            "No available constructor found for class `{$class->name}`.",
             1646916477
         );
     }
index f47208efb34985d1398d1c411c2f6197bf68d7a1..5f7fc45b11176f5bb78ea8a2fda60ed1ef04d3ac 100644 (file)
@@ -14,7 +14,7 @@ final class InvalidConstructorClassTypeParameter extends LogicException
     public function __construct(FunctionDefinition $function, Type $type)
     {
         parent::__construct(
-            "Invalid type `{$type->toString()}` for the first parameter of the constructor `{$function->signature()}`, it should be of type `class-string`.",
+            "Invalid type `{$type->toString()}` for the first parameter of the constructor `{$function->signature}`, it should be of type `class-string`.",
             1661517000
         );
     }
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorMethodWithAttributeReturnType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/InvalidConstructorMethodWithAttributeReturnType.php
new file mode 100644 (file)
index 0000000..083fa24
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Mapper\Object\Exception;
+
+use CuyZ\Valinor\Definition\MethodDefinition;
+use CuyZ\Valinor\Type\Types\UnresolvableType;
+use LogicException;
+
+/** @internal */
+final class InvalidConstructorMethodWithAttributeReturnType extends LogicException
+{
+    /**
+     * @param class-string $expectedClassName
+     */
+    public function __construct(string $expectedClassName, MethodDefinition $method)
+    {
+        if ($method->returnType instanceof UnresolvableType) {
+            $message = $method->returnType->message();
+        } else {
+            $message = "Invalid return type `{$method->returnType->toString()}` for constructor `{$method->signature}`, it must be `$expectedClassName`.";
+        }
+
+        parent::__construct($message, 1708104783);
+    }
+}
index 136b66e29d2c470ff1b288992c851fe73a9e2dac..5fff4656ed95b8aa2a8aadaf1653160369d03d17 100644 (file)
@@ -13,12 +13,10 @@ final class InvalidConstructorReturnType extends LogicException
 {
     public function __construct(FunctionDefinition $function)
     {
-        $returnType = $function->returnType();
-
-        if ($returnType instanceof UnresolvableType) {
-            $message = $returnType->message();
+        if ($function->returnType instanceof UnresolvableType) {
+            $message = $function->returnType->message();
         } else {
-            $message = "Invalid return type `{$returnType->toString()}` for constructor `{$function->signature()}`, it must be a valid class name.";
+            $message = "Invalid return type `{$function->returnType->toString()}` for constructor `{$function->signature}`, it must be a valid class name.";
         }
 
         parent::__construct($message, 1659446121);
index 1c5c8d4e172c1757fa057bead5d59257542943f9..57110e9c40b0207ac9bebd5c9cdd6a6fa687df95 100644 (file)
@@ -13,7 +13,7 @@ final class MissingConstructorClassTypeParameter extends LogicException
     public function __construct(FunctionDefinition $function)
     {
         parent::__construct(
-            "Missing first parameter of type `class-string` for the constructor `{$function->signature()}`.",
+            "Missing first parameter of type `class-string` for the constructor `{$function->signature}`.",
             1661516853
         );
     }
index dce0aeee48f815798fd8b4f7e416ff417f73bdda..697fed7cbbbe423e08062195041ebff5cfa0d023 100644 (file)
@@ -20,7 +20,7 @@ final class ObjectBuildersCollision extends RuntimeException
         $constructors = implode('`, `', $constructors);
 
         parent::__construct(
-            "A collision was detected between the following constructors of the class `{$class->type()->toString()}`: `$constructors`.",
+            "A collision was detected between the following constructors of the class `{$class->type->toString()}`: `$constructors`.",
             1654955787
         );
     }
index 82f140072b493d08b4edbfc666c843b961809e12..c1e460dfdd533df2754ece24a3874a50072aecff 100644 (file)
@@ -19,7 +19,7 @@ final class CacheObjectBuilderFactory implements ObjectBuilderFactory
 
     public function for(ClassDefinition $class): array
     {
-        $signature = $class->type()->toString();
+        $signature = $class->type->toString();
 
         $entry = $this->cache->get($signature);
 
index feff612f5c90bc972daa1300689524e7a32d4fc0..22346f325a5dad0c7543777f88659bbe721e2b9f 100644 (file)
@@ -7,9 +7,11 @@ namespace CuyZ\Valinor\Mapper\Object\Factory;
 use CuyZ\Valinor\Definition\ClassDefinition;
 use CuyZ\Valinor\Definition\FunctionObject;
 use CuyZ\Valinor\Definition\FunctionsContainer;
+use CuyZ\Valinor\Mapper\Object\Constructor;
 use CuyZ\Valinor\Mapper\Object\DynamicConstructor;
 use CuyZ\Valinor\Mapper\Object\Exception\CannotInstantiateObject;
 use CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorClassTypeParameter;
+use CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorMethodWithAttributeReturnType;
 use CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorReturnType;
 use CuyZ\Valinor\Mapper\Object\Exception\MissingConstructorClassTypeParameter;
 use CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder;
@@ -22,8 +24,10 @@ use CuyZ\Valinor\Type\ObjectType;
 use CuyZ\Valinor\Type\Types\ClassStringType;
 use CuyZ\Valinor\Type\Types\EnumType;
 use CuyZ\Valinor\Type\Types\NativeStringType;
+use CuyZ\Valinor\Utility\Reflection\Reflection;
 
 use function array_key_exists;
+use function array_values;
 use function count;
 use function is_a;
 
@@ -37,7 +41,7 @@ final class ConstructorObjectBuilderFactory implements ObjectBuilderFactory
         private ObjectBuilderFactory $delegate,
         /** @var array<class-string, null> */
         private array $nativeConstructors,
-        private FunctionsContainer $constructors
+        private FunctionsContainer $constructors,
     ) {}
 
     public function for(ClassDefinition $class): array
@@ -45,7 +49,7 @@ final class ConstructorObjectBuilderFactory implements ObjectBuilderFactory
         $builders = $this->builders($class);
 
         if (count($builders) === 0) {
-            if ($class->methods()->hasConstructor()) {
+            if ($class->methods->hasConstructor()) {
                 throw new CannotInstantiateObject($class);
             }
 
@@ -60,9 +64,9 @@ final class ConstructorObjectBuilderFactory implements ObjectBuilderFactory
      */
     private function builders(ClassDefinition $class): array
     {
-        $className = $class->name();
-        $classType = $class->type();
-        $methods = $class->methods();
+        $className = $class->name;
+        $classType = $class->type;
+        $methods = $class->methods;
 
         $builders = [];
 
@@ -71,50 +75,79 @@ final class ConstructorObjectBuilderFactory implements ObjectBuilderFactory
                 continue;
             }
 
-            $definition = $constructor->definition();
-            $functionClass = $definition->class();
+            $definition = $constructor->definition;
+            $functionClass = $definition->class;
 
-            if ($functionClass && $definition->isStatic() && ! $definition->isClosure()) {
+            if ($functionClass && $definition->isStatic && ! $definition->isClosure) {
                 $scopedClass = is_a($className, $functionClass, true) ? $className : $functionClass;
 
-                $builders[] = new MethodObjectBuilder($scopedClass, $definition->name(), $definition->parameters());
+                $builders[$definition->signature] = new MethodObjectBuilder($scopedClass, $definition->name, $definition->parameters);
             } else {
-                $builders[] = new FunctionObjectBuilder($constructor, $classType);
+                $builders[$definition->signature] = new FunctionObjectBuilder($constructor, $classType);
             }
         }
 
-        if (! array_key_exists($className, $this->nativeConstructors) && count($builders) > 0) {
-            return $builders;
+        foreach ($methods as $method) {
+            if (! $method->isStatic) {
+                continue;
+            }
+
+            if (! $method->attributes->has(Constructor::class)) {
+                continue;
+            }
+
+            if (! $method->returnType instanceof ClassType) {
+                throw new InvalidConstructorMethodWithAttributeReturnType($className, $method);
+            }
+
+            if (! is_a($className, $method->returnType->className(), true)) {
+                throw new InvalidConstructorMethodWithAttributeReturnType($className, $method);
+            }
+
+            if (! $class->type->matches($method->returnType)) {
+                continue;
+            }
+
+            $builders[$method->signature] = new MethodObjectBuilder($className, $method->name, $method->parameters);
         }
 
         if ($classType instanceof EnumType) {
-            $builders[] = new NativeEnumObjectBuilder($classType);
-        } elseif ($methods->hasConstructor() && $methods->constructor()->isPublic()) {
+            $buildersWithOneArguments = array_filter($builders, fn (ObjectBuilder $builder) => $builder->describeArguments()->count() === 1);
+
+            if (count($buildersWithOneArguments) === 0) {
+                $builders[] = new NativeEnumObjectBuilder($classType);
+            }
+        } elseif ($methods->hasConstructor()
+            && $methods->constructor()->isPublic
+            && (
+                count($builders) === 0
+                || $methods->constructor()->attributes->has(Constructor::class)
+                || array_key_exists($className, $this->nativeConstructors)
+            )
+        ) {
             $builders[] = new NativeConstructorObjectBuilder($class);
         }
 
-        return $builders;
+        return array_values($builders);
     }
 
-    private function constructorMatches(FunctionObject $function, ClassType $classType): bool
+    private function constructorMatches(FunctionObject $function, ObjectType $classType): bool
     {
-        $definition = $function->definition();
-        $parameters = $definition->parameters();
-        $returnType = $definition->returnType();
+        $definition = $function->definition;
 
-        if (! $classType->matches($returnType)) {
+        if (! $classType->matches($definition->returnType)) {
             return false;
         }
 
-        if (! $definition->attributes()->has(DynamicConstructor::class)) {
+        if (! $definition->attributes->has(DynamicConstructor::class)) {
             return true;
         }
 
-        if (count($parameters) === 0) {
+        if (count($definition->parameters) === 0) {
             throw new MissingConstructorClassTypeParameter($definition);
         }
 
-        $parameterType = $parameters->at(0)->type();
+        $parameterType = $definition->parameters->at(0)->type;
 
         if ($parameterType instanceof NativeStringType) {
             $parameterType = ClassStringType::get();
@@ -142,13 +175,16 @@ final class ConstructorObjectBuilderFactory implements ObjectBuilderFactory
             $this->filteredConstructors = [];
 
             foreach ($this->constructors as $constructor) {
-                $function = $constructor->definition();
+                $function = $constructor->definition;
 
-                if (enum_exists($function->class() ?? '') && in_array($function->name(), ['from', 'tryFrom'], true)) {
+                if ($function->class
+                    && Reflection::enumExists($function->class)
+                    && in_array($function->name, ['from', 'tryFrom'], true)
+                ) {
                     continue;
                 }
 
-                if (! $function->returnType() instanceof ObjectType) {
+                if (! $function->returnType instanceof ObjectType) {
                     throw new InvalidConstructorReturnType($function);
                 }
 
index f1c80bc20951e8e8bbd3d0df01733afc94b06227..fa5613ac4d98db9733200ecc4b82c90ad466bb0a 100644 (file)
@@ -12,7 +12,7 @@ use CuyZ\Valinor\Mapper\Object\DateTimeFormatConstructor;
 use CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder;
 use CuyZ\Valinor\Mapper\Object\NativeConstructorObjectBuilder;
 use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
-use CuyZ\Valinor\Type\ClassType;
+use CuyZ\Valinor\Type\ObjectType;
 use DateTime;
 use DateTimeImmutable;
 
@@ -31,7 +31,7 @@ final class DateTimeObjectBuilderFactory implements ObjectBuilderFactory
 
     public function for(ClassDefinition $class): array
     {
-        $className = $class->name();
+        $className = $class->name;
 
         $builders = $this->delegate->for($class);
 
@@ -45,13 +45,13 @@ final class DateTimeObjectBuilderFactory implements ObjectBuilderFactory
         $buildersWithOneArgument = array_filter($builders, fn (ObjectBuilder $builder) => count($builder->describeArguments()) === 1);
 
         if (count($buildersWithOneArgument) === 0 || $this->supportedDateFormats !== Settings::DEFAULT_SUPPORTED_DATETIME_FORMATS) {
-            $builders[] = $this->internalDateTimeBuilder($class->type());
+            $builders[] = $this->internalDateTimeBuilder($class->type);
         }
 
         return $builders;
     }
 
-    private function internalDateTimeBuilder(ClassType $type): FunctionObjectBuilder
+    private function internalDateTimeBuilder(ObjectType $type): FunctionObjectBuilder
     {
         $constructor = new DateTimeFormatConstructor(...$this->supportedDateFormats);
         $function = new FunctionObject($this->functionDefinitionRepository->for($constructor), $constructor);
index 48871ca9c77fcd7066c3cca06db1836d6d3129fc..cc17713fcbe30e9a6334e93bdb2cff24426c7f73 100644 (file)
@@ -11,7 +11,7 @@ use CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder;
 use CuyZ\Valinor\Mapper\Object\NativeConstructorObjectBuilder;
 use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
 use CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder;
-use CuyZ\Valinor\Type\ClassType;
+use CuyZ\Valinor\Type\ObjectType;
 use DateTimeZone;
 use Exception;
 
@@ -35,7 +35,7 @@ final class DateTimeZoneObjectBuilderFactory implements ObjectBuilderFactory
     {
         $builders = $this->delegate->for($class);
 
-        if ($class->name() !== DateTimeZone::class) {
+        if ($class->name !== DateTimeZone::class) {
             return $builders;
         }
 
@@ -54,13 +54,13 @@ final class DateTimeZoneObjectBuilderFactory implements ObjectBuilderFactory
 
         if ($useDefaultBuilder) {
             // @infection-ignore-all / Ignore memoization
-            $builders[] = $this->defaultBuilder($class->type());
+            $builders[] = $this->defaultBuilder($class->type);
         }
 
         return $builders;
     }
 
-    private function defaultBuilder(ClassType $type): FunctionObjectBuilder
+    private function defaultBuilder(ObjectType $type): FunctionObjectBuilder
     {
         $constructor = function (string $timezone) {
             try {
index abdb91dc7e53f5d84eb5712eacf3d096299c6af5..187f11bcc2a691708af24038f5b9f1dae2093ec9 100644 (file)
@@ -6,15 +6,14 @@ namespace CuyZ\Valinor\Mapper\Object\Factory;
 
 use CuyZ\Valinor\Definition\ClassDefinition;
 use CuyZ\Valinor\Mapper\Object\ReflectionObjectBuilder;
-
-use function enum_exists;
+use CuyZ\Valinor\Utility\Reflection\Reflection;
 
 /** @internal */
 final class ReflectionObjectBuilderFactory implements ObjectBuilderFactory
 {
     public function for(ClassDefinition $class): array
     {
-        if (enum_exists($class->name())) {
+        if (Reflection::enumExists($class->name)) {
             return [];
         }
 
index ed5dd07e77613785420b9d8b2e64f122f1139af5..acabadfe17522d9db7ec36ed18acf3d0f73b4255 100644 (file)
@@ -9,23 +9,30 @@ use CuyZ\Valinor\Mapper\Object\Exception\SeveralObjectBuildersFound;
 
 use function count;
 use function is_array;
+use function reset;
 
 /** @internal */
 final class FilteredObjectBuilder implements ObjectBuilder
 {
     private ObjectBuilder $delegate;
 
-    private Arguments $arguments;
-
-    public function __construct(mixed $source, ObjectBuilder ...$builders)
+    private function __construct(mixed $source, ObjectBuilder ...$builders)
     {
         $this->delegate = $this->filterBuilder($source, ...$builders);
-        $this->arguments = $this->delegate->describeArguments();
+    }
+
+    public static function from(mixed $source, ObjectBuilder ...$builders): ObjectBuilder
+    {
+        if (count($builders) === 1) {
+            return $builders[0];
+        }
+
+        return new self($source, ...$builders);
     }
 
     public function describeArguments(): Arguments
     {
-        return $this->arguments;
+        return $this->delegate->describeArguments();
     }
 
     public function build(array $arguments): object
@@ -41,7 +48,7 @@ final class FilteredObjectBuilder implements ObjectBuilder
     private function filterBuilder(mixed $source, ObjectBuilder ...$builders): ObjectBuilder
     {
         if (count($builders) === 1) {
-            return $builders[0];
+            return reset($builders);
         }
 
         /** @var non-empty-list<ObjectBuilder> $builders */
index 57f2e08f92112c442ca714839706198a3a7a2354..db1cc19dae3bcc6dc2210ab4303fbecfba666e63 100644 (file)
@@ -7,7 +7,7 @@ namespace CuyZ\Valinor\Mapper\Object;
 use CuyZ\Valinor\Definition\FunctionObject;
 use CuyZ\Valinor\Definition\ParameterDefinition;
 use CuyZ\Valinor\Mapper\Tree\Message\UserlandError;
-use CuyZ\Valinor\Type\ClassType;
+use CuyZ\Valinor\Type\ObjectType;
 use Exception;
 
 use function array_map;
@@ -24,16 +24,16 @@ final class FunctionObjectBuilder implements ObjectBuilder
 
     private bool $isDynamicConstructor;
 
-    public function __construct(FunctionObject $function, ClassType $type)
+    public function __construct(FunctionObject $function, ObjectType $type)
     {
-        $definition = $function->definition();
+        $definition = $function->definition;
 
         $arguments = array_map(
             fn (ParameterDefinition $parameter) => Argument::fromParameter($parameter),
-            array_values(iterator_to_array($definition->parameters())) // PHP8.1 array unpacking
+            array_values([...$definition->parameters])
         );
 
-        $this->isDynamicConstructor = $definition->attributes()->has(DynamicConstructor::class);
+        $this->isDynamicConstructor = $definition->attributes->has(DynamicConstructor::class);
 
         if ($this->isDynamicConstructor) {
             array_shift($arguments);
@@ -51,16 +51,16 @@ final class FunctionObjectBuilder implements ObjectBuilder
 
     public function build(array $arguments): object
     {
-        $parameters = $this->function->definition()->parameters();
+        $parameters = $this->function->definition->parameters;
 
         if ($this->isDynamicConstructor) {
-            $arguments[$parameters->at(0)->name()] = $this->className;
+            $arguments[$parameters->at(0)->name] = $this->className;
         }
 
         $arguments = new MethodArguments($parameters, $arguments);
 
         try {
-            return ($this->function->callback())(...$arguments);
+            return ($this->function->callback)(...$arguments);
         } catch (Exception $exception) {
             throw UserlandError::from($exception);
         }
@@ -68,6 +68,6 @@ final class FunctionObjectBuilder implements ObjectBuilder
 
     public function signature(): string
     {
-        return $this->function->definition()->signature();
+        return $this->function->definition->signature;
     }
 }
index 7a10723f42fd812acddc977fbe360a28adac79b6..ded388c63c837674657cc6bf9a9549e256263cc2 100644 (file)
@@ -26,9 +26,9 @@ final class MethodArguments implements IteratorAggregate
     public function __construct(Parameters $parameters, array $arguments)
     {
         foreach ($parameters as $parameter) {
-            $name = $parameter->name();
+            $name = $parameter->name;
 
-            if ($parameter->isVariadic()) {
+            if ($parameter->isVariadic) {
                 $this->arguments = [...$this->arguments, ...array_values($arguments[$name])]; // @phpstan-ignore-line we know that the argument is iterable
             } else {
                 $this->arguments[] = $arguments[$name];
index ec7bedd304950aba9321899965ec617aa5dd5af9..808a16f998babb28975c93e130290ea719df5650 100644 (file)
@@ -17,13 +17,13 @@ final class NativeConstructorObjectBuilder implements ObjectBuilder
 
     public function describeArguments(): Arguments
     {
-        return $this->arguments ??= Arguments::fromParameters($this->class->methods()->constructor()->parameters());
+        return $this->arguments ??= Arguments::fromParameters($this->class->methods->constructor()->parameters);
     }
 
     public function build(array $arguments): object
     {
-        $className = $this->class->name();
-        $arguments = new MethodArguments($this->class->methods()->constructor()->parameters(), $arguments);
+        $className = $this->class->name;
+        $arguments = new MethodArguments($this->class->methods->constructor()->parameters, $arguments);
 
         try {
             return new $className(...$arguments);
@@ -34,6 +34,6 @@ final class NativeConstructorObjectBuilder implements ObjectBuilder
 
     public function signature(): string
     {
-        return $this->class->methods()->constructor()->signature();
+        return $this->class->methods->constructor()->signature;
     }
 }
index 54447c757df80a43bf4191097cff8e6be13f304b..54eccbf42791b9aa835c84dee68e3a92555c735c 100644 (file)
@@ -17,12 +17,12 @@ final class ReflectionObjectBuilder implements ObjectBuilder
 
     public function describeArguments(): Arguments
     {
-        return $this->arguments ??= Arguments::fromProperties($this->class->properties());
+        return $this->arguments ??= Arguments::fromProperties($this->class->properties);
     }
 
     public function build(array $arguments): object
     {
-        $object = new ($this->class->name())();
+        $object = new ($this->class->name)();
 
         if (count($arguments) > 0) {
             (function () use ($arguments): void {
@@ -37,6 +37,6 @@ final class ReflectionObjectBuilder implements ObjectBuilder
 
     public function signature(): string
     {
-        return $this->class->name() . ' (properties)';
+        return $this->class->name . ' (properties)';
     }
 }
index db2de428b2dd2689b8ccb0807e42f791378ef4b7..9a50c340faf7386b099669ad6e25be6401685359 100644 (file)
@@ -5,6 +5,10 @@ declare(strict_types=1);
 namespace CuyZ\Valinor\Mapper\Tree\Builder;
 
 use CuyZ\Valinor\Mapper\Tree\Shell;
+use CuyZ\Valinor\Type\CompositeTraversableType;
+use CuyZ\Valinor\Type\Type;
+use CuyZ\Valinor\Type\Types\ShapedArrayType;
+use CuyZ\Valinor\Type\Types\UnionType;
 
 /** @internal */
 final class CasterProxyNodeBuilder implements NodeBuilder
@@ -16,11 +20,28 @@ final class CasterProxyNodeBuilder implements NodeBuilder
         if ($shell->hasValue()) {
             $value = $shell->value();
 
-            if ($shell->type()->accepts($value)) {
+            if ($this->typeAcceptsValue($shell->type(), $value)) {
                 return TreeNode::leaf($shell, $value);
             }
         }
 
         return $this->delegate->build($shell, $rootBuilder);
     }
+
+    private function typeAcceptsValue(Type $type, mixed $value): bool
+    {
+        if ($type instanceof UnionType) {
+            foreach ($type->types() as $subType) {
+                if ($this->typeAcceptsValue($subType, $value)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        return ! $type instanceof CompositeTraversableType
+            && ! $type instanceof ShapedArrayType
+            && $type->accepts($value);
+    }
 }
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/FilteredObjectNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/FilteredObjectNodeBuilder.php
new file mode 100644 (file)
index 0000000..712de13
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Mapper\Tree\Builder;
+
+use CuyZ\Valinor\Mapper\Object\ArgumentsValues;
+use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
+use CuyZ\Valinor\Mapper\Tree\Exception\UnexpectedArrayKeysForClass;
+use CuyZ\Valinor\Mapper\Tree\Shell;
+
+use function count;
+
+/** @internal */
+final class FilteredObjectNodeBuilder
+{
+    public function __construct(private bool $allowSuperfluousKeys) {}
+
+    public function build(ObjectBuilder $builder, Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+    {
+        $arguments = ArgumentsValues::forClass($builder->describeArguments(), $shell->value(), $this->allowSuperfluousKeys);
+
+        $children = $this->children($shell, $arguments, $rootBuilder);
+
+        $object = $this->buildObject($builder, $children);
+
+        $node = $arguments->hadSingleArgument()
+            ? TreeNode::flattenedBranch($shell, $object, $children[0])
+            : TreeNode::branch($shell, $object, $children);
+
+        if (! $this->allowSuperfluousKeys && count($arguments->superfluousKeys()) > 0) {
+            $node = $node->withMessage(new UnexpectedArrayKeysForClass($arguments));
+        }
+
+        return $node;
+    }
+
+    /**
+     * @return array<TreeNode>
+     */
+    private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuilder $rootBuilder): array
+    {
+        $children = [];
+
+        foreach ($arguments as $argument) {
+            $name = $argument->name();
+            $type = $argument->type();
+            $attributes = $argument->attributes();
+
+            $child = $shell->child($name, $type, $attributes);
+
+            if ($arguments->hasValue($name)) {
+                $child = $child->withValue($arguments->getValue($name));
+            }
+
+            $children[] = $rootBuilder->build($child);
+        }
+
+        return $children;
+    }
+
+    /**
+     * @param TreeNode[] $children
+     */
+    private function buildObject(ObjectBuilder $builder, array $children): ?object
+    {
+        $arguments = [];
+
+        foreach ($children as $child) {
+            if (! $child->isValid()) {
+                return null;
+            }
+
+            $arguments[$child->name()] = $child->value();
+        }
+
+        return $builder->build($arguments);
+    }
+}
index eb6a03c44f456e55ee084aa263af371c07022975..b96b663a48eb680a3a96de3a17f81df2d4e9e117 100644 (file)
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Mapper\Tree\Builder;
 
+use CuyZ\Valinor\Definition\FunctionsContainer;
 use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
 use CuyZ\Valinor\Mapper\Object\Arguments;
 use CuyZ\Valinor\Mapper\Object\ArgumentsValues;
@@ -11,9 +12,11 @@ use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
 use CuyZ\Valinor\Mapper\Object\FilteredObjectBuilder;
 use CuyZ\Valinor\Mapper\Tree\Exception\CannotInferFinalClass;
 use CuyZ\Valinor\Mapper\Tree\Exception\CannotResolveObjectType;
+use CuyZ\Valinor\Mapper\Tree\Exception\InterfaceHasBothConstructorAndInfer;
 use CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationCallbackError;
 use CuyZ\Valinor\Mapper\Tree\Message\UserlandError;
 use CuyZ\Valinor\Mapper\Tree\Shell;
+use CuyZ\Valinor\Type\Type;
 use CuyZ\Valinor\Type\Types\NativeClassType;
 use CuyZ\Valinor\Type\Types\InterfaceType;
 
@@ -25,8 +28,10 @@ final class InterfaceNodeBuilder implements NodeBuilder
         private ObjectImplementations $implementations,
         private ClassDefinitionRepository $classDefinitionRepository,
         private ObjectBuilderFactory $objectBuilderFactory,
-        private ObjectNodeBuilder $objectNodeBuilder,
-        private bool $enableFlexibleCasting
+        private FilteredObjectNodeBuilder $filteredObjectNodeBuilder,
+        private FunctionsContainer $constructors,
+        private bool $enableFlexibleCasting,
+        private bool $allowSuperfluousKeys,
     ) {}
 
     public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
@@ -37,6 +42,14 @@ final class InterfaceNodeBuilder implements NodeBuilder
             return $this->delegate->build($shell, $rootBuilder);
         }
 
+        if ($this->constructorRegisteredFor($type)) {
+            if ($this->implementations->has($type->className())) {
+                throw new InterfaceHasBothConstructorAndInfer($type->className());
+            }
+
+            return $this->delegate->build($shell, $rootBuilder);
+        }
+
         if ($this->enableFlexibleCasting && $shell->value() === null) {
             $shell = $shell->withValue([]);
         }
@@ -44,7 +57,7 @@ final class InterfaceNodeBuilder implements NodeBuilder
         $className = $type->className();
 
         if (! $this->implementations->has($className)) {
-            if ($type instanceof InterfaceType || $this->classDefinitionRepository->for($type)->isAbstract()) {
+            if ($type instanceof InterfaceType || $this->classDefinitionRepository->for($type)->isAbstract) {
                 throw new CannotResolveObjectType($className);
             }
 
@@ -52,9 +65,9 @@ final class InterfaceNodeBuilder implements NodeBuilder
         }
 
         $function = $this->implementations->function($className);
-        $arguments = Arguments::fromParameters($function->parameters());
+        $arguments = Arguments::fromParameters($function->parameters);
 
-        if ($type instanceof NativeClassType && $this->classDefinitionRepository->for($type)->isFinal()) {
+        if ($type instanceof NativeClassType && $this->classDefinitionRepository->for($type)->isFinal) {
             throw new CannotInferFinalClass($type, $function);
         }
 
@@ -73,15 +86,26 @@ final class InterfaceNodeBuilder implements NodeBuilder
         try {
             $classType = $this->implementations->implementation($className, $values);
         } catch (ObjectImplementationCallbackError $exception) {
-            throw UserlandError::from($exception->original());
+            throw UserlandError::from($exception);
         }
 
         $class = $this->classDefinitionRepository->for($classType);
-        $objectBuilder = new FilteredObjectBuilder($shell->value(), ...$this->objectBuilderFactory->for($class));
+        $objectBuilder = FilteredObjectBuilder::from($shell->value(), ...$this->objectBuilderFactory->for($class));
 
         $shell = $this->transformSourceForClass($shell, $arguments, $objectBuilder->describeArguments());
 
-        return $this->objectNodeBuilder->build($objectBuilder, $shell, $rootBuilder);
+        return $this->filteredObjectNodeBuilder->build($objectBuilder, $shell, $rootBuilder);
+    }
+
+    private function constructorRegisteredFor(Type $type): bool
+    {
+        foreach ($this->constructors as $constructor) {
+            if ($type->matches($constructor->definition->returnType)) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     private function transformSourceForClass(Shell $shell, Arguments $interfaceArguments, Arguments $classArguments): Shell
@@ -116,7 +140,7 @@ final class InterfaceNodeBuilder implements NodeBuilder
      */
     private function children(Shell $shell, Arguments $arguments, RootNodeBuilder $rootBuilder): array
     {
-        $arguments = ArgumentsValues::forInterface($arguments, $shell->value());
+        $arguments = ArgumentsValues::forInterface($arguments, $shell->value(), $this->allowSuperfluousKeys);
 
         $children = [];
 
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NativeClassNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NativeClassNodeBuilder.php
deleted file mode 100644 (file)
index f129b3d..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Mapper\Tree\Builder;
-
-use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
-use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
-use CuyZ\Valinor\Mapper\Object\FilteredObjectBuilder;
-use CuyZ\Valinor\Mapper\Tree\Shell;
-use CuyZ\Valinor\Type\ClassType;
-
-use function assert;
-
-/** @internal */
-final class NativeClassNodeBuilder implements NodeBuilder
-{
-    public function __construct(
-        private ClassDefinitionRepository $classDefinitionRepository,
-        private ObjectBuilderFactory $objectBuilderFactory,
-        private ObjectNodeBuilder $objectNodeBuilder,
-        private bool $enableFlexibleCasting,
-    ) {}
-
-    public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
-    {
-        $type = $shell->type();
-
-        // @infection-ignore-all
-        assert($type instanceof ClassType);
-
-        if ($this->enableFlexibleCasting && $shell->value() === null) {
-            $shell = $shell->withValue([]);
-        }
-
-        $class = $this->classDefinitionRepository->for($type);
-        $objectBuilder = new FilteredObjectBuilder($shell->value(), ...$this->objectBuilderFactory->for($class));
-
-        return $this->objectNodeBuilder->build($objectBuilder, $shell, $rootBuilder);
-    }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php
new file mode 100644 (file)
index 0000000..d7631be
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Mapper\Tree\Builder;
+
+use CuyZ\Valinor\Mapper\Tree\Exception\SourceIsNotNull;
+use CuyZ\Valinor\Mapper\Tree\Shell;
+use CuyZ\Valinor\Type\Types\NullType;
+
+use function assert;
+
+/** @internal */
+final class NullNodeBuilder implements NodeBuilder
+{
+    public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+    {
+        $type = $shell->type();
+        $value = $shell->value();
+
+        assert($type instanceof NullType);
+
+        if ($value !== null) {
+            throw new SourceIsNotNull();
+        }
+
+        return TreeNode::leaf($shell, null);
+    }
+}
index 8b9ba566e33062aaeda94774888c0f1bf0ce38d4..bf92f657209b760a7ae40ed0ab954424516b9430 100644 (file)
@@ -6,14 +6,12 @@ namespace CuyZ\Valinor\Mapper\Tree\Builder;
 
 use CuyZ\Valinor\Definition\FunctionDefinition;
 use CuyZ\Valinor\Definition\FunctionsContainer;
-use CuyZ\Valinor\Mapper\Tree\Exception\InvalidAbstractObjectName;
 use CuyZ\Valinor\Mapper\Tree\Exception\InvalidResolvedImplementationValue;
 use CuyZ\Valinor\Mapper\Tree\Exception\MissingObjectImplementationRegistration;
 use CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationCallbackError;
 use CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationNotRegistered;
 use CuyZ\Valinor\Mapper\Tree\Exception\ResolvedImplementationIsNotAccepted;
 use CuyZ\Valinor\Type\ClassType;
-use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
 use CuyZ\Valinor\Type\Parser\TypeParser;
 use CuyZ\Valinor\Type\Type;
 use CuyZ\Valinor\Type\Types\ClassStringType;
@@ -21,6 +19,8 @@ use CuyZ\Valinor\Type\Types\InterfaceType;
 use CuyZ\Valinor\Type\Types\UnionType;
 use Exception;
 
+use function assert;
+
 /** @internal */
 final class ObjectImplementations
 {
@@ -30,12 +30,7 @@ final class ObjectImplementations
     public function __construct(
         private FunctionsContainer $functions,
         private TypeParser $typeParser
-    ) {
-        foreach ($functions as $name => $function) {
-            /** @var string $name */
-            $this->implementations[$name] = $this->implementations($name);
-        }
-    }
+    ) {}
 
     public function has(string $name): bool
     {
@@ -44,7 +39,7 @@ final class ObjectImplementations
 
     public function function(string $name): FunctionDefinition
     {
-        return $this->functions->get($name)->definition();
+        return $this->functions->get($name)->definition;
     }
 
     /**
@@ -52,6 +47,9 @@ final class ObjectImplementations
      */
     public function implementation(string $name, array $arguments): ClassType
     {
+        /** @infection-ignore-all / We cannot test the assignment */
+        $this->implementations[$name] ??= $this->implementations($name);
+
         $class = $this->call($name, $arguments);
 
         return $this->implementations[$name][$class]
@@ -64,7 +62,7 @@ final class ObjectImplementations
     private function call(string $name, array $arguments): string
     {
         try {
-            $signature = ($this->functions->get($name)->callback())(...$arguments);
+            $signature = ($this->functions->get($name)->callback)(...$arguments);
         } catch (Exception $exception) {
             throw new ObjectImplementationCallbackError($name, $exception);
         }
@@ -81,20 +79,16 @@ final class ObjectImplementations
      */
     private function implementations(string $name): array
     {
-        $function = $this->functions->get($name)->definition();
+        $function = $this->functions->get($name)->definition;
 
-        try {
-            $type = $this->typeParser->parse($name);
-        } catch (InvalidType) {
-        }
+        $type = $this->typeParser->parse($name);
 
-        if (! isset($type) || (! $type instanceof InterfaceType && ! $type instanceof ClassType)) {
-            throw new InvalidAbstractObjectName($name);
-        }
+        /** @infection-ignore-all */
+        assert($type instanceof InterfaceType || $type instanceof ClassType);
 
         $classes = $this->implementationsByReturnSignature($name, $function);
 
-        if (empty($classes)) {
+        if ($classes === []) {
             throw new MissingObjectImplementationRegistration($name, $function);
         }
 
@@ -113,10 +107,10 @@ final class ObjectImplementations
      */
     private function implementationsByReturnSignature(string $name, FunctionDefinition $function): array
     {
-        $returnType = $function->returnType();
+        $returnType = $function->returnType;
 
         if (! $returnType instanceof ClassStringType && ! $returnType instanceof UnionType) {
-            if (count($function->parameters()) > 0) {
+            if (count($function->parameters) > 0) {
                 return [];
             }
 
index e0965e5b4996517fe9bbee502c283fd8348087fc..e67b613a768d1d432822f8c54ac5619aec215880 100644 (file)
@@ -4,76 +4,39 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Mapper\Tree\Builder;
 
-use CuyZ\Valinor\Mapper\Object\ArgumentsValues;
-use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
-use CuyZ\Valinor\Mapper\Tree\Exception\UnexpectedArrayKeysForClass;
+use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
+use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
+use CuyZ\Valinor\Mapper\Object\FilteredObjectBuilder;
 use CuyZ\Valinor\Mapper\Tree\Shell;
 
-use function count;
+use CuyZ\Valinor\Type\ObjectType;
+
+use function assert;
 
 /** @internal */
-final class ObjectNodeBuilder
+final class ObjectNodeBuilder implements NodeBuilder
 {
-    public function __construct(private bool $allowSuperfluousKeys) {}
-
-    public function build(ObjectBuilder $builder, Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+    public function __construct(
+        private ClassDefinitionRepository $classDefinitionRepository,
+        private ObjectBuilderFactory $objectBuilderFactory,
+        private FilteredObjectNodeBuilder $filteredObjectNodeBuilder,
+        private bool $enableFlexibleCasting,
+    ) {}
+
+    public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
     {
-        $arguments = ArgumentsValues::forClass($builder->describeArguments(), $shell->value());
-
-        $children = $this->children($shell, $arguments, $rootBuilder);
+        $type = $shell->type();
 
-        $object = $this->buildObject($builder, $children);
+        // @infection-ignore-all
+        assert($type instanceof ObjectType);
 
-        $node = $arguments->hadSingleArgument()
-            ? TreeNode::flattenedBranch($shell, $object, $children[0])
-            : TreeNode::branch($shell, $object, $children);
-
-        if (! $this->allowSuperfluousKeys && count($arguments->superfluousKeys()) > 0) {
-            $node = $node->withMessage(new UnexpectedArrayKeysForClass($arguments));
+        if ($this->enableFlexibleCasting && $shell->value() === null) {
+            $shell = $shell->withValue([]);
         }
 
-        return $node;
-    }
-
-    /**
-     * @return array<TreeNode>
-     */
-    private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuilder $rootBuilder): array
-    {
-        $children = [];
-
-        foreach ($arguments as $argument) {
-            $name = $argument->name();
-            $type = $argument->type();
-            $attributes = $argument->attributes();
-
-            $child = $shell->child($name, $type, $attributes);
-
-            if ($arguments->hasValue($name)) {
-                $child = $child->withValue($arguments->getValue($name));
-            }
-
-            $children[] = $rootBuilder->build($child);
-        }
-
-        return $children;
-    }
-
-    /**
-     * @param TreeNode[] $children
-     */
-    private function buildObject(ObjectBuilder $builder, array $children): ?object
-    {
-        $arguments = [];
-
-        foreach ($children as $child) {
-            if (! $child->isValid()) {
-                return null;
-            }
-
-            $arguments[$child->name()] = $child->value();
-        }
+        $class = $this->classDefinitionRepository->for($type);
+        $objectBuilder = FilteredObjectBuilder::from($shell->value(), ...$this->objectBuilderFactory->for($class));
 
-        return $builder->build($arguments);
+        return $this->filteredObjectNodeBuilder->build($objectBuilder, $shell, $rootBuilder);
     }
 }
index 492d74a402434aa33ea7c4d6c7a75a9660ca0c81..6dc633f235237c2492f67b97981f22e9cc93e42d 100644 (file)
@@ -69,6 +69,15 @@ final class ShapedArrayNodeBuilder implements NodeBuilder
             unset($value[$key]);
         }
 
+        if ($type->isUnsealed()) {
+            $unsealedShell = $shell->withType($type->unsealedType())->withValue($value);
+            $unsealedChildren = $rootBuilder->build($unsealedShell)->children();
+
+            foreach ($unsealedChildren as $unsealedChild) {
+                $children[$unsealedChild->name()] = $unsealedChild;
+            }
+        }
+
         return $children;
     }
 
index 8a17f437e72e4380fc4ab3408c97a6488ef9371a..890c2ec053b4eccefa151c0b13b1afa1118d804f 100644 (file)
@@ -9,6 +9,7 @@ use CuyZ\Valinor\Mapper\Tree\Message\Message;
 use CuyZ\Valinor\Mapper\Tree\Node;
 use CuyZ\Valinor\Mapper\Tree\Shell;
 use CuyZ\Valinor\Type\FloatType;
+use CuyZ\Valinor\Type\Type;
 use Throwable;
 
 use function array_map;
@@ -77,11 +78,7 @@ final class TreeNode
         return $instance;
     }
 
-    /**
-     * PHP8.1 intersection
-     * @param Throwable&Message $message
-     */
-    public static function error(Shell $shell, Throwable $message): self
+    public static function error(Shell $shell, Throwable&Message $message): self
     {
         return (new self($shell, null))->withMessage($message);
     }
@@ -91,6 +88,19 @@ final class TreeNode
         return $this->shell->name();
     }
 
+    public function type(): Type
+    {
+        return $this->shell->type();
+    }
+
+    /**
+     * @return array<self>
+     */
+    public function children(): array
+    {
+        return $this->children;
+    }
+
     public function isValid(): bool
     {
         return $this->valid;
index eebd747aec9eb7344175b2a582d5100fe6fa3966..c118c2e5d0ff567845d619d55c536f98b573f0f8 100644 (file)
@@ -4,30 +4,26 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Mapper\Tree\Builder;
 
-use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
-use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
-use CuyZ\Valinor\Mapper\Object\FilteredObjectBuilder;
-use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
 use CuyZ\Valinor\Mapper\Tree\Exception\CannotResolveTypeFromUnion;
+use CuyZ\Valinor\Mapper\Tree\Exception\TooManyResolvedTypesFromUnion;
 use CuyZ\Valinor\Mapper\Tree\Shell;
-use CuyZ\Valinor\Type\ScalarType;
-use CuyZ\Valinor\Type\Type;
 use CuyZ\Valinor\Type\ClassType;
-use CuyZ\Valinor\Type\Types\NullType;
+use CuyZ\Valinor\Type\FloatType;
+use CuyZ\Valinor\Type\IntegerType;
+use CuyZ\Valinor\Type\ScalarType;
+use CuyZ\Valinor\Type\StringType;
+use CuyZ\Valinor\Type\Types\InterfaceType;
+use CuyZ\Valinor\Type\Types\ShapedArrayType;
 use CuyZ\Valinor\Type\Types\UnionType;
 
 use function count;
+use function krsort;
+use function reset;
 
 /** @internal */
 final class UnionNodeBuilder implements NodeBuilder
 {
-    public function __construct(
-        private NodeBuilder $delegate,
-        private ClassDefinitionRepository $classDefinitionRepository,
-        private ObjectBuilderFactory $objectBuilderFactory,
-        private ObjectNodeBuilder $objectNodeBuilder,
-        private bool $enableFlexibleCasting
-    ) {}
+    public function __construct(private NodeBuilder $delegate) {}
 
     public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
     {
@@ -37,72 +33,78 @@ final class UnionNodeBuilder implements NodeBuilder
             return $this->delegate->build($shell, $rootBuilder);
         }
 
-        $classNode = $this->tryToBuildClassNode($type, $shell, $rootBuilder);
-
-        if ($classNode instanceof TreeNode) {
-            return $classNode;
-        }
+        $structs = [];
+        $scalars = [];
+        $all = [];
 
-        $narrowedType = $this->narrow($type, $shell->value());
+        foreach ($type->types() as $subType) {
+            $node = $rootBuilder->build($shell->withType($subType));
 
-        return $rootBuilder->build($shell->withType($narrowedType));
-    }
-
-    private function narrow(UnionType $type, mixed $source): Type
-    {
-        $subTypes = $type->types();
-
-        if ($source !== null && count($subTypes) === 2) {
-            if ($subTypes[0] instanceof NullType) {
-                return $subTypes[1];
-            } elseif ($subTypes[1] instanceof NullType) {
-                return $subTypes[0];
-            }
-        }
-
-        foreach ($subTypes as $subType) {
-            if (! $subType instanceof ScalarType) {
+            if (! $node->isValid()) {
                 continue;
             }
 
-            if (! $this->enableFlexibleCasting) {
-                continue;
-            }
+            $all[] = $node;
 
-            if ($subType->canCast($source)) {
-                return $subType;
+            if ($subType instanceof InterfaceType || $subType instanceof ClassType || $subType instanceof ShapedArrayType) {
+                $structs[] = $node;
+            } elseif ($subType instanceof ScalarType) {
+                $scalars[] = $node;
             }
         }
 
-        throw new CannotResolveTypeFromUnion($source, $type);
-    }
-
-    private function tryToBuildClassNode(UnionType $type, Shell $shell, RootNodeBuilder $rootBuilder): ?TreeNode
-    {
-        $classTypes = array_filter(
-            $type->types(),
-            fn (Type $type) => $type instanceof ClassType,
-        );
+        if ($all === []) {
+            throw new CannotResolveTypeFromUnion($shell->value(), $type);
+        }
 
-        if (count($classTypes) === 0) {
-            return null;
+        if (count($all) === 1) {
+            return $all[0];
         }
 
-        $objectBuilder = $this->objectBuilder($shell->value(), ...$classTypes);
+        if ($structs !== []) {
+            // Structs can be either an interface, a class or a shaped array.
+            // We prioritize the one with the most children, as it's the most
+            // specific type. If there are multiple types with the same number
+            // of children, we consider it as a collision.
+            $childrenCount = [];
 
-        return $this->objectNodeBuilder->build($objectBuilder, $shell, $rootBuilder);
-    }
+            foreach ($structs as $node) {
+                $childrenCount[count($node->children())][] = $node;
+            }
 
-    private function objectBuilder(mixed $value, ClassType ...$types): ObjectBuilder
-    {
-        $builders = [];
+            krsort($childrenCount);
+
+            $first = reset($childrenCount);
+
+            if (count($first) === 1) {
+                return $first[0];
+            }
+        } elseif ($scalars !== []) {
+            // Sorting the scalar types by priority: int, float, string, bool.
+            $sorted = [];
+
+            foreach ($scalars as $node) {
+                if ($node->type() instanceof IntegerType) {
+                    $sorted[IntegerType::class] = $node;
+                } elseif ($node->type() instanceof FloatType) {
+                    $sorted[FloatType::class] = $node;
+                } elseif ($node->type() instanceof StringType) {
+                    $sorted[StringType::class] = $node;
+                }
+            }
 
-        foreach ($types as $type) {
-            $class = $this->classDefinitionRepository->for($type);
+            if (isset($sorted[IntegerType::class])) {
+                return $sorted[IntegerType::class];
+            } elseif (isset($sorted[FloatType::class])) {
+                return $sorted[FloatType::class];
+            } elseif (isset($sorted[StringType::class])) {
+                return $sorted[StringType::class];
+            }
 
-            $builders = [...$builders, ...$this->objectBuilderFactory->for($class)];
+            // @infection-ignore-all / We know this is a boolean, so we don't need to mutate the index
+            return $scalars[0];
         }
 
-        return new FilteredObjectBuilder($value, ...$builders);
+        throw new TooManyResolvedTypesFromUnion($type);
     }
 }
index 66a6630dac91704fe0849f26a121754c6516991a..ed518d946e1f7f9c66a03fad5cf6c0593b5c44d1 100644 (file)
@@ -26,19 +26,19 @@ final class ValueAlteringNodeBuilder implements NodeBuilder
         $value = $node->value();
 
         foreach ($this->functions as $function) {
-            $parameters = $function->definition()->parameters();
+            $parameters = $function->definition->parameters;
 
             if (count($parameters) === 0) {
                 continue;
             }
 
-            $firstParameterType = $parameters->at(0)->type();
+            $firstParameterType = $parameters->at(0)->type;
 
             if (! $firstParameterType->accepts($value)) {
                 continue;
             }
 
-            $value = ($function->callback())($value);
+            $value = ($function->callback)($value);
             $node = $node->withValue($value);
         }
 
index d2bd244e4ade21cce9a9befa39e33f1136f4247b..b8e84f7b72e128d08c84807e1df004ee599379d6 100644 (file)
@@ -14,7 +14,7 @@ final class CannotInferFinalClass extends RuntimeException
     public function __construct(ClassType $class, FunctionDefinition $function)
     {
         parent::__construct(
-            "Cannot infer final class `{$class->className()}` with function `{$function->signature()}`.",
+            "Cannot infer final class `{$class->className()}` with function `$function->signature`.",
             1671468163
         );
     }
index ee470cacdb9036bb16dab528d6a082801754831c..a8dacb2f6ce899c4cdd2af8813339cfb57d2704a 100644 (file)
@@ -27,8 +27,7 @@ final class CannotResolveTypeFromUnion extends RuntimeException implements Error
         $this->parameters = [
             'allowed_types' => implode(
                 ', ',
-                // PHP8.1 First-class callable syntax
-                array_map([TypeHelper::class, 'dump'], $unionType->types())
+                array_map(TypeHelper::dump(...), $unionType->types())
             ),
         ];
 
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InterfaceHasBothConstructorAndInfer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InterfaceHasBothConstructorAndInfer.php
new file mode 100644 (file)
index 0000000..77cb985
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Mapper\Tree\Exception;
+
+use LogicException;
+
+/** @internal */
+final class InterfaceHasBothConstructorAndInfer extends LogicException
+{
+    /**
+     * @param interface-string $name
+     */
+    public function __construct(string $name)
+    {
+        parent::__construct(
+            "Interface `$name` is configured with at least one constructor but also has an infer configuration. Only one method can be used.",
+            1711915749,
+        );
+    }
+}
index 9d5b4712673b413bc647e86a76200f77fe89917d..850c3097c03a12ebc86f89be4f166a9b64f97941 100644 (file)
@@ -13,7 +13,7 @@ final class MissingObjectImplementationRegistration extends RuntimeException
     public function __construct(string $name, FunctionDefinition $functionDefinition)
     {
         parent::__construct(
-            "No implementation of `$name` found with return type `{$functionDefinition->returnType()->toString()}` of `{$functionDefinition->signature()}`.",
+            "No implementation of `$name` found with return type `{$functionDefinition->returnType->toString()}` of `$functionDefinition->signature`.",
             1653990549
         );
     }
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php
new file mode 100644 (file)
index 0000000..a07f436
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Mapper\Tree\Exception;
+
+use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use RuntimeException;
+
+/** @internal */
+final class SourceIsNotNull extends RuntimeException implements ErrorMessage
+{
+    private string $body;
+
+    public function __construct()
+    {
+        $this->body = 'Value {source_value} is not null.';
+
+        parent::__construct($this->body, 1710263908);
+    }
+
+    public function body(): string
+    {
+        return $this->body;
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php
new file mode 100644 (file)
index 0000000..857eaae
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Mapper\Tree\Exception;
+
+use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
+use CuyZ\Valinor\Type\Types\UnionType;
+use CuyZ\Valinor\Utility\String\StringFormatter;
+use CuyZ\Valinor\Utility\TypeHelper;
+use RuntimeException;
+
+use function array_map;
+use function implode;
+
+/** @internal */
+final class TooManyResolvedTypesFromUnion extends RuntimeException implements ErrorMessage, HasParameters
+{
+    private string $body;
+
+    /** @var array<string, string> */
+    private array $parameters;
+
+    public function __construct(UnionType $unionType)
+    {
+        $this->parameters = [
+            'allowed_types' => implode(
+                ', ',
+                array_map(TypeHelper::dump(...), $unionType->types())
+            ),
+        ];
+
+        $this->body = TypeHelper::containsObject($unionType)
+            ? 'Invalid value {source_value}, it matches two or more types from union: cannot take a decision.'
+            : 'Invalid value {source_value}, it matches two or more types from {allowed_types}: cannot take a decision.';
+
+        parent::__construct(StringFormatter::for($this), 1710262975);
+    }
+
+    public function body(): string
+    {
+        return $this->body;
+    }
+
+    public function parameters(): array
+    {
+        return $this->parameters;
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/UnresolvableShellType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/UnresolvableShellType.php
new file mode 100644 (file)
index 0000000..5552973
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Mapper\Tree\Exception;
+
+use CuyZ\Valinor\Type\Types\UnresolvableType;
+use LogicException;
+
+/** @internal */
+final class UnresolvableShellType extends LogicException
+{
+    public function __construct(UnresolvableType $type)
+    {
+        parent::__construct($type->message());
+    }
+}
index b35d0f58302a9404a3ea2de73843c40ed7a962fb..3fdb96bdb6cdae12b2b8179ff4c1f1cedbcfd2bc 100644 (file)
@@ -35,6 +35,9 @@ interface DefaultMessage
         'Value {source_value} does not match string value {expected_value}.' => [
             'en' => 'Value {source_value} does not match string value {expected_value}.',
         ],
+        'Value {source_value} is not null.' => [
+            'en' => 'Value {source_value} is not null.',
+        ],
         'Value {source_value} is not a valid boolean.' => [
             'en' => 'Value {source_value} is not a valid boolean.',
         ],
@@ -74,6 +77,12 @@ interface DefaultMessage
         'Invalid value {source_value}.' => [
             'en' => 'Invalid value {source_value}.',
         ],
+        'Invalid value {source_value}, it matches at least two types from union.' => [
+            'en' => 'Invalid value {source_value}, it matches at least two types from union.',
+        ],
+        'Invalid value {source_value}, it matches at least two types from {allowed_types}.' => [
+            'en' => 'Invalid value {source_value}, it matches at least two types from {allowed_types}.',
+        ],
         'Invalid sequential key {key}, expected {expected}.' => [
             'en' => 'Invalid sequential key {key}, expected {expected}.',
         ],
index aa7e55cf210d9863a288dfc56e5d8bd5cd0203b5..943e1ed26d03bb29bc457b71a06095edfd15aa6e 100644 (file)
@@ -121,10 +121,9 @@ final class MessageBuilder
     /**
      * @psalm-pure
      *
-     * PHP8.1 intersection
      * @return MessageType&HasCode&HasParameters
      */
-    public function build(): Message
+    public function build(): Message&HasCode&HasParameters
     {
         /** @var MessageType&HasCode&HasParameters */
         return $this->isError
@@ -132,7 +131,7 @@ final class MessageBuilder
             : $this->buildMessage();
     }
 
-    private function buildMessage(): Message
+    private function buildMessage(): Message&HasCode&HasParameters
     {
         return new class ($this->body, $this->code, $this->parameters) implements Message, HasCode, HasParameters {
             /**
@@ -161,7 +160,7 @@ final class MessageBuilder
         };
     }
 
-    private function buildErrorMessage(): ErrorMessage
+    private function buildErrorMessage(): ErrorMessage&HasCode&HasParameters
     {
         return new class ($this->body, $this->code, $this->parameters) extends RuntimeException implements ErrorMessage, HasCode, HasParameters {
             /**
index 2313239a7d1e29b522e29175de939e5224c33e0d..fd4fc4f41b817b60a02c5e8b10c0545b2fa9eef8 100644 (file)
@@ -10,11 +10,7 @@ use Throwable;
 /** @internal */
 final class UserlandError extends RuntimeException implements ErrorMessage
 {
-    /**
-     * PHP8.1 intersection
-     * @return Message&Throwable
-     */
-    public static function from(Throwable $message): Message
+    public static function from(Throwable $message): Message&Throwable
     {
         // @infection-ignore-all
         return $message instanceof Message
index 8d61989189b31002238fddc17631161790e25c6e..ca87bafe78296b06c4b4e44aa1d0bfa6cb2ebc9b 100644 (file)
@@ -5,7 +5,7 @@ declare(strict_types=1);
 namespace CuyZ\Valinor\Mapper\Tree;
 
 use CuyZ\Valinor\Definition\Attributes;
-use CuyZ\Valinor\Definition\AttributesContainer;
+use CuyZ\Valinor\Mapper\Tree\Exception\UnresolvableShellType;
 use CuyZ\Valinor\Type\Type;
 use CuyZ\Valinor\Type\Types\UnresolvableType;
 
@@ -28,7 +28,9 @@ final class Shell
 
     private function __construct(private Type $type)
     {
-        assert(! $type instanceof UnresolvableType);
+        if ($type instanceof UnresolvableType) {
+            throw new UnresolvableShellType($type);
+        }
     }
 
     public static function root(Type $type, mixed $value): self
@@ -95,7 +97,7 @@ final class Shell
 
     public function attributes(): Attributes
     {
-        return $this->attributes ?? AttributesContainer::empty();
+        return $this->attributes ?? Attributes::empty();
     }
 
     public function path(): string
index 5d128cdc233b98e2784420b75af2e3b0a81ec1f1..f0d73f81166444f56d44537c7df215a21ca29006 100644 (file)
@@ -6,7 +6,9 @@ namespace CuyZ\Valinor\Mapper;
 
 use CuyZ\Valinor\Definition\ParameterDefinition;
 use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
+use CuyZ\Valinor\Mapper\Exception\TypeErrorDuringArgumentsMapping;
 use CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder;
+use CuyZ\Valinor\Mapper\Tree\Exception\UnresolvableShellType;
 use CuyZ\Valinor\Mapper\Tree\Shell;
 use CuyZ\Valinor\Type\Types\ShapedArrayElement;
 use CuyZ\Valinor\Type\Types\ShapedArrayType;
@@ -32,17 +34,21 @@ final class TypeArgumentsMapper implements ArgumentsMapper
 
         $elements = array_map(
             fn (ParameterDefinition $parameter) => new ShapedArrayElement(
-                new StringValueType($parameter->name()),
-                $parameter->type(),
-                $parameter->isOptional()
+                new StringValueType($parameter->name),
+                $parameter->type,
+                $parameter->isOptional
             ),
-            iterator_to_array($function->parameters())
+            iterator_to_array($function->parameters)
         );
 
         $type = new ShapedArrayType(...$elements);
         $shell = Shell::root($type, $source);
 
-        $node = $this->nodeBuilder->build($shell);
+        try {
+            $node = $this->nodeBuilder->build($shell);
+        } catch (UnresolvableShellType $exception) {
+            throw new TypeErrorDuringArgumentsMapping($function, $exception);
+        }
 
         if (! $node->isValid()) {
             throw new ArgumentsMapperError($function, $node->node());
index a9b4194c7e84c647a9f503cf33560353eff88b73..d7691dc18511f2d7b18665e2ae924b43cd6ef554 100644 (file)
@@ -5,8 +5,10 @@ declare(strict_types=1);
 namespace CuyZ\Valinor\Mapper;
 
 use CuyZ\Valinor\Mapper\Exception\InvalidMappingTypeSignature;
+use CuyZ\Valinor\Mapper\Exception\TypeErrorDuringMapping;
 use CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder;
 use CuyZ\Valinor\Mapper\Tree\Builder\TreeNode;
+use CuyZ\Valinor\Mapper\Tree\Exception\UnresolvableShellType;
 use CuyZ\Valinor\Mapper\Tree\Shell;
 use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
 use CuyZ\Valinor\Type\Parser\TypeParser;
@@ -41,6 +43,10 @@ final class TypeTreeMapper implements TreeMapper
 
         $shell = Shell::root($type, $source);
 
-        return $this->nodeBuilder->build($shell);
+        try {
+            return $this->nodeBuilder->build($shell);
+        } catch (UnresolvableShellType $exception) {
+            throw new TypeErrorDuringMapping($type, $exception);
+        }
     }
 }
index 60d48881769ebec2c604e4a320235bc2b18d3082..48ffecc21a08b0165237e0ac4deefc2d4b5174f4 100644 (file)
@@ -176,8 +176,8 @@ final class MapperBuilder
      *     case CASE_D = 'BAR_VALUE_2';
      *
      *     /**
-     *      * @param 'FOO'|'BAR' $type
-     *      * @param int<1, 2> $number
+     *      * \@param 'FOO'|'BAR' $type
+     *      * \@param int<1, 2> $number
      *      * /
      *     public static function fromMatrix(string $type, int $number): self
      *     {
@@ -477,8 +477,9 @@ final class MapperBuilder
      * transformer will be called. Default priority is 0.
      *
      * An attribute on a property or a class can act as a transformer if:
-     *  1. It is callable (they define an `__invoke` method)
-     *  2. It is registered using `registerTransformer()`
+     *  1. It defines a `normalize` or `normalizeKey` method.
+     *  2. It is registered using either the `registerTransformer()` method or
+     *     the following attribute: @see \CuyZ\Valinor\Normalizer\AsTransformer
      *
      * Example:
      *
@@ -505,9 +506,9 @@ final class MapperBuilder
      *         priority: -100 // Negative priority: transformer is called early
      *     )
      *
-     *     // Transformer attributes must be registered before they are used by
-     *     // the normalizer.
-     *     ->registerTransformer(SomeAttribute::class)
+     *     // External transformer attributes must be registered before they are
+     *     // used by the normalizer.
+     *     ->registerTransformer(\Some\External\TransformerAttribute::class)
      *
      *     ->normalizer()
      *     ->normalize('Hello world'); // HELLO WORLD?!
index c711e5e69f53b1ec6e7fcc29f56a168eaccbf762..c76a000f7e1253465b7c812b65ffdd9b209f3745 100644 (file)
@@ -26,13 +26,10 @@ final class ArrayNormalizer implements Normalizer
     {
         $value = $this->transformer->transform($value);
 
+        /** @var array<mixed>|scalar|null */
         return $this->normalizeIterator($value);
     }
 
-    /**
-     * @param iterable<mixed>|scalar|null $value
-     * @return array<mixed>|scalar|null
-     */
     private function normalizeIterator(mixed $value): mixed
     {
         if (is_iterable($value)) {
@@ -40,8 +37,7 @@ final class ArrayNormalizer implements Normalizer
                 $value = iterator_to_array($value);
             }
 
-            // PHP8.1 First-class callable syntax
-            $value = array_map([$this, 'normalizeIterator'], $value);
+            $value = array_map($this->normalizeIterator(...), $value);
         }
 
         return $value;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/AsTransformer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/AsTransformer.php
new file mode 100644 (file)
index 0000000..7263e9a
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Normalizer;
+
+use Attribute;
+
+/**
+ * This attribute can be used to automatically register a transformer attribute.
+ *
+ * When there is no control over the transformer attribute class, the following
+ * method can be used: @see \CuyZ\Valinor\MapperBuilder::registerTransformer
+ *
+ * ```php
+ * namespace My\App;
+ *
+ * #[\CuyZ\Valinor\Normalizer\AsTransformer]
+ * #[\Attribute(\Attribute::TARGET_PROPERTY)]
+ * final class DateTimeFormat
+ * {
+ *     public function __construct(private string $format) {}
+ *
+ *     public function normalize(\DateTimeInterface $date): string
+ *     {
+ *         return $date->format($this->format);
+ *     }
+ * }
+ *
+ * final readonly class Event
+ * {
+ *     public function __construct(
+ *         public string $eventName,
+ *         #[\My\App\DateTimeFormat('Y/m/d')]
+ *         public \DateTimeInterface $date,
+ *     ) {}
+ * }
+ *
+ * (new \CuyZ\Valinor\MapperBuilder())
+ *     ->normalizer(\CuyZ\Valinor\Normalizer\Format::array())
+ *     ->normalize(new \My\App\Event(
+ *         eventName: 'Release of legendary album',
+ *         date: new \DateTimeImmutable('1971-11-08'),
+ *     ));
+ *
+ * // [
+ * //     'eventName' => 'Release of legendary album',
+ * //     'date' => '1971/11/08',
+ * // ]
+ * ```
+ *
+ * @api
+ */
+#[Attribute(Attribute::TARGET_CLASS)]
+final class AsTransformer {}
index eec49cd50dbaf3f8aa77659ff60451dafe210ea2..3a751f3dbe295b3e06b369ca730f0070eecf8875 100644 (file)
@@ -4,16 +4,16 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Normalizer\Exception;
 
-use CuyZ\Valinor\Definition\FunctionDefinition;
+use CuyZ\Valinor\Definition\MethodDefinition;
 use LogicException;
 
 /** @internal */
 final class KeyTransformerHasTooManyParameters extends LogicException
 {
-    public function __construct(FunctionDefinition $function)
+    public function __construct(MethodDefinition $method)
     {
         parent::__construct(
-            "Key transformer must have at most 1 parameter, {$function->parameters()->count()} given for `{$function->signature()}`.",
+            "Key transformer must have at most 1 parameter, {$method->parameters->count()} given for `$method->signature`.",
             1701701102,
         );
     }
index 30c955fc36304aecf703bbf685d71d2f54566ad9..a1cef8427603e981dc5a144b0261a1a72be73f85 100644 (file)
@@ -4,16 +4,16 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Normalizer\Exception;
 
-use CuyZ\Valinor\Definition\FunctionDefinition;
+use CuyZ\Valinor\Definition\MethodDefinition;
 use LogicException;
 
 /** @internal */
 final class KeyTransformerParameterInvalidType extends LogicException
 {
-    public function __construct(FunctionDefinition $function)
+    public function __construct(MethodDefinition $method)
     {
         parent::__construct(
-            "Key transformer parameter must be a string, {$function->parameters()->at(0)->type()->toString()} given for `{$function->signature()}`.",
+            "Key transformer parameter must be a string, {$method->parameters->at(0)->type->toString()} given for `$method->signature`.",
             1701706316,
         );
     }
index a9794b12098251aed3df24a2ad15eb2fd7245855..b0360044bb6ef447087b52e36e4b72f587516530 100644 (file)
@@ -5,16 +5,17 @@ declare(strict_types=1);
 namespace CuyZ\Valinor\Normalizer\Exception;
 
 use CuyZ\Valinor\Definition\FunctionDefinition;
+use CuyZ\Valinor\Definition\MethodDefinition;
 use CuyZ\Valinor\Type\Type;
 use LogicException;
 
 /** @internal */
 final class TransformerHasInvalidCallableParameter extends LogicException
 {
-    public function __construct(FunctionDefinition $function, Type $parameterType)
+    public function __construct(MethodDefinition|FunctionDefinition $method, Type $parameterType)
     {
         parent::__construct(
-            "Transformer's second parameter must be a callable, `{$parameterType->toString()}` given for `{$function->signature()}`.",
+            "Transformer's second parameter must be a callable, `{$parameterType->toString()}` given for `$method->signature`.",
             1695065710,
         );
     }
index 272e441263375ba32f674308f13467fc46c26109..d04173db1832897c6bb88a997e87710ddc290a7d 100644 (file)
@@ -5,15 +5,16 @@ declare(strict_types=1);
 namespace CuyZ\Valinor\Normalizer\Exception;
 
 use CuyZ\Valinor\Definition\FunctionDefinition;
+use CuyZ\Valinor\Definition\MethodDefinition;
 use LogicException;
 
 /** @internal */
 final class TransformerHasNoParameter extends LogicException
 {
-    public function __construct(FunctionDefinition $function)
+    public function __construct(MethodDefinition|FunctionDefinition $method)
     {
         parent::__construct(
-            "Transformer must have at least one parameter, none given for `{$function->signature()}`.",
+            "Transformer must have at least one parameter, none given for `$method->signature`.",
             1695064946,
         );
     }
index f8291094ec060cbc6bb01040c319ddb1772e42f5..46dc929be2d27c69f8c830d3184a2e60ea30738b 100644 (file)
@@ -5,15 +5,16 @@ declare(strict_types=1);
 namespace CuyZ\Valinor\Normalizer\Exception;
 
 use CuyZ\Valinor\Definition\FunctionDefinition;
+use CuyZ\Valinor\Definition\MethodDefinition;
 use LogicException;
 
 /** @internal */
 final class TransformerHasTooManyParameters extends LogicException
 {
-    public function __construct(FunctionDefinition $function)
+    public function __construct(MethodDefinition|FunctionDefinition $method)
     {
         parent::__construct(
-            "Transformer must have at most 2 parameters, {$function->parameters()->count()} given for `{$function->signature()}`.",
+            "Transformer must have at most 2 parameters, {$method->parameters->count()} given for `$method->signature`.",
             1695065433,
         );
     }
index c8c32b454e5e803084efbabbddf16f5fd3f8d1c0..758b6c3c1f53a1587783fa7e6f67875d98a86195 100644 (file)
@@ -52,6 +52,37 @@ final class Format
         return new self(ArrayNormalizer::class);
     }
 
+    /**
+     * Allows a normalizer to format an input to JSON syntax.
+     *
+     * ```php
+     * namespace My\App;
+     *
+     * $normalizer = (new \CuyZ\Valinor\MapperBuilder())
+     *     ->normalizer(\CuyZ\Valinor\Normalizer\Format::json());
+     *
+     * $userAsJson = $normalizer->normalize(
+     *     new \My\App\User(
+     *         name: 'John Doe',
+     *         age: 42,
+     *         country: new \My\App\Country(
+     *             name: 'France',
+     *             code: 'FR',
+     *         ),
+     *     )
+     * );
+     *
+     * // `$userAsJson` is a valid JSON string representing the data:
+     * // {"name":"John Doe","age":42,"country":{"name":"France","code":"FR"}}
+     * ```
+     *
+     * @return self<JsonNormalizer>
+     */
+    public static function json(): self
+    {
+        return new self(JsonNormalizer::class);
+    }
+
     /**
      * @param class-string<T> $type
      */
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Formatter/Exception/CannotFormatInvalidTypeToJson.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Formatter/Exception/CannotFormatInvalidTypeToJson.php
new file mode 100644 (file)
index 0000000..ae0854c
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Normalizer\Formatter\Exception;
+
+use RuntimeException;
+
+/** @internal */
+final class CannotFormatInvalidTypeToJson extends RuntimeException
+{
+    public function __construct(mixed $value)
+    {
+        $type = get_debug_type($value);
+
+        parent::__construct(
+            "Value of type `$type` cannot be normalized to JSON.",
+            1704749897,
+        );
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Formatter/JsonFormatter.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Formatter/JsonFormatter.php
new file mode 100644 (file)
index 0000000..feec835
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Normalizer\Formatter;
+
+use CuyZ\Valinor\Normalizer\Formatter\Exception\CannotFormatInvalidTypeToJson;
+use Generator;
+
+use function array_is_list;
+use function fwrite;
+use function is_array;
+use function is_bool;
+use function is_iterable;
+use function is_null;
+use function is_scalar;
+use function json_encode;
+
+use const JSON_THROW_ON_ERROR;
+
+/** @internal */
+final class JsonFormatter implements StreamFormatter
+{
+    /**
+     * @param resource $resource
+     */
+    public function __construct(
+        private mixed $resource,
+        private int $jsonEncodingOptions,
+    ) {}
+
+    public function format(mixed $value): void
+    {
+        if (is_null($value)) {
+            $this->write('null');
+        } elseif (is_bool($value)) {
+            $this->write($value ? 'true' : 'false');
+        } elseif (is_scalar($value)) {
+            /**
+             * @phpstan-ignore-next-line / Due to the new json encoding options feature, it is not possible to let SA
+             *                             tools understand that JSON_THROW_ON_ERROR is always set.
+             */
+            $this->write(json_encode($value, $this->jsonEncodingOptions));
+        } elseif (is_iterable($value)) {
+            // Note: when a generator is formatted, it is considered as a list
+            // if its first key is 0. This is done early because the first JSON
+            // character for an array differs from the one for an object, and we
+            // need to know that before actually looping on the generator.
+            //
+            // For generators having a first key of 0 and inconsistent keys
+            // afterward, this leads to a JSON array being written, while it
+            // should have been an object. This is a trade-off we accept,
+            // considering most generators starting at 0 are actually lists.
+            $isList = ($value instanceof Generator && $value->key() === 0)
+                || (is_array($value) && array_is_list($value));
+
+            $isFirst = true;
+
+            $this->write($isList ? '[' : '{');
+
+            foreach ($value as $key => $val) {
+                if (! $isFirst) {
+                    $this->write(',');
+                }
+
+                $isFirst = false;
+
+                if (! $isList) {
+                    $key = json_encode((string)$key, $this->jsonEncodingOptions);
+
+                    $this->write($key . ':');
+                }
+
+                $this->format($val);
+            }
+
+            $this->write($isList ? ']' : '}');
+        } else {
+            throw new CannotFormatInvalidTypeToJson($value);
+        }
+    }
+
+    public function resource(): mixed
+    {
+        return $this->resource;
+    }
+
+    private function write(string $content): void
+    {
+        fwrite($this->resource, $content);
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Formatter/StreamFormatter.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Formatter/StreamFormatter.php
new file mode 100644 (file)
index 0000000..a1c8e4c
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Normalizer\Formatter;
+
+/** @internal */
+interface StreamFormatter
+{
+    public function format(mixed $value): void;
+
+    /**
+     * @return resource
+     */
+    public function resource(): mixed;
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/JsonNormalizer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/JsonNormalizer.php
new file mode 100644 (file)
index 0000000..872955e
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Normalizer;
+
+use CuyZ\Valinor\Normalizer\Formatter\JsonFormatter;
+use CuyZ\Valinor\Normalizer\Transformer\RecursiveTransformer;
+use RuntimeException;
+
+use function fclose;
+use function fopen;
+use function get_debug_type;
+use function is_resource;
+use function stream_get_contents;
+
+use const JSON_HEX_AMP;
+use const JSON_HEX_APOS;
+use const JSON_HEX_QUOT;
+use const JSON_HEX_TAG;
+use const JSON_INVALID_UTF8_IGNORE;
+use const JSON_INVALID_UTF8_SUBSTITUTE;
+use const JSON_NUMERIC_CHECK;
+use const JSON_PRESERVE_ZERO_FRACTION;
+use const JSON_THROW_ON_ERROR;
+use const JSON_UNESCAPED_LINE_TERMINATORS;
+use const JSON_UNESCAPED_SLASHES;
+use const JSON_UNESCAPED_UNICODE;
+
+/**
+ * @api
+ *
+ * @implements Normalizer<string>
+ */
+final class JsonNormalizer implements Normalizer
+{
+    private const ACCEPTABLE_JSON_OPTIONS = JSON_HEX_QUOT
+    | JSON_HEX_TAG
+    | JSON_HEX_AMP
+    | JSON_HEX_APOS
+    | JSON_INVALID_UTF8_IGNORE
+    | JSON_INVALID_UTF8_SUBSTITUTE
+    | JSON_NUMERIC_CHECK
+    | JSON_PRESERVE_ZERO_FRACTION
+    | JSON_UNESCAPED_LINE_TERMINATORS
+    | JSON_UNESCAPED_SLASHES
+    | JSON_UNESCAPED_UNICODE
+    | JSON_THROW_ON_ERROR;
+
+    private RecursiveTransformer $transformer;
+
+    private int $jsonEncodingOptions;
+
+    /**
+     * Internal note
+     * -------------
+     *
+     * We could use the `int-mask-of<JsonNormalizer::JSON_*>` annotation
+     * to let PHPStan infer the type of the accepted options, but some caveats
+     * were found:
+     * - SA tools are not able to infer that we end up having only accepted
+     *   options. Might be fixed with https://github.com/phpstan/phpstan/issues/9384
+     *   for PHPStan but Psalm does have some (not all) issues as well.
+     * - Using this annotation provokes *severe* performance issues when
+     *   running PHPStan analysis, therefore it is preferable to avoid it.
+     */
+    public function __construct(
+        RecursiveTransformer $transformer,
+        int $jsonEncodingOptions = JSON_THROW_ON_ERROR,
+    ) {
+        $this->transformer = $transformer;
+        $this->jsonEncodingOptions = (self::ACCEPTABLE_JSON_OPTIONS & $jsonEncodingOptions) | JSON_THROW_ON_ERROR;
+    }
+
+    /**
+     * By default, the JSON normalizer will only use `JSON_THROW_ON_ERROR` to
+     * encode non-boolean scalar values. There might be use-cases where projects
+     * will need flags like `JSON_JSON_PRESERVE_ZERO_FRACTION`.
+     *
+     * This can be achieved by passing these flags to this method:
+     *
+     * ```php
+     * $normalizer = (new \CuyZ\Valinor\MapperBuilder())
+     *     ->normalizer(\CuyZ\Valinor\Normalizer\Format::json())
+     *     ->withOptions(\JSON_PRESERVE_ZERO_FRACTION);
+     *
+     * $lowerManhattanAsJson = $normalizer->normalize(
+     *     new \My\App\Coordinates(
+     *         longitude: 40.7128,
+     *         latitude: -74.0000
+     *     )
+     * );
+     *
+     * // `$lowerManhattanAsJson` is a valid JSON string representing the data:
+     * // {"longitude":40.7128,"latitude":-74.0000}
+     * ```
+     */
+    public function withOptions(int $options): self
+    {
+        return new self($this->transformer, $options);
+    }
+
+    public function normalize(mixed $value): string
+    {
+        $value = $this->transformer->transform($value);
+
+        /** @var resource $resource */
+        $resource = fopen('php://memory', 'w');
+
+        (new JsonFormatter($resource, $this->jsonEncodingOptions))->format($value);
+
+        rewind($resource);
+
+        /** @var string */
+        $json = stream_get_contents($resource);
+
+        fclose($resource);
+
+        return $json;
+    }
+
+    /**
+     * Returns a new normalizer that will write the JSON to the given resource
+     * instead of returning a string.
+     *
+     * A benefit of streaming the data to a PHP resource is that it may be more
+     * memory-efficient when using generators — for instance when querying a
+     * database:
+     *
+     * ```php
+     * // In this example, we assume that the result of the query below is a
+     * // generator, every entry will be yielded one by one, instead of
+     * // everything being loaded in memory at once.
+     * $users = $database->execute('SELECT * FROM users');
+     *
+     * $file = fopen('path/to/some_file.json', 'w');
+     *
+     * $normalizer = (new \CuyZ\Valinor\MapperBuilder())
+     *     ->normalizer(\CuyZ\Valinor\Normalizer\Format::json())
+     *     ->streamTo($file);
+     *
+     * // Even if there are thousands of users, memory usage will be kept low
+     * // when writing JSON into the file.
+     * $normalizer->normalize($users);
+     * ```
+     *
+     * @param resource $resource
+     */
+    public function streamTo(mixed $resource): StreamNormalizer
+    {
+        // This check is there to help people that do not use static analyzers.
+        // @phpstan-ignore-next-line
+        if (! is_resource($resource)) {
+            throw new RuntimeException('Expected a valid resource, got ' . get_debug_type($resource));
+        }
+
+        return new StreamNormalizer($this->transformer, new JsonFormatter($resource, $this->jsonEncodingOptions));
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/StreamNormalizer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/StreamNormalizer.php
new file mode 100644 (file)
index 0000000..a5a683b
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Normalizer;
+
+use CuyZ\Valinor\Normalizer\Formatter\StreamFormatter;
+use CuyZ\Valinor\Normalizer\Transformer\RecursiveTransformer;
+
+/**
+ * @api
+ *
+ * @implements Normalizer<resource>
+ */
+final class StreamNormalizer implements Normalizer
+{
+    public function __construct(
+        private RecursiveTransformer $transformer,
+        private StreamFormatter $formatter,
+    ) {}
+
+    public function normalize(mixed $value): mixed
+    {
+        $value = $this->transformer->transform($value);
+
+        $this->formatter->format($value);
+
+        return $this->formatter->resource();
+    }
+}
index 3c6c2d67ebf5b50ab1d3508253f1527c7e0f1b6a..8f86c89f0399397c70150057faedab3d4413dfe2 100644 (file)
@@ -4,8 +4,8 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Normalizer\Transformer;
 
-use CuyZ\Valinor\Definition\FunctionDefinition;
-use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
+use CuyZ\Valinor\Definition\AttributeDefinition;
+use CuyZ\Valinor\Definition\MethodDefinition;
 use CuyZ\Valinor\Normalizer\Exception\KeyTransformerHasTooManyParameters;
 use CuyZ\Valinor\Normalizer\Exception\KeyTransformerParameterInvalidType;
 use CuyZ\Valinor\Type\StringType;
@@ -16,53 +16,48 @@ final class KeyTransformersHandler
     /** @var array<string, true> */
     private array $keyTransformerCheck = [];
 
-    public function __construct(
-        private FunctionDefinitionRepository $functionDefinitionRepository,
-    ) {}
-
     /**
-     * @param list<object> $attributes
+     * @param list<AttributeDefinition> $attributes
      */
     public function transformKey(string|int $key, array $attributes): string|int
     {
         foreach ($attributes as $attribute) {
-            if (! method_exists($attribute, 'normalizeKey')) {
+            if (! $attribute->class->methods->has('normalizeKey')) {
                 continue;
             }
 
-            // PHP8.1 First-class callable syntax
-            $function = $this->functionDefinitionRepository->for([$attribute, 'normalizeKey']);
+            $method = $attribute->class->methods->get('normalizeKey');
 
-            $this->checkKeyTransformer($function);
+            $this->checkKeyTransformer($method);
 
-            if ($function->parameters()->count() === 0 || $function->parameters()->at(0)->type()->accepts($key)) {
-                $key = $attribute->normalizeKey($key);
+            if ($method->parameters->count() === 0 || $method->parameters->at(0)->type->accepts($key)) {
+                $key = $attribute->instantiate()->normalizeKey($key); // @phpstan-ignore-line / We know the method exists
             }
         }
 
         return $key;
     }
 
-    private function checkKeyTransformer(FunctionDefinition $function): void
+    private function checkKeyTransformer(MethodDefinition $method): void
     {
-        if (isset($this->keyTransformerCheck[$function->signature()])) {
+        if (isset($this->keyTransformerCheck[$method->signature])) {
             return;
         }
 
         // @infection-ignore-all
-        $this->keyTransformerCheck[$function->signature()] = true;
+        $this->keyTransformerCheck[$method->signature] = true;
 
-        $parameters = $function->parameters();
+        $parameters = $method->parameters;
 
         if ($parameters->count() > 1) {
-            throw new KeyTransformerHasTooManyParameters($function);
+            throw new KeyTransformerHasTooManyParameters($method);
         }
 
         if ($parameters->count() > 0) {
-            $type = $parameters->at(0)->type();
+            $type = $parameters->at(0)->type;
 
             if (! $type instanceof StringType) {
-                throw new KeyTransformerParameterInvalidType($function);
+                throw new KeyTransformerParameterInvalidType($method);
             }
         }
     }
index 917cb3726b6c4c3257315a8d4849e6130f6e5b9b..2b8763df5726d146a4063c7cf70671b84a3a6ac4 100644 (file)
@@ -4,23 +4,26 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Normalizer\Transformer;
 
+use ArrayObject;
 use BackedEnum;
 use Closure;
+use CuyZ\Valinor\Definition\AttributeDefinition;
 use CuyZ\Valinor\Definition\Attributes;
 use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
+use CuyZ\Valinor\Normalizer\AsTransformer;
 use CuyZ\Valinor\Normalizer\Exception\CircularReferenceFoundDuringNormalization;
 use CuyZ\Valinor\Normalizer\Exception\TypeUnhandledByNormalizer;
 use CuyZ\Valinor\Type\Types\NativeClassType;
 use DateTimeInterface;
+use DateTimeZone;
 use Generator;
-use ReflectionClass;
 use stdClass;
 use UnitEnum;
 use WeakMap;
 
 use function array_map;
-use function array_reverse;
 use function get_object_vars;
+use function is_a;
 use function is_array;
 use function is_iterable;
 
@@ -47,7 +50,7 @@ final class RecursiveTransformer
 
     /**
      * @param WeakMap<object, true> $references
-     * @param list<object> $attributes
+     * @param list<AttributeDefinition> $attributes
      * @return iterable<mixed>|scalar|null
      */
     private function doTransform(mixed $value, WeakMap $references, array $attributes = []): mixed
@@ -57,20 +60,23 @@ final class RecursiveTransformer
                 throw new CircularReferenceFoundDuringNormalization($value);
             }
 
+            $references = clone $references;
+
             // @infection-ignore-all
             $references[$value] = true;
         }
 
-        if ($this->transformers === [] && $this->transformerAttributes === []) {
-            return $this->defaultTransformer($value, $references);
-        }
-
-        if ($this->transformerAttributes !== [] && is_object($value)) {
-            $classAttributes = $this->classDefinitionRepository->for(NativeClassType::for($value::class))->attributes();
+        if (is_object($value)) {
+            $classAttributes = $this->classDefinitionRepository->for(new NativeClassType($value::class))->attributes;
+            $classAttributes = $this->filterAttributes($classAttributes);
 
             $attributes = [...$attributes, ...$classAttributes];
         }
 
+        if ($this->transformers === [] && $attributes === []) {
+            return $this->defaultTransformer($value, $references);
+        }
+
         return $this->valueTransformers->transform(
             $value,
             $attributes,
@@ -102,7 +108,11 @@ final class RecursiveTransformer
                 return $value->format('Y-m-d\\TH:i:s.uP'); // RFC 3339
             }
 
-            if ($value::class === stdClass::class) {
+            if ($value instanceof DateTimeZone) {
+                return $value->getName();
+            }
+
+            if ($value::class === stdClass::class || $value instanceof ArrayObject) {
                 return array_map(
                     fn (mixed $value) => $this->doTransform($value, $references),
                     (array)$value
@@ -111,37 +121,12 @@ final class RecursiveTransformer
 
             $values = (fn () => get_object_vars($this))->call($value);
 
-            // @infection-ignore-all
-            if (PHP_VERSION_ID < 8_01_00) {
-                // In PHP 8.1, behavior changed for `get_object_vars` function:
-                // the sorting order was taking children properties first, now
-                // it takes parents properties first. This code is a temporary
-                // workaround to keep the same behavior in PHP 8.0 and later
-                // versions.
-                $sorted = [];
-
-                $parents = array_reverse(class_parents($value));
-                $parents[] = $value::class;
-
-                foreach ($parents as $parent) {
-                    foreach ((new ReflectionClass($parent))->getProperties() as $property) {
-                        if (! isset($values[$property->name])) {
-                            continue;
-                        }
-
-                        $sorted[$property->name] = $values[$property->name];
-                    }
-                }
-
-                $values = $sorted;
-            }
-
             $transformed = [];
 
-            $class = $this->classDefinitionRepository->for(NativeClassType::for($value::class));
+            $class = $this->classDefinitionRepository->for(new NativeClassType($value::class));
 
             foreach ($values as $key => $subValue) {
-                $attributes = $this->filterAttributes($class->properties()->get($key)->attributes());
+                $attributes = $this->filterAttributes($class->properties->get($key)->attributes)->toArray();
 
                 $key = $this->keyTransformers->transformKey($key, $attributes);
 
@@ -169,22 +154,20 @@ final class RecursiveTransformer
         throw new TypeUnhandledByNormalizer($value);
     }
 
-    /**
-     * @return list<object>
-     */
-    private function filterAttributes(Attributes $attributes): array
+    private function filterAttributes(Attributes $attributes): Attributes
     {
-        $filteredAttributes = [];
+        return $attributes->filter(function (AttributeDefinition $attribute) {
+            if ($attribute->class->attributes->has(AsTransformer::class)) {
+                return true;
+            }
 
-        foreach ($attributes as $attribute) {
             foreach ($this->transformerAttributes as $transformerAttribute) {
-                if ($attribute instanceof $transformerAttribute) {
-                    $filteredAttributes[] = $attribute;
-                    break;
+                if (is_a($attribute->class->type->className(), $transformerAttribute, true)) {
+                    return true;
                 }
             }
-        }
 
-        return $filteredAttributes;
+            return false;
+        });
     }
 }
index ad5b629f46469e34c0b69575edf014d512e2f442..d5cdf90ad392c9a2431c7fb1d184cb277143d1eb 100644 (file)
@@ -4,7 +4,9 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Normalizer\Transformer;
 
+use CuyZ\Valinor\Definition\AttributeDefinition;
 use CuyZ\Valinor\Definition\FunctionDefinition;
+use CuyZ\Valinor\Definition\MethodDefinition;
 use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
 use CuyZ\Valinor\Normalizer\Exception\TransformerHasInvalidCallableParameter;
 use CuyZ\Valinor\Normalizer\Exception\TransformerHasNoParameter;
@@ -13,7 +15,6 @@ use CuyZ\Valinor\Type\Types\CallableType;
 
 use function array_shift;
 use function call_user_func;
-use function method_exists;
 
 /** @internal */
 final class ValueTransformersHandler
@@ -26,7 +27,7 @@ final class ValueTransformersHandler
     ) {}
 
     /**
-     * @param array<object> $attributes
+     * @param array<AttributeDefinition> $attributes
      * @param list<callable> $transformers
      * @return array<mixed>|scalar|null
      */
@@ -39,7 +40,7 @@ final class ValueTransformersHandler
 
     /**
      * @param list<callable> $transformers
-     * @param list<object> $attributes
+     * @param array<AttributeDefinition> $attributes
      */
     private function next(array $transformers, mixed $value, array $attributes, callable $defaultTransformer): callable
     {
@@ -61,7 +62,7 @@ final class ValueTransformersHandler
 
         $this->checkTransformer($function);
 
-        if (! $function->parameters()->at(0)->type()->accepts($value)) {
+        if (! $function->parameters->at(0)->type->accepts($value)) {
             return $this->next($transformers, $value, [], $defaultTransformer);
         }
 
@@ -69,7 +70,7 @@ final class ValueTransformersHandler
     }
 
     /**
-     * @param array<object> $attributes
+     * @param array<AttributeDefinition> $attributes
      */
     private function nextAttribute(mixed $value, array $attributes, callable $next): callable
     {
@@ -79,43 +80,46 @@ final class ValueTransformersHandler
             return $next;
         }
 
-        if (! method_exists($attribute, 'normalize')) {
+        if (! $attribute->class->methods->has('normalize')) {
             return $this->nextAttribute($value, $attributes, $next);
         }
 
-        // PHP8.1 First-class callable syntax
-        $function = $this->functionDefinitionRepository->for([$attribute, 'normalize']);
+        $method = $attribute->class->methods->get('normalize');
 
-        $this->checkTransformer($function);
+        $this->checkTransformer($method);
 
-        if (! $function->parameters()->at(0)->type()->accepts($value)) {
+        if (! $method->parameters->at(0)->type->accepts($value)) {
             return $this->nextAttribute($value, $attributes, $next);
         }
 
-        return fn () => $attribute->normalize($value, fn () => call_user_func($this->nextAttribute($value, $attributes, $next)));
+        // @phpstan-ignore-next-line / We know the method exists
+        return fn () => $attribute->instantiate()->normalize(
+            $value,
+            fn () => call_user_func($this->nextAttribute($value, $attributes, $next))
+        );
     }
 
-    private function checkTransformer(FunctionDefinition $function): void
+    private function checkTransformer(MethodDefinition|FunctionDefinition $method): void
     {
-        if (isset($this->transformerCheck[$function->signature()])) {
+        if (isset($this->transformerCheck[$method->signature])) {
             return;
         }
 
         // @infection-ignore-all
-        $this->transformerCheck[$function->signature()] = true;
+        $this->transformerCheck[$method->signature] = true;
 
-        $parameters = $function->parameters();
+        $parameters = $method->parameters;
 
         if ($parameters->count() === 0) {
-            throw new TransformerHasNoParameter($function);
+            throw new TransformerHasNoParameter($method);
         }
 
         if ($parameters->count() > 2) {
-            throw new TransformerHasTooManyParameters($function);
+            throw new TransformerHasTooManyParameters($method);
         }
 
-        if ($parameters->count() > 1 && ! $parameters->at(1)->type() instanceof CallableType) {
-            throw new TransformerHasInvalidCallableParameter($function, $parameters->at(1)->type());
+        if ($parameters->count() > 1 && ! $parameters->at(1)->type instanceof CallableType) {
+            throw new TransformerHasInvalidCallableParameter($method, $parameters->at(1)->type);
         }
     }
 }
index a659fde28b5492ab435ffa4185c17754fb83d405..8dbd7f98adf3cf594a93c44f906979ece068dfee 100644 (file)
@@ -10,7 +10,7 @@ interface CombiningType extends CompositeType
     public function isMatchedBy(Type $other): bool;
 
     /**
-     * @return Type[]
+     * @return non-empty-list<Type>
      */
-    public function types(): iterable;
+    public function types(): array;
 }
index ce8a491960f1d25ff93ca88264d36df912e121f3..fb8fde99b5516d30b5630c799e7b0a8296422279 100644 (file)
@@ -6,7 +6,12 @@ namespace CuyZ\Valinor\Type;
 interface GenericType extends CompositeType
 {
     /**
-     * @return array<string, Type>
+     * @return class-string
+     */
+    public function className(): string;
+
+    /**
+     * @return array<non-empty-string, Type>
      */
     public function generics(): array;
 }
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/ExtendTagTypeError.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/ExtendTagTypeError.php
deleted file mode 100644 (file)
index 6db374a..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Type\Parser\Exception\Generic;
-
-use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
-use ReflectionClass;
-use RuntimeException;
-
-/** @internal */
-final class ExtendTagTypeError extends RuntimeException implements InvalidType
-{
-    /**
-     * @param ReflectionClass<object> $reflection
-     */
-    public function __construct(ReflectionClass $reflection, InvalidType $previous)
-    {
-        parent::__construct(
-            "The `@extends` tag of the class `$reflection->name` is not valid: {$previous->getMessage()}",
-            1670193574,
-            $previous
-        );
-    }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidExtendTagClassName.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidExtendTagClassName.php
deleted file mode 100644 (file)
index e61268b..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Type\Parser\Exception\Generic;
-
-use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
-use CuyZ\Valinor\Type\Type;
-use ReflectionClass;
-use RuntimeException;
-
-/** @internal */
-final class InvalidExtendTagClassName extends RuntimeException implements InvalidType
-{
-    /**
-     * @param ReflectionClass<object> $reflection
-     */
-    public function __construct(ReflectionClass $reflection, Type $invalidExtendTag)
-    {
-        /** @var ReflectionClass<object> $parentClass */
-        $parentClass = $reflection->getParentClass();
-
-        parent::__construct(
-            "The `@extends` tag of the class `$reflection->name` has invalid class `{$invalidExtendTag->toString()}`, it should be `$parentClass->name`.",
-            1670183564
-        );
-    }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidExtendTagType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/InvalidExtendTagType.php
deleted file mode 100644 (file)
index cb2afe7..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Type\Parser\Exception\Generic;
-
-use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
-use CuyZ\Valinor\Type\Type;
-use ReflectionClass;
-use RuntimeException;
-
-/** @internal */
-final class InvalidExtendTagType extends RuntimeException implements InvalidType
-{
-    /**
-     * @param ReflectionClass<object> $reflection
-     */
-    public function __construct(ReflectionClass $reflection, Type $invalidExtendTag)
-    {
-        /** @var ReflectionClass<object> $parentClass */
-        $parentClass = $reflection->getParentClass();
-
-        parent::__construct(
-            "The `@extends` tag of the class `$reflection->name` has invalid type `{$invalidExtendTag->toString()}`, it should be `{$parentClass->name}`.",
-            1670181134
-        );
-    }
-}
index 2f7039c5aa6372b53935ef50d699c687b83f696a..c775bcadcdaeee2aee046b93040d2738326899d5 100644 (file)
@@ -19,7 +19,7 @@ final class MissingGenerics extends RuntimeException implements InvalidType
     /**
      * @param class-string $className
      * @param Type[] $generics
-     * @param Type[] $templates
+     * @param array<non-empty-string> $templates
      */
     public function __construct(string $className, array $generics, array $templates)
     {
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/SeveralExtendTagsFound.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Generic/SeveralExtendTagsFound.php
deleted file mode 100644 (file)
index b6cf57c..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Type\Parser\Exception\Generic;
-
-use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
-use ReflectionClass;
-use RuntimeException;
-
-/** @internal */
-final class SeveralExtendTagsFound extends RuntimeException implements InvalidType
-{
-    /**
-     * @param ReflectionClass<object> $reflection
-     */
-    public function __construct(ReflectionClass $reflection)
-    {
-        parent::__construct(
-            "Only one `@extends` tag should be set for the class `$reflection->name`.",
-            1670195494
-        );
-    }
-}
index b6bf3bc081318cad595cceaac231c06323ea7e87..9ee03855be80848ad70ee7d3e9c20d3325353c12 100644 (file)
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace CuyZ\Valinor\Type\Parser\Exception\Iterable;
 
 use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
+use CuyZ\Valinor\Type\Type;
 use CuyZ\Valinor\Type\Types\ShapedArrayElement;
 use RuntimeException;
 
@@ -16,10 +17,16 @@ final class ShapedArrayClosingBracketMissing extends RuntimeException implements
     /**
      * @param ShapedArrayElement[] $elements
      */
-    public function __construct(array $elements)
+    public function __construct(array $elements, Type|null|false $unsealedType = null)
     {
         $signature = 'array{' . implode(', ', array_map(fn (ShapedArrayElement $element) => $element->toString(), $elements));
 
+        if ($unsealedType === false) {
+            $signature .= ', ...';
+        } elseif ($unsealedType instanceof Type) {
+            $signature .= ', ...' . $unsealedType->toString();
+        }
+
         parent::__construct(
             "Missing closing curly bracket in shaped array signature `$signature`.",
             1631283658
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayInvalidUnsealedType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayInvalidUnsealedType.php
new file mode 100644 (file)
index 0000000..cef92e2
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Type\Parser\Exception\Iterable;
+
+use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
+use CuyZ\Valinor\Type\Type;
+use CuyZ\Valinor\Type\Types\ShapedArrayElement;
+use RuntimeException;
+
+use function implode;
+
+/** @internal */
+final class ShapedArrayInvalidUnsealedType extends RuntimeException implements InvalidType
+{
+    /**
+     * @param ShapedArrayElement[] $elements
+     */
+    public function __construct(array $elements, Type $unsealedType)
+    {
+        $signature = 'array{';
+        $signature .= implode(', ', array_map(fn (ShapedArrayElement $element) => $element->toString(), $elements));
+        $signature .= ', ...' . $unsealedType->toString();
+        $signature .= '}';
+
+        parent::__construct(
+            "Invalid unsealed type `{$unsealedType->toString()}` in shaped array signature `$signature`, it should be a valid array.",
+            1711618899,
+        );
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayUnexpectedTokenAfterSealedType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayUnexpectedTokenAfterSealedType.php
new file mode 100644 (file)
index 0000000..15be028
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Type\Parser\Exception\Iterable;
+
+use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
+use CuyZ\Valinor\Type\Type;
+use CuyZ\Valinor\Type\Types\ShapedArrayElement;
+use RuntimeException;
+
+use function implode;
+
+/** @internal */
+final class ShapedArrayUnexpectedTokenAfterSealedType extends RuntimeException implements InvalidType
+{
+    /**
+     * @param array<ShapedArrayElement> $elements
+     * @param list<Token> $unexpectedTokens
+     */
+    public function __construct(array $elements, Type $unsealedType, array $unexpectedTokens)
+    {
+        $unexpected = implode('', array_map(fn (Token $token) => $token->symbol(), $unexpectedTokens));
+
+        $signature = 'array{';
+        $signature .= implode(', ', array_map(fn (ShapedArrayElement $element) => $element->toString(), $elements));
+        $signature .= ', ...' . $unsealedType->toString();
+        $signature .= $unexpected;
+
+        parent::__construct(
+            "Unexpected `$unexpected` after sealed type in shaped array signature `$signature`, expected a `}`.",
+            1711618958,
+        );
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayWithoutElementsWithSealedType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Exception/Iterable/ShapedArrayWithoutElementsWithSealedType.php
new file mode 100644 (file)
index 0000000..578f00c
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Type\Parser\Exception\Iterable;
+
+use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
+use CuyZ\Valinor\Type\Type;
+use RuntimeException;
+
+/** @internal */
+final class ShapedArrayWithoutElementsWithSealedType extends RuntimeException implements InvalidType
+{
+    public function __construct(Type $unsealedType)
+    {
+        $signature = "array{...{$unsealedType->toString()}}";
+
+        parent::__construct(
+            "Missing elements in shaped array signature `$signature`.",
+            1711629845,
+        );
+    }
+}
index facde9206e00290943b58443a7aebcea526a9e59..461f18d087bfd0e8593c50e4276be64f5b821dc0 100644 (file)
@@ -6,8 +6,8 @@ namespace CuyZ\Valinor\Type\Parser\Factory;
 
 use CuyZ\Valinor\Type\Parser\CachedParser;
 use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeParserSpecification;
-use CuyZ\Valinor\Type\Parser\Lexer\AdvancedClassLexer;
 use CuyZ\Valinor\Type\Parser\Lexer\NativeLexer;
+use CuyZ\Valinor\Type\Parser\Lexer\SpecificationsLexer;
 use CuyZ\Valinor\Type\Parser\LexingParser;
 use CuyZ\Valinor\Type\Parser\TypeParser;
 
@@ -18,26 +18,24 @@ final class LexingTypeParserFactory implements TypeParserFactory
 
     public function get(TypeParserSpecification ...$specifications): TypeParser
     {
-        if (empty($specifications)) {
-            return $this->nativeParser ??= $this->nativeParser();
+        if ($specifications === []) {
+            return $this->nativeParser ??= new CachedParser($this->buildTypeParser());
         }
 
-        $lexer = new NativeLexer();
-        $lexer = new AdvancedClassLexer($lexer, $this);
-
-        foreach ($specifications as $specification) {
-            $lexer = $specification->transform($lexer);
-        }
-
-        return new LexingParser($lexer);
+        return $this->buildTypeParser(...$specifications);
     }
 
-    private function nativeParser(): TypeParser
+    private function buildTypeParser(TypeParserSpecification ...$specifications): TypeParser
     {
-        $lexer = new NativeLexer();
-        $lexer = new AdvancedClassLexer($lexer, $this);
+        $lexer = new SpecificationsLexer($specifications);
+        $lexer = new NativeLexer($lexer);
+
         $parser = new LexingParser($lexer);
 
-        return new CachedParser($parser);
+        foreach ($specifications as $specification) {
+            $parser = $specification->manipulateParser($parser, $this);
+        }
+
+        return $parser;
     }
 }
index 59e706016a23bfe2f4a12c7757343271a0b28513..bf8e4aaa0b12cf71a030b21acc8ed083f0d71883 100644 (file)
@@ -4,8 +4,12 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Type\Parser\Factory\Specifications;
 
-use CuyZ\Valinor\Type\Parser\Lexer\AliasLexer;
-use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
+use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\ObjectToken;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\TraversingToken;
+use CuyZ\Valinor\Type\Parser\TypeParser;
+use CuyZ\Valinor\Utility\Reflection\PhpParser;
+use CuyZ\Valinor\Utility\Reflection\Reflection;
 use ReflectionClass;
 use ReflectionFunction;
 use Reflector;
@@ -15,11 +19,95 @@ final class AliasSpecification implements TypeParserSpecification
 {
     public function __construct(
         /** @var ReflectionClass<object>|ReflectionFunction */
-        private Reflector $reflection
+        private Reflector $reflection,
     ) {}
 
-    public function transform(TypeLexer $lexer): TypeLexer
+    public function manipulateToken(TraversingToken $token): TraversingToken
     {
-        return new AliasLexer($lexer, $this->reflection);
+        $symbol = $token->symbol();
+
+        // Matches the case where a class extends a class with the same name but
+        // in a different namespace.
+        if ($symbol === $this->reflection->getShortName() && Reflection::classOrInterfaceExists($symbol)) {
+            return $token;
+        }
+
+        $alias = $this->resolveAlias($symbol);
+
+        if (strtolower($alias) !== strtolower($symbol)) {
+            /** @var class-string $alias */
+            return new ObjectToken($alias);
+        }
+
+        $namespaced = $this->resolveNamespaced($symbol);
+
+        if ($namespaced !== $symbol) {
+            /** @var class-string $namespaced */
+            return new ObjectToken($namespaced);
+        }
+
+        return $token;
+    }
+
+    public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser
+    {
+        return $parser;
+    }
+
+    private function resolveAlias(string $symbol): string
+    {
+        $alias = $symbol;
+
+        $namespaceParts = explode('\\', $symbol);
+        $lastPart = array_shift($namespaceParts);
+
+        if ($lastPart) {
+            $alias = strtolower($lastPart);
+        }
+
+        $aliases = PhpParser::parseUseStatements($this->reflection);
+
+        if (! isset($aliases[$alias])) {
+            return $symbol;
+        }
+
+        if ($aliases[$alias] === $symbol) {
+            return $symbol;
+        }
+
+        $full = $aliases[$alias];
+
+        if (! empty($namespaceParts)) {
+            $full .= '\\' . implode('\\', $namespaceParts);
+        }
+
+        return $full;
+    }
+
+    private function resolveNamespaced(string $symbol): string
+    {
+        $reflection = $this->reflection;
+
+        if ($reflection instanceof ReflectionFunction) {
+            $classReflection = $reflection->getClosureScopeClass();
+
+            if ($classReflection && $classReflection->getFileName() === $reflection->getFileName()) {
+                $reflection = $classReflection;
+            }
+        }
+
+        $namespace = $reflection->getNamespaceName();
+
+        if (! $namespace) {
+            return $symbol;
+        }
+
+        $full = $namespace . '\\' . $symbol;
+
+        if (Reflection::classOrInterfaceExists($full)) {
+            return $full;
+        }
+
+        return $symbol;
     }
 }
index dcb3c628e1f80592e3e27e1650173d8104f55e5f..272ea946b25d0d4c45b5af2e8cd472dbb6b74dc0 100644 (file)
@@ -4,19 +4,30 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Type\Parser\Factory\Specifications;
 
-use CuyZ\Valinor\Type\Parser\Lexer\ClassContextLexer;
-use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
+use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\ObjectToken;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\TraversingToken;
+use CuyZ\Valinor\Type\Parser\TypeParser;
 
 /** @internal */
 final class ClassContextSpecification implements TypeParserSpecification
 {
     public function __construct(
         /** @var class-string */
-        private string $className
+        private string $className,
     ) {}
 
-    public function transform(TypeLexer $lexer): TypeLexer
+    public function manipulateToken(TraversingToken $token): TraversingToken
     {
-        return new ClassContextLexer($lexer, $this->className);
+        if ($token->symbol() === 'self' || $token->symbol() === 'static') {
+            return new ObjectToken($this->className);
+        }
+
+        return $token;
+    }
+
+    public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser
+    {
+        return $parser;
     }
 }
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Factory/Specifications/GenericCheckerSpecification.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Factory/Specifications/GenericCheckerSpecification.php
new file mode 100644 (file)
index 0000000..a33eaa5
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Type\Parser\Factory\Specifications;
+
+use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
+use CuyZ\Valinor\Type\Parser\GenericCheckerParser;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\TraversingToken;
+use CuyZ\Valinor\Type\Parser\TypeParser;
+
+/** @internal */
+final class GenericCheckerSpecification implements TypeParserSpecification
+{
+    public function manipulateToken(TraversingToken $token): TraversingToken
+    {
+        return $token;
+    }
+
+    public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser
+    {
+        return new GenericCheckerParser($parser, $typeParserFactory);
+    }
+}
index 140603610eabc893a3093ab2559ca8f90c219b96..1c07a9b714aae9da75c3aa2e7945f42733c2a40a 100644 (file)
@@ -4,8 +4,10 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Type\Parser\Factory\Specifications;
 
-use CuyZ\Valinor\Type\Parser\Lexer\TypeAliasLexer;
-use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
+use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\TraversingToken;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\TypeToken;
+use CuyZ\Valinor\Type\Parser\TypeParser;
 use CuyZ\Valinor\Type\Type;
 
 /** @internal */
@@ -13,11 +15,22 @@ final class TypeAliasAssignerSpecification implements TypeParserSpecification
 {
     public function __construct(
         /** @var array<string, Type> */
-        private array $aliases
+        private array $aliases,
     ) {}
 
-    public function transform(TypeLexer $lexer): TypeLexer
+    public function manipulateToken(TraversingToken $token): TraversingToken
     {
-        return new TypeAliasLexer($lexer, $this->aliases);
+        $symbol = $token->symbol();
+
+        if (isset($this->aliases[$symbol])) {
+            return new TypeToken($this->aliases[$symbol], $symbol);
+        }
+
+        return $token;
+    }
+
+    public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser
+    {
+        return $parser;
     }
 }
index 6c26b887bf6c56ce641efca7b6586c6c213be66e..a48aba87b8636beab7c6ae7aaf25cded4a10c89a 100644 (file)
@@ -4,10 +4,14 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Type\Parser\Factory\Specifications;
 
-use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
+use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\TraversingToken;
+use CuyZ\Valinor\Type\Parser\TypeParser;
 
 /** @internal */
 interface TypeParserSpecification
 {
-    public function transform(TypeLexer $lexer): TypeLexer;
+    public function manipulateToken(TraversingToken $token): TraversingToken;
+
+    public function manipulateParser(TypeParser $parser, TypeParserFactory $typeParserFactory): TypeParser;
 }
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/GenericCheckerParser.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/GenericCheckerParser.php
new file mode 100644 (file)
index 0000000..6e47b9b
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Type\Parser;
+
+use CuyZ\Valinor\Type\CompositeTraversableType;
+use CuyZ\Valinor\Type\GenericType;
+use CuyZ\Valinor\Type\IntegerType;
+use CuyZ\Valinor\Type\Parser\Exception\Generic\AssignedGenericNotFound;
+use CuyZ\Valinor\Type\Parser\Exception\Generic\InvalidAssignedGeneric;
+use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
+use CuyZ\Valinor\Type\Parser\Exception\Template\InvalidClassTemplate;
+use CuyZ\Valinor\Type\Parser\Factory\Specifications\AliasSpecification;
+use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
+use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
+use CuyZ\Valinor\Type\StringType;
+use CuyZ\Valinor\Type\Type;
+use CuyZ\Valinor\Type\Types\ArrayKeyType;
+use CuyZ\Valinor\Utility\Reflection\DocParser;
+use CuyZ\Valinor\Utility\Reflection\Reflection;
+
+use function array_keys;
+
+/** @internal */
+final class GenericCheckerParser implements TypeParser
+{
+    public function __construct(
+        private TypeParser $delegate,
+        private TypeParserFactory $typeParserFactory,
+    ) {}
+
+    public function parse(string $raw): Type
+    {
+        $type = $this->delegate->parse($raw);
+
+        $this->checkGenerics($type);
+
+        return $type;
+    }
+
+    private function checkGenerics(Type $type): void
+    {
+        if ($type instanceof CompositeTraversableType) {
+            foreach ($type->traverse() as $subType) {
+                $this->checkGenerics($subType);
+            }
+        }
+
+        if (! $type instanceof GenericType) {
+            return;
+        }
+
+        $reflection = Reflection::class($type->className());
+        $templates = DocParser::classTemplates($reflection);
+
+        if ($templates === []) {
+            return;
+        }
+
+        $generics = $type->generics();
+
+        $parser = $this->typeParserFactory->get(
+            new ClassContextSpecification($reflection->name),
+            new AliasSpecification($reflection),
+        );
+
+        foreach ($templates as $templateName => $template) {
+            if (! isset($generics[$templateName])) {
+                throw new AssignedGenericNotFound($reflection->name, ...array_keys($templates));
+            }
+
+            array_shift($templates);
+
+            if ($template === null) {
+                // If no template is provided, it defaults to mixed type.
+                continue;
+            }
+
+            $genericType = $generics[$templateName];
+
+            try {
+                $templateType = $parser->parse($template);
+            } catch (InvalidType $invalidType) {
+                throw new InvalidClassTemplate($reflection->name, $templateName, $invalidType);
+            }
+
+            if ($templateType instanceof ArrayKeyType && $genericType instanceof StringType) {
+                $genericType = ArrayKeyType::string();
+            }
+
+            if ($templateType instanceof ArrayKeyType && $genericType instanceof IntegerType) {
+                $genericType = ArrayKeyType::integer();
+            }
+
+            if (! $genericType->matches($templateType)) {
+                throw new InvalidAssignedGeneric($genericType, $templateType, $templateName, $type->className());
+            }
+        }
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/AdvancedClassLexer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/AdvancedClassLexer.php
deleted file mode 100644 (file)
index 6601ef8..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Type\Parser\Lexer;
-
-use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
-use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassNameToken;
-use CuyZ\Valinor\Type\Parser\Lexer\Token\AdvancedClassNameToken;
-use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
-
-/** @internal */
-final class AdvancedClassLexer implements TypeLexer
-{
-    public function __construct(
-        private TypeLexer $delegate,
-        private TypeParserFactory $typeParserFactory,
-    ) {}
-
-    public function tokenize(string $symbol): Token
-    {
-        $token = $this->delegate->tokenize($symbol);
-
-        if ($token instanceof ClassNameToken) {
-            return new AdvancedClassNameToken($token, $this->typeParserFactory);
-        }
-
-        return $token;
-    }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/AliasLexer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/AliasLexer.php
deleted file mode 100644 (file)
index 746d0a7..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Type\Parser\Lexer;
-
-use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
-use CuyZ\Valinor\Utility\Reflection\PhpParser;
-use CuyZ\Valinor\Utility\Reflection\Reflection;
-use ReflectionClass;
-use ReflectionFunction;
-use Reflector;
-
-use function strtolower;
-
-/** @internal */
-final class AliasLexer implements TypeLexer
-{
-    public function __construct(
-        private TypeLexer $delegate,
-        /** @var ReflectionClass<object>|ReflectionFunction */
-        private Reflector $reflection
-    ) {}
-
-    public function tokenize(string $symbol): Token
-    {
-        $symbol = $this->resolve($symbol);
-
-        return $this->delegate->tokenize($symbol);
-    }
-
-    private function resolve(string $symbol): string
-    {
-        // Matches the case where a class extends a class with the same name but
-        // in a different namespace.
-        if ($symbol === $this->reflection->getShortName() && Reflection::classOrInterfaceExists($symbol)) {
-            return $symbol;
-        }
-
-        $alias = $this->resolveAlias($symbol);
-
-        if (strtolower($alias) !== strtolower($symbol)) {
-            return $alias;
-        }
-
-        $namespaced = $this->resolveNamespaced($symbol);
-
-        if ($namespaced !== $symbol) {
-            return $namespaced;
-        }
-
-        return $symbol;
-    }
-
-    private function resolveAlias(string $symbol): string
-    {
-        $alias = $symbol;
-
-        $namespaceParts = explode('\\', $symbol);
-        $lastPart = array_shift($namespaceParts);
-
-        if ($lastPart) {
-            $alias = strtolower($lastPart);
-        }
-
-        $aliases = PhpParser::parseUseStatements($this->reflection);
-
-        if (! isset($aliases[$alias])) {
-            return $symbol;
-        }
-
-        $full = $aliases[$alias];
-
-        if (! empty($namespaceParts)) {
-            $full .= '\\' . implode('\\', $namespaceParts);
-        }
-
-        return $full;
-    }
-
-    private function resolveNamespaced(string $symbol): string
-    {
-        $reflection = $this->reflection;
-
-        if ($reflection instanceof ReflectionFunction) {
-            $classReflection = $reflection->getClosureScopeClass();
-
-            if ($classReflection && $classReflection->getFileName() === $reflection->getFileName()) {
-                $reflection = $classReflection;
-            }
-        }
-
-        $namespace = $reflection->getNamespaceName();
-
-        if (! $namespace) {
-            return $symbol;
-        }
-
-        $full = $namespace . '\\' . $symbol;
-
-        if (Reflection::classOrInterfaceExists($full)) {
-            return $full;
-        }
-
-        return $symbol;
-    }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/ClassContextLexer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/ClassContextLexer.php
deleted file mode 100644 (file)
index b3af6bf..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Type\Parser\Lexer;
-
-use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
-use CuyZ\Valinor\Utility\Reflection\Reflection;
-use ReflectionClass;
-
-/** @internal */
-final class ClassContextLexer implements TypeLexer
-{
-    private TypeLexer $delegate;
-
-    /** @var ReflectionClass<object> */
-    private ReflectionClass $reflection;
-
-    /**
-     * @param class-string $className
-     */
-    public function __construct(TypeLexer $delegate, string $className)
-    {
-        $this->delegate = $delegate;
-        $this->reflection = Reflection::class($className);
-    }
-
-    public function tokenize(string $symbol): Token
-    {
-        $symbol = $this->resolve($symbol);
-
-        return $this->delegate->tokenize($symbol);
-    }
-
-    private function resolve(string $symbol): string
-    {
-        if ($symbol === 'self' || $symbol === 'static') {
-            return $this->reflection->name;
-        }
-
-        return $symbol;
-    }
-}
index 68b1ad040fcdd5c68baaaa311f1b14036f150564..5040df131b42f20ce8f668456da0bb46ffcc91dd 100644 (file)
@@ -6,7 +6,6 @@ namespace CuyZ\Valinor\Type\Parser\Lexer;
 
 use CuyZ\Valinor\Type\Parser\Lexer\Token\ArrayToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\CallableToken;
-use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassNameToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\ClassStringToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\ClosingBracketToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\ClosingCurlyBracketToken;
@@ -14,7 +13,6 @@ use CuyZ\Valinor\Type\Parser\Lexer\Token\ClosingSquareBracketToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\ColonToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\CommaToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\DoubleColonToken;
-use CuyZ\Valinor\Type\Parser\Lexer\Token\EnumNameToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\FloatValueToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\IntegerToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\IntegerValueToken;
@@ -28,10 +26,8 @@ use CuyZ\Valinor\Type\Parser\Lexer\Token\OpeningCurlyBracketToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\OpeningSquareBracketToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\QuoteToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\TripleDotsToken;
 use CuyZ\Valinor\Type\Parser\Lexer\Token\UnionToken;
-use CuyZ\Valinor\Type\Parser\Lexer\Token\UnknownSymbolToken;
-use CuyZ\Valinor\Utility\Reflection\Reflection;
-use UnitEnum;
 
 use function filter_var;
 use function is_numeric;
@@ -40,6 +36,8 @@ use function strtolower;
 /** @internal */
 final class NativeLexer implements TypeLexer
 {
+    public function __construct(private TypeLexer $delegate) {}
+
     public function tokenize(string $symbol): Token
     {
         if (NativeToken::accepts($symbol)) {
@@ -59,6 +57,7 @@ final class NativeLexer implements TypeLexer
             ':' => ColonToken::get(),
             '?' => NullableToken::get(),
             ',' => CommaToken::get(),
+            '...' => TripleDotsToken::get(),
             '"', "'" => new QuoteToken($symbol),
             'int', 'integer' => IntegerToken::get(),
             'array' => ArrayToken::array(),
@@ -83,17 +82,6 @@ final class NativeLexer implements TypeLexer
             return new FloatValueToken((float)$symbol);
         }
 
-        /** @infection-ignore-all */
-        if (PHP_VERSION_ID >= 8_01_00 && enum_exists($symbol)) {
-            /** @var class-string<UnitEnum> $symbol */
-            return new EnumNameToken($symbol);
-        }
-
-        if (Reflection::classOrInterfaceExists($symbol)) {
-            /** @var class-string $symbol */
-            return new ClassNameToken($symbol);
-        }
-
-        return new UnknownSymbolToken($symbol);
+        return $this->delegate->tokenize($symbol);
     }
 }
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/SpecificationsLexer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/SpecificationsLexer.php
new file mode 100644 (file)
index 0000000..521863a
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Type\Parser\Lexer;
+
+use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeParserSpecification;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
+use CuyZ\Valinor\Type\Parser\Lexer\Token\VacantToken;
+
+/** @internal */
+final class SpecificationsLexer implements TypeLexer
+{
+    public function __construct(
+        /** @var array<TypeParserSpecification> */
+        private array $specifications,
+    ) {}
+
+    public function tokenize(string $symbol): Token
+    {
+        return (new VacantToken($symbol, $this->specifications));
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/AdvancedClassNameToken.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/AdvancedClassNameToken.php
deleted file mode 100644 (file)
index adb3dc2..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Type\Parser\Lexer\Token;
-
-use CuyZ\Valinor\Type\IntegerType;
-use CuyZ\Valinor\Type\Parser\Exception\Generic\AssignedGenericNotFound;
-use CuyZ\Valinor\Type\Parser\Exception\Generic\CannotAssignGeneric;
-use CuyZ\Valinor\Type\Parser\Exception\Generic\GenericClosingBracketMissing;
-use CuyZ\Valinor\Type\Parser\Exception\Generic\GenericCommaMissing;
-use CuyZ\Valinor\Type\Parser\Exception\Generic\InvalidAssignedGeneric;
-use CuyZ\Valinor\Type\Parser\Exception\Generic\InvalidExtendTagClassName;
-use CuyZ\Valinor\Type\Parser\Exception\Generic\InvalidExtendTagType;
-use CuyZ\Valinor\Type\Parser\Exception\Generic\MissingGenerics;
-use CuyZ\Valinor\Type\Parser\Exception\Generic\ExtendTagTypeError;
-use CuyZ\Valinor\Type\Parser\Exception\Generic\SeveralExtendTagsFound;
-use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
-use CuyZ\Valinor\Type\Parser\Exception\Template\InvalidClassTemplate;
-use CuyZ\Valinor\Type\Parser\Factory\Specifications\AliasSpecification;
-use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
-use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
-use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeParserSpecification;
-use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
-use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
-use CuyZ\Valinor\Type\Parser\TypeParser;
-use CuyZ\Valinor\Type\StringType;
-use CuyZ\Valinor\Type\Type;
-use CuyZ\Valinor\Type\Types\ArrayKeyType;
-use CuyZ\Valinor\Type\ClassType;
-use CuyZ\Valinor\Type\Types\MixedType;
-use CuyZ\Valinor\Type\Types\NativeClassType;
-use CuyZ\Valinor\Utility\Reflection\DocParser;
-use CuyZ\Valinor\Utility\Reflection\Reflection;
-use ReflectionClass;
-
-use function array_keys;
-use function array_shift;
-use function array_slice;
-use function count;
-
-/** @internal */
-final class AdvancedClassNameToken implements TraversingToken
-{
-    public function __construct(
-        private ClassNameToken $delegate,
-        private TypeParserFactory $typeParserFactory,
-    ) {}
-
-    public function traverse(TokenStream $stream): Type
-    {
-        $type = $this->delegate->traverse($stream);
-
-        if (! $type instanceof ClassType) {
-            return $type;
-        }
-
-        $className = $type->className();
-        $reflection = Reflection::class($className);
-        $parentReflection = $reflection->getParentClass();
-
-        $specifications = [
-            new ClassContextSpecification($className),
-            new AliasSpecification($reflection),
-        ];
-
-        $templates = $this->templatesTypes($reflection, ...$specifications);
-
-        $generics = $this->generics($stream, $className, $templates);
-        $generics = $this->assignGenerics($className, $templates, $generics);
-
-        if ($parentReflection) {
-            $parserWithGenerics = $this->typeParserFactory->get(new TypeAliasAssignerSpecification($generics), ...$specifications);
-
-            $parentType = $this->parentType($reflection, $parentReflection, $parserWithGenerics);
-        }
-
-        return new NativeClassType($className, $generics, $parentType ?? null);
-    }
-
-    public function symbol(): string
-    {
-        return $this->delegate->symbol();
-    }
-
-    /**
-     * @param ReflectionClass<object> $reflection
-     * @return array<string, Type>
-     */
-    private function templatesTypes(ReflectionClass $reflection, TypeParserSpecification ...$specifications): array
-    {
-        $templates = DocParser::classTemplates($reflection);
-
-        if ($templates === []) {
-            return [];
-        }
-
-        $types = [];
-
-        foreach ($templates as $templateName => $type) {
-            try {
-                if ($type === '') {
-                    $types[$templateName] = MixedType::get();
-                } else {
-                    /** @infection-ignore-all */
-                    $parser ??= $this->typeParserFactory->get(...$specifications);
-
-                    $types[$templateName] = $parser->parse($type);
-                }
-            } catch (InvalidType $invalidType) {
-                throw new InvalidClassTemplate($reflection->name, $templateName, $invalidType);
-            }
-        }
-
-        return $types;
-    }
-
-    /**
-     * @param array<string, Type> $templates
-     * @param class-string $className
-     * @return Type[]
-     */
-    private function generics(TokenStream $stream, string $className, array $templates): array
-    {
-        if ($stream->done() || ! $stream->next() instanceof OpeningBracketToken) {
-            return [];
-        }
-
-        $generics = [];
-
-        $stream->forward();
-
-        while (true) {
-            if ($stream->done()) {
-                throw new MissingGenerics($className, $generics, $templates);
-            }
-
-            $generics[] = $stream->read();
-
-            if ($stream->done()) {
-                throw new GenericClosingBracketMissing($className, $generics);
-            }
-
-            $next = $stream->forward();
-
-            if ($next instanceof ClosingBracketToken) {
-                break;
-            }
-
-            if (! $next instanceof CommaToken) {
-                throw new GenericCommaMissing($className, $generics);
-            }
-        }
-
-        return $generics;
-    }
-
-    /**
-     * @param class-string $className
-     * @param array<string, Type> $templates
-     * @param Type[] $generics
-     * @return array<string, Type>
-     */
-    private function assignGenerics(string $className, array $templates, array $generics): array
-    {
-        $assignedGenerics = [];
-
-        foreach ($templates as $name => $template) {
-            $generic = array_shift($generics);
-
-            if ($generic === null) {
-                $remainingTemplates = array_keys(array_slice($templates, count($assignedGenerics)));
-
-                throw new AssignedGenericNotFound($className, ...$remainingTemplates);
-            }
-
-            if ($template instanceof ArrayKeyType && $generic instanceof StringType) {
-                $generic = ArrayKeyType::string();
-            }
-
-            if ($template instanceof ArrayKeyType && $generic instanceof IntegerType) {
-                $generic = ArrayKeyType::integer();
-            }
-
-            if (! $generic->matches($template)) {
-                throw new InvalidAssignedGeneric($generic, $template, $name, $className);
-            }
-
-            $assignedGenerics[$name] = $generic;
-        }
-
-        if (! empty($generics)) {
-            throw new CannotAssignGeneric($className, ...$generics);
-        }
-
-        return $assignedGenerics;
-    }
-
-    /**
-     * @param ReflectionClass<object> $reflection
-     * @param ReflectionClass<object> $parentReflection
-     */
-    private function parentType(ReflectionClass $reflection, ReflectionClass $parentReflection, TypeParser $typeParser): NativeClassType
-    {
-        $extendedClass = DocParser::classExtendsTypes($reflection);
-
-        if (count($extendedClass) > 1) {
-            throw new SeveralExtendTagsFound($reflection);
-        } elseif (count($extendedClass) === 0) {
-            $extendedClass = $parentReflection->name;
-        } else {
-            $extendedClass = $extendedClass[0];
-        }
-
-        try {
-            $parentType = $typeParser->parse($extendedClass);
-        } catch (InvalidType $exception) {
-            throw new ExtendTagTypeError($reflection, $exception);
-        }
-
-        if (! $parentType instanceof NativeClassType) {
-            throw new InvalidExtendTagType($reflection, $parentType);
-        }
-
-        if ($parentType->className() !== $parentReflection->name) {
-            throw new InvalidExtendTagClassName($reflection, $parentType);
-        }
-
-        return $parentType;
-    }
-}
index 8d1563f0693dcb2c452a38baec97c87f628f7c66..57bcd85f4ff967d410448507612e120a4dde0712 100644 (file)
@@ -12,6 +12,9 @@ use CuyZ\Valinor\Type\Parser\Exception\Iterable\ShapedArrayColonTokenMissing;
 use CuyZ\Valinor\Type\Parser\Exception\Iterable\ShapedArrayCommaMissing;
 use CuyZ\Valinor\Type\Parser\Exception\Iterable\ShapedArrayElementTypeMissing;
 use CuyZ\Valinor\Type\Parser\Exception\Iterable\ShapedArrayEmptyElements;
+use CuyZ\Valinor\Type\Parser\Exception\Iterable\ShapedArrayInvalidUnsealedType;
+use CuyZ\Valinor\Type\Parser\Exception\Iterable\ShapedArrayUnexpectedTokenAfterSealedType;
+use CuyZ\Valinor\Type\Parser\Exception\Iterable\ShapedArrayWithoutElementsWithSealedType;
 use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
 use CuyZ\Valinor\Type\Type;
 use CuyZ\Valinor\Type\Types\ArrayKeyType;
@@ -99,6 +102,8 @@ final class ArrayToken implements TraversingToken
 
         $elements = [];
         $index = 0;
+        $isUnsealed = false;
+        $unsealedType = null;
 
         while (! $stream->done()) {
             if ($stream->next() instanceof ClosingCurlyBracketToken) {
@@ -121,12 +126,50 @@ final class ArrayToken implements TraversingToken
 
             $optional = false;
 
-            if ($stream->next() instanceof UnknownSymbolToken) {
+            if ($stream->next() instanceof TripleDotsToken) {
+                $isUnsealed = true;
+                $stream->forward();
+            }
+
+            if ($stream->done()) {
+                throw new ShapedArrayClosingBracketMissing($elements, unsealedType: false);
+            }
+
+            if ($stream->next() instanceof VacantToken) {
                 $type = new StringValueType($stream->forward()->symbol());
+            } elseif ($isUnsealed && $stream->next() instanceof ClosingCurlyBracketToken) {
+                $stream->forward();
+                break;
             } else {
                 $type = $stream->read();
             }
 
+            if ($isUnsealed) {
+                $unsealedType = $type;
+
+                if ($elements === []) {
+                    throw new ShapedArrayWithoutElementsWithSealedType($unsealedType);
+                }
+
+                if (! $unsealedType instanceof ArrayType) {
+                    throw new ShapedArrayInvalidUnsealedType($elements, $unsealedType);
+                }
+
+                if ($stream->done()) {
+                    throw new ShapedArrayClosingBracketMissing($elements, $unsealedType);
+                } elseif (! $stream->next() instanceof ClosingCurlyBracketToken) {
+                    $unexpected = [];
+
+                    while (! $stream->done() && ! $stream->next() instanceof ClosingCurlyBracketToken) {
+                        $unexpected[] = $stream->forward();
+                    }
+
+                    throw new ShapedArrayUnexpectedTokenAfterSealedType($elements, $unsealedType, $unexpected);
+                }
+
+                continue;
+            }
+
             if ($stream->done()) {
                 $elements[] = new ShapedArrayElement(new IntegerValueType($index), $type);
 
@@ -178,10 +221,16 @@ final class ArrayToken implements TraversingToken
             }
         }
 
-        if (empty($elements)) {
+        if ($elements === []) {
             throw new ShapedArrayEmptyElements();
         }
 
+        if ($unsealedType) {
+            return ShapedArrayType::unsealed($unsealedType, ...$elements);
+        } elseif ($isUnsealed) {
+            return ShapedArrayType::unsealedWithoutType(...$elements);
+        }
+
         return new ShapedArrayType(...$elements);
     }
 }
index f3be92690a65f3f3a66c9d3cec2d63b968dd82e0..d1a5e0abf61b9311b7b8464443322efda86f8bd1 100644 (file)
@@ -7,17 +7,25 @@ namespace CuyZ\Valinor\Type\Parser\Lexer\Token;
 use CuyZ\Valinor\Type\Parser\Exception\Constant\ClassConstantCaseNotFound;
 use CuyZ\Valinor\Type\Parser\Exception\Constant\MissingClassConstantCase;
 use CuyZ\Valinor\Type\Parser\Exception\Constant\MissingSpecificClassConstantCase;
+use CuyZ\Valinor\Type\Parser\Exception\Generic\CannotAssignGeneric;
+use CuyZ\Valinor\Type\Parser\Exception\Generic\GenericClosingBracketMissing;
+use CuyZ\Valinor\Type\Parser\Exception\Generic\GenericCommaMissing;
+use CuyZ\Valinor\Type\Parser\Exception\Generic\MissingGenerics;
 use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
 use CuyZ\Valinor\Type\Type;
-use CuyZ\Valinor\Type\Types\NativeClassType;
 use CuyZ\Valinor\Type\Types\Factory\ValueTypeFactory;
 use CuyZ\Valinor\Type\Types\InterfaceType;
+use CuyZ\Valinor\Type\Types\NativeClassType;
 use CuyZ\Valinor\Type\Types\UnionType;
+use CuyZ\Valinor\Utility\Reflection\DocParser;
 use CuyZ\Valinor\Utility\Reflection\Reflection;
 use ReflectionClass;
 use ReflectionClassConstant;
 
+use function array_keys;
 use function array_map;
+use function array_shift;
+use function array_values;
 use function count;
 use function explode;
 
@@ -47,7 +55,14 @@ final class ClassNameToken implements TraversingToken
             return new InterfaceType($this->reflection->name);
         }
 
-        return new NativeClassType($this->reflection->name);
+        $reflection = Reflection::class($this->reflection->name);
+
+        $templates = array_keys(DocParser::classTemplates($reflection));
+
+        $generics = $this->generics($stream, $this->reflection->name, $templates);
+        $generics = $this->assignGenerics($this->reflection->name, $templates, $generics);
+
+        return new NativeClassType($this->reflection->name, $generics);
     }
 
     public function symbol(): string
@@ -87,9 +102,74 @@ final class ClassNameToken implements TraversingToken
         $cases = array_map(static fn ($value) => ValueTypeFactory::from($value), $cases);
 
         if (count($cases) > 1) {
-            return new UnionType(...$cases);
+            return new UnionType(...array_values($cases));
         }
 
         return reset($cases);
     }
+
+    /**
+     * @param array<non-empty-string> $templates
+     * @param class-string $className
+     * @return list<Type>
+     */
+    private function generics(TokenStream $stream, string $className, array $templates): array
+    {
+        if ($stream->done() || ! $stream->next() instanceof OpeningBracketToken) {
+            return [];
+        }
+
+        $generics = [];
+
+        $stream->forward();
+
+        while (true) {
+            if ($stream->done()) {
+                throw new MissingGenerics($className, $generics, $templates);
+            }
+
+            $generics[] = $stream->read();
+
+            if ($stream->done()) {
+                throw new GenericClosingBracketMissing($className, $generics);
+            }
+
+            $next = $stream->forward();
+
+            if ($next instanceof ClosingBracketToken) {
+                break;
+            }
+
+            if (! $next instanceof CommaToken) {
+                throw new GenericCommaMissing($className, $generics);
+            }
+        }
+
+        return $generics;
+    }
+
+    /**
+     * @param class-string $className
+     * @param array<non-empty-string> $templates
+     * @param list<Type> $generics
+     * @return array<non-empty-string, Type>
+     */
+    private function assignGenerics(string $className, array $templates, array $generics): array
+    {
+        $assignedGenerics = [];
+
+        foreach ($templates as $template) {
+            $generic = array_shift($generics);
+
+            if ($generic) {
+                $assignedGenerics[$template] = $generic;
+            }
+        }
+
+        if (! empty($generics)) {
+            throw new CannotAssignGeneric($className, ...$generics);
+        }
+
+        return $assignedGenerics;
+    }
 }
index 2f2e69950492faa591c00ad3405debfb297dd1bb..43b05976c31e81b8d853f387337be502afe0d9e1 100644 (file)
@@ -34,7 +34,7 @@ final class IntegerToken implements TraversingToken
             throw new IntegerRangeMissingMinValue();
         }
 
-        if ($stream->next() instanceof UnknownSymbolToken) {
+        if ($stream->next()->symbol() === 'min') {
             $min = new IntegerValueType(PHP_INT_MIN);
             $stream->forward();
         } else {
@@ -53,7 +53,7 @@ final class IntegerToken implements TraversingToken
             throw new IntegerRangeMissingMaxValue($min);
         }
 
-        if ($stream->next() instanceof UnknownSymbolToken) {
+        if ($stream->next()->symbol() === 'max') {
             $max = new IntegerValueType(PHP_INT_MAX);
             $stream->forward();
         } else {
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/ObjectToken.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/ObjectToken.php
new file mode 100644 (file)
index 0000000..852978f
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Type\Parser\Lexer\Token;
+
+use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
+use CuyZ\Valinor\Type\Type;
+use CuyZ\Valinor\Utility\Reflection\Reflection;
+
+/** @internal */
+final class ObjectToken implements TraversingToken
+{
+    public function __construct(
+        /** @var class-string */
+        private string $className,
+    ) {}
+
+    public function traverse(TokenStream $stream): Type
+    {
+        return Reflection::enumExists($this->className)
+            ? (new EnumNameToken($this->className))->traverse($stream)
+            : (new ClassNameToken($this->className))->traverse($stream);
+    }
+
+    public function symbol(): string
+    {
+        return $this->className;
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/TripleDotsToken.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/TripleDotsToken.php
new file mode 100644 (file)
index 0000000..a800bba
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Type\Parser\Lexer\Token;
+
+use CuyZ\Valinor\Utility\IsSingleton;
+
+/** @internal */
+final class TripleDotsToken implements Token
+{
+    use IsSingleton;
+
+    public function symbol(): string
+    {
+        return '...';
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/UnknownSymbolToken.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/UnknownSymbolToken.php
deleted file mode 100644 (file)
index 0104962..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Type\Parser\Lexer\Token;
-
-use CuyZ\Valinor\Type\Parser\Exception\UnknownSymbol;
-use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
-use CuyZ\Valinor\Type\Type;
-
-/** @internal */
-final class UnknownSymbolToken implements TraversingToken
-{
-    public function __construct(private string $symbol) {}
-
-    public function traverse(TokenStream $stream): Type
-    {
-        throw new UnknownSymbol($this->symbol);
-    }
-
-    public function symbol(): string
-    {
-        return $this->symbol;
-    }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/VacantToken.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/Token/VacantToken.php
new file mode 100644 (file)
index 0000000..f3fa0c3
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace CuyZ\Valinor\Type\Parser\Lexer\Token;
+
+use CuyZ\Valinor\Type\Parser\Exception\UnknownSymbol;
+use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeParserSpecification;
+use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
+use CuyZ\Valinor\Type\Type;
+use CuyZ\Valinor\Utility\Reflection\Reflection;
+
+/** @internal */
+final class VacantToken implements TraversingToken
+{
+    public function __construct(
+        private string $symbol,
+        /** @var array<TypeParserSpecification> */
+        private array $specifications,
+    ) {}
+
+    public function traverse(TokenStream $stream): Type
+    {
+        $token = $this;
+
+        foreach ($this->specifications as $specification) {
+            $new = $specification->manipulateToken($token);
+
+            if ($new !== $token) {
+                return $new->traverse($stream);
+            }
+        }
+
+        if (Reflection::enumExists($this->symbol)) {
+            return (new EnumNameToken($this->symbol))->traverse($stream);
+        }
+
+        if (Reflection::classOrInterfaceExists($this->symbol)) {
+            return (new ClassNameToken($this->symbol))->traverse($stream);
+        }
+
+        throw new UnknownSymbol($this->symbol);
+    }
+
+    public function symbol(): string
+    {
+        return $this->symbol;
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/TokensExtractor.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/TokensExtractor.php
new file mode 100644 (file)
index 0000000..aa21974
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+namespace CuyZ\Valinor\Type\Parser\Lexer;
+
+use function array_map;
+use function array_shift;
+use function implode;
+use function preg_split;
+
+/** @internal */
+final class TokensExtractor
+{
+    private const TOKEN_PATTERNS = [
+        'Anonymous class' => '[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++',
+        'Double colons' => '\:\:',
+        'Triple dots' => '\.\.\.',
+        'Dollar sign' => '\$',
+        'Whitespace' => '\s',
+        'Union' => '\|',
+        'Intersection' => '&',
+        'Opening bracket' => '\<',
+        'Closing bracket' => '\>',
+        'Opening square bracket' => '\[',
+        'Closing square bracket' => '\]',
+        'Opening curly bracket' => '\{',
+        'Closing curly bracket' => '\}',
+        'Colon' => '\:',
+        'Question mark' => '\?',
+        'Comma' => ',',
+        'Single quote' => "'",
+        'Double quote' => '"',
+    ];
+
+    /** @var list<string> */
+    private array $symbols = [];
+
+    public function __construct(string $string)
+    {
+        $pattern = '/(' . implode('|', self::TOKEN_PATTERNS) . ')' . '/';
+        $tokens = preg_split($pattern, $string, flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+        $quote = null;
+        $text = null;
+
+        while (($token = array_shift($tokens)) !== null) {
+            if ($token === $quote) {
+                if ($text !== null) {
+                    $this->symbols[] = $text;
+                }
+
+                $this->symbols[] = $token;
+
+                $text = null;
+                $quote = null;
+            } elseif ($quote !== null) {
+                $text .= $token;
+            } elseif ($token === '"' || $token === "'") {
+                $quote = $token;
+
+                $this->symbols[] = $token;
+            } else {
+                $this->symbols[] = $token;
+            }
+        }
+
+        if ($text !== null) {
+            $this->symbols[] = $text;
+        }
+
+        $this->symbols = array_map('trim', $this->symbols);
+        $this->symbols = array_filter($this->symbols, static fn ($value) => $value !== '');
+        $this->symbols = array_values($this->symbols);
+    }
+
+    /**
+     * @return list<string>
+     */
+    public function all(): array
+    {
+        return $this->symbols;
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/TypeAliasLexer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/TypeAliasLexer.php
deleted file mode 100644 (file)
index 698a929..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace CuyZ\Valinor\Type\Parser\Lexer;
-
-use CuyZ\Valinor\Type\Parser\Lexer\Token\Token;
-use CuyZ\Valinor\Type\Parser\Lexer\Token\TypeToken;
-use CuyZ\Valinor\Type\Type;
-
-/** @internal */
-final class TypeAliasLexer implements TypeLexer
-{
-    public function __construct(
-        private TypeLexer $delegate,
-        /** @var array<string, Type> */
-        private array $aliases
-    ) {}
-
-    public function tokenize(string $symbol): Token
-    {
-        if (isset($this->aliases[$symbol])) {
-            return new TypeToken($this->aliases[$symbol], $symbol);
-        }
-
-        return $this->delegate->tokenize($symbol);
-    }
-}
index 116de4ce0fe68dc29b8cd6d30297a286e99dc552..10ecb35f0373a2ccaa33eb86e75a94e6708a2abb 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace CuyZ\Valinor\Type\Parser;
 
+use CuyZ\Valinor\Type\Parser\Lexer\TokensExtractor;
 use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
 use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
 use CuyZ\Valinor\Type\Type;
@@ -13,7 +14,7 @@ class LexingParser implements TypeParser
 
     public function parse(string $raw): Type
     {
-        $symbols = new ParserSymbols($raw);
+        $symbols = new TokensExtractor($raw);
 
         $tokens = array_map(
             fn (string $symbol) => $this->lexer->tokenize($symbol),
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/ParserSymbols.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/ParserSymbols.php
deleted file mode 100644 (file)
index 15053ae..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-namespace CuyZ\Valinor\Type\Parser;
-
-/** @internal */
-final class ParserSymbols
-{
-    private const OPERATORS = [' ', '|', '&', '<', '>', '[', ']', '{', '}', ':', '?', ',', "'", '"'];
-
-    /** @var list<string> */
-    private array $symbols = [];
-
-    public function __construct(string $string)
-    {
-        $current = null;
-        $quote = null;
-
-        foreach (str_split($string) as $char) {
-            if ($char === $quote) {
-                $quote = null;
-            } elseif ($char === '"' || $char === "'") {
-                $quote = $char;
-            } elseif ($quote !== null || ! in_array($char, self::OPERATORS, true)) {
-                $current .= $char;
-                continue;
-            }
-
-            if ($current !== null) {
-                $this->symbols[] = $current;
-                $current = null;
-            }
-
-            $this->symbols[] = $char;
-        }
-
-        if ($current !== null) {
-            $this->symbols[] = $current;
-        }
-
-        $this->symbols = array_map('trim', $this->symbols);
-        $this->symbols = array_filter($this->symbols, static fn ($value) => $value !== '');
-
-        $this->mergeDoubleColons();
-        $this->detectAnonymousClass();
-    }
-
-    /**
-     * @return list<string>
-     */
-    public function all(): array
-    {
-        return $this->symbols;
-    }
-
-    private function mergeDoubleColons(): void
-    {
-        foreach ($this->symbols as $key => $symbol) {
-            /** @infection-ignore-all should not happen so it is not tested */
-            if ($key === 0) {
-                continue;
-            }
-
-            if ($symbol === ':' && $this->symbols[$key - 1] === ':') {
-                $this->symbols[$key - 1] = '::';
-                unset($this->symbols[$key]);
-            }
-        }
-    }
-
-    private function detectAnonymousClass(): void
-    {
-        foreach ($this->symbols as $key => $symbol) {
-            if (! str_starts_with($symbol, "class@anonymous\0")) {
-                continue;
-            }
-
-            $this->symbols[$key] = $symbol . $this->symbols[$key + 1] . $this->symbols[$key + 2];
-
-            array_splice($this->symbols, $key + 1, 2);
-        }
-    }
-}
index dd0bd84777480f9500a44f23625cbc2a159f86e0..931f062a17efda265dc9eb3a150a0563e4d16e27 100644 (file)
@@ -4,16 +4,19 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Type\Types;
 
-use CuyZ\Valinor\Type\CombiningType;
+use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder;
 use CuyZ\Valinor\Type\IntegerType;
 use CuyZ\Valinor\Type\Parser\Exception\Iterable\InvalidArrayKey;
+use CuyZ\Valinor\Type\ScalarType;
 use CuyZ\Valinor\Type\StringType;
 use CuyZ\Valinor\Type\Type;
+use LogicException;
 
 use function is_int;
 
 /** @internal */
-final class ArrayKeyType implements Type
+final class ArrayKeyType implements ScalarType
 {
     private static self $default;
 
@@ -21,28 +24,36 @@ final class ArrayKeyType implements Type
 
     private static self $string;
 
-    /** @var array<Type> */
+    /** @var non-empty-list<IntegerType|StringType> */
     private array $types;
 
     private string $signature;
 
     private function __construct(Type $type)
     {
-        $this->signature = $type->toString();
-        $this->types = $type instanceof CombiningType
+        $types = $type instanceof UnionType
             ? [...$type->types()]
             : [$type];
 
-        foreach ($this->types as $subType) {
+        foreach ($types as $subType) {
             if (! $subType instanceof IntegerType && ! $subType instanceof StringType) {
                 throw new InvalidArrayKey($subType);
             }
         }
+
+        /** @var non-empty-list<IntegerType|StringType> $types */
+        $this->types = $types;
+        $this->signature = $type->toString();
     }
 
     public static function default(): self
     {
-        return self::$default ??= new self(new UnionType(NativeIntegerType::get(), NativeStringType::get()));
+        if (!isset(self::$default)) {
+            self::$default = new self(new UnionType(NativeIntegerType::get(), NativeStringType::get()));
+            self::$default->signature = 'array-key';
+        }
+
+        return self::$default;
     }
 
     public static function integer(): self
@@ -55,7 +66,7 @@ final class ArrayKeyType implements Type
         return self::$string ??= new self(NativeStringType::get());
     }
 
-    public static function from(Type $type): ?self
+    public static function from(Type $type): self
     {
         return match (true) {
             $type instanceof self => $type,
@@ -86,6 +97,10 @@ final class ArrayKeyType implements Type
             return true;
         }
 
+        if ($other instanceof UnionType) {
+            return $this->isMatchedBy($other);
+        }
+
         if (! $other instanceof self) {
             return false;
         }
@@ -114,6 +129,33 @@ final class ArrayKeyType implements Type
         return false;
     }
 
+    public function canCast(mixed $value): bool
+    {
+        foreach ($this->types as $type) {
+            if ($type->canCast($value)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public function cast(mixed $value): string|int
+    {
+        foreach ($this->types as $type) {
+            if ($type->canCast($value)) {
+                return $type->cast($value);
+            }
+        }
+
+        throw new LogicException();
+    }
+
+    public function errorMessage(): ErrorMessage
+    {
+        return MessageBuilder::newError('Value {source_value} is not a valid array key.')->build();
+    }
+
     public function toString(): string
     {
         return $this->signature;
index 4db2359d443d7d3ab84bc13abc71e3ac195e404f..b94cfddca309ddfadbe0a34ae22b00c8b746103f 100644 (file)
@@ -18,7 +18,7 @@ final class InterfaceType implements ObjectType, GenericType
     public function __construct(
         /** @var class-string */
         private string $interfaceName,
-        /** @var array<string, Type> */
+        /** @var array<non-empty-string, Type> */
         private array $generics = []
     ) {}
 
@@ -51,7 +51,7 @@ final class InterfaceType implements ObjectType, GenericType
             return false;
         }
 
-        return is_a($other->className(), $this->interfaceName, true);
+        return is_a($this->interfaceName, $other->className(), true);
     }
 
     public function traverse(): array
index 61724eba52593f53e6f1519b59b3709ab1cbeb66..9b220df91f6c93e8bdd456c16bfec5cd58719d97 100644 (file)
@@ -9,20 +9,21 @@ use CuyZ\Valinor\Type\ObjectType;
 use CuyZ\Valinor\Type\CompositeType;
 use CuyZ\Valinor\Type\Type;
 
+use function array_values;
 use function implode;
 
 /** @internal */
 final class IntersectionType implements CombiningType
 {
-    /** @var ObjectType[] */
+    /** @var non-empty-list<ObjectType> */
     private array $types;
 
     private string $signature;
 
-    public function __construct(ObjectType ...$types)
+    public function __construct(ObjectType $type, ObjectType $otherType, ObjectType ...$otherTypes)
     {
-        $this->types = $types;
-        $this->signature = implode('&', array_map(fn (Type $type) => $type->toString(), $types));
+        $this->types = [$type, $otherType, ...array_values($otherTypes)];
+        $this->signature = implode('&', array_map(fn (Type $type) => $type->toString(), $this->types));
     }
 
     public function accepts(mixed $value): bool
@@ -82,7 +83,7 @@ final class IntersectionType implements CombiningType
     }
 
     /**
-     * @return ObjectType[]
+     * @return non-empty-list<ObjectType>
      */
     public function types(): array
     {
index f669dd08eaaac805f079d748783796837f96fac2..84d6460e7fc31bbb9036ed8f064d9a2bd857cf54 100644 (file)
@@ -42,7 +42,6 @@ final class ListType implements CompositeTraversableType
             return false;
         }
 
-        // PHP8.1 use `array_is_list`
         $i = 0;
 
         foreach ($value as $key => $item) {
index 6b84346b19fa29bcfaaadb7fb548da25b0391fa8..e7ccf38381e4c4ae6cec8ddbe0a64673e680a522 100644 (file)
@@ -11,8 +11,6 @@ use CuyZ\Valinor\Type\CompositeType;
 use CuyZ\Valinor\Type\Type;
 
 use function array_map;
-use function assert;
-use function get_parent_class;
 use function is_a;
 
 /** @internal */
@@ -21,24 +19,12 @@ final class NativeClassType implements ClassType, GenericType
     public function __construct(
         /** @var class-string */
         private string $className,
-        /** @var array<string, Type> */
+        /** @var array<non-empty-string, Type> */
         private array $generics = [],
-        private ?self $parent = null,
     ) {
         $this->className = ltrim($this->className, '\\');
     }
 
-    /**
-     * @param class-string $className
-     */
-    public static function for(string $className): self
-    {
-        $parentClass = get_parent_class($className);
-        $parent = $parentClass ? self::for($parentClass) : null;
-
-        return new self($className, [], $parent);
-    }
-
     public function className(): string
     {
         return $this->className;
@@ -49,18 +35,6 @@ final class NativeClassType implements ClassType, GenericType
         return $this->generics;
     }
 
-    public function hasParent(): bool
-    {
-        return $this->parent instanceof self;
-    }
-
-    public function parent(): self
-    {
-        assert($this->parent instanceof self);
-
-        return $this->parent;
-    }
-
     public function accepts(mixed $value): bool
     {
         return $value instanceof $this->className;
index 4238cdb6ffdd2280c852286b57a0281c5750c730..e94cd09aa3b5dbda342eae065781014b1d807f66 100644 (file)
@@ -9,10 +9,9 @@ use CuyZ\Valinor\Type\CompositeType;
 use CuyZ\Valinor\Type\Parser\Exception\Iterable\ShapedArrayElementDuplicatedKey;
 use CuyZ\Valinor\Type\Type;
 
-use function array_diff;
 use function array_key_exists;
-use function array_keys;
 use function array_map;
+use function assert;
 use function count;
 use function implode;
 use function in_array;
@@ -24,15 +23,13 @@ final class ShapedArrayType implements CompositeType
     /** @var ShapedArrayElement[] */
     private array $elements;
 
-    private string $signature;
+    private bool $isUnsealed = false;
+
+    private ?ArrayType $unsealedType = null;
 
     public function __construct(ShapedArrayElement ...$elements)
     {
         $this->elements = $elements;
-        $this->signature =
-            'array{' .
-            implode(', ', array_map(fn (ShapedArrayElement $element) => $element->toString(), $elements))
-            . '}';
 
         $keys = [];
 
@@ -40,24 +37,56 @@ final class ShapedArrayType implements CompositeType
             $key = $element->key()->value();
 
             if (in_array($key, $keys, true)) {
-                throw new ShapedArrayElementDuplicatedKey((string)$key, $this->signature);
+                throw new ShapedArrayElementDuplicatedKey((string)$key, $this->toString());
             }
 
             $keys[] = $key;
         }
     }
 
+    public static function unsealed(ArrayType $unsealedType, ShapedArrayElement ...$elements): self
+    {
+        $self = new self(...$elements);
+        $self->isUnsealed = true;
+        $self->unsealedType = $unsealedType;
+
+        return $self;
+    }
+
+    public static function unsealedWithoutType(ShapedArrayElement ...$elements): self
+    {
+        $self = new self(...$elements);
+        $self->isUnsealed = true;
+
+        return $self;
+    }
+
+    public function isUnsealed(): bool
+    {
+        return $this->isUnsealed;
+    }
+
+    public function hasUnsealedType(): bool
+    {
+        return $this->unsealedType !== null;
+    }
+
+    public function unsealedType(): ArrayType
+    {
+        assert($this->isUnsealed);
+
+        return $this->unsealedType ?? ArrayType::native();
+    }
+
     public function accepts(mixed $value): bool
     {
         if (! is_array($value)) {
             return false;
         }
 
-        $keys = [];
-
         foreach ($this->elements as $shape) {
             $type = $shape->type();
-            $keys[] = $key = $shape->key()->value();
+            $key = $shape->key()->value();
             $valueExists = array_key_exists($key, $value);
 
             if (! $valueExists && ! $shape->isOptional()) {
@@ -67,11 +96,15 @@ final class ShapedArrayType implements CompositeType
             if ($valueExists && ! $type->accepts($value[$key])) {
                 return false;
             }
+
+            unset($value[$key]);
         }
 
-        $excess = array_diff(array_keys($value), $keys);
+        if ($this->isUnsealed) {
+            return $this->unsealedType()->accepts($value);
+        }
 
-        return count($excess) === 0;
+        return count($value) === 0;
     }
 
     public function matches(Type $other): bool
@@ -98,6 +131,10 @@ final class ShapedArrayType implements CompositeType
                 }
             }
 
+            if ($this->isUnsealed && ! $this->unsealedType()->matches($other)) {
+                return false;
+            }
+
             return true;
         }
 
@@ -107,11 +144,9 @@ final class ShapedArrayType implements CompositeType
 
         foreach ($this->elements as $element) {
             foreach ($other->elements as $otherElement) {
-                if ($element->key()->matches($otherElement->key())) {
-                    if (! $element->type()->matches($otherElement->type())) {
-                        return false;
-                    }
-
+                if ($element->key()->matches($otherElement->key())
+                    && $element->type()->matches($otherElement->type())
+                ) {
                     continue 2;
                 }
             }
@@ -121,6 +156,11 @@ final class ShapedArrayType implements CompositeType
             }
         }
 
+        if ($other->isUnsealed) {
+            return $this->isUnsealed
+                && $this->unsealedType()->matches($other->unsealedType());
+        }
+
         return true;
     }
 
@@ -136,6 +176,10 @@ final class ShapedArrayType implements CompositeType
             }
         }
 
+        if ($this->isUnsealed) {
+            $types = [...$types, $this->unsealedType(), ...$this->unsealedType()->traverse()];
+        }
+
         return $types;
     }
 
@@ -149,6 +193,19 @@ final class ShapedArrayType implements CompositeType
 
     public function toString(): string
     {
-        return $this->signature;
+        $signature = 'array{';
+        $signature .= implode(', ', array_map(fn (ShapedArrayElement $element) => $element->toString(), $this->elements));
+
+        if ($this->isUnsealed) {
+            $signature .= ', ...';
+
+            if ($this->unsealedType) {
+                $signature .= $this->unsealedType->toString();
+            }
+        }
+
+        $signature .= '}';
+
+        return $signature;
     }
 }
index c66ce2bcc2ecd1273b92d87556f2a0aae0959169..5ed460ddd49d9a60a5c2a0a480efa8d15fbc29a3 100644 (file)
@@ -15,30 +15,34 @@ use function implode;
 /** @internal */
 final class UnionType implements CombiningType
 {
-    /** @var Type[] */
-    private array $types = [];
+    /** @var non-empty-list<Type> */
+    private array $types;
 
     private string $signature;
 
-    public function __construct(Type ...$types)
+    public function __construct(Type $type, Type $otherType, Type ...$otherTypes)
     {
-        $this->signature = implode('|', array_map(fn (Type $type) => $type->toString(), $types));
+        $types = [$type, $otherType, ...$otherTypes];
+        $filteredTypes = [];
 
-        foreach ($types as $type) {
-            if ($type instanceof self) {
-                foreach ($type->types as $subType) {
-                    $this->types[] = $subType;
+        foreach ($types as $subType) {
+            if ($subType instanceof self) {
+                foreach ($subType->types as $anotherSubType) {
+                    $filteredTypes[] = $anotherSubType;
                 }
 
                 continue;
             }
 
-            if ($type instanceof MixedType) {
+            if ($subType instanceof MixedType) {
                 throw new ForbiddenMixedType();
             }
 
-            $this->types[] = $type;
+            $filteredTypes[] = $subType;
         }
+
+        $this->types = $filteredTypes;
+        $this->signature = implode('|', array_map(fn (Type $type) => $type->toString(), $this->types));
     }
 
     public function accepts(mixed $value): bool
@@ -65,12 +69,12 @@ final class UnionType implements CombiningType
         }
 
         foreach ($this->types as $type) {
-            if ($type->matches($other)) {
-                return false;
+            if ($type->matches($other)) {
+                return true;
             }
         }
 
-        return true;
+        return false;
     }
 
     public function isMatchedBy(Type $other): bool
index a212e12478c010ad509365d8c1bee382ac0d2cc3..db4e68114b759805106fc912ba640bb2e275750e 100644 (file)
@@ -4,11 +4,15 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Type\Types;
 
-use CuyZ\Valinor\Type\ClassType;
+use CuyZ\Valinor\Type\ObjectType;
 use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
 use CuyZ\Valinor\Type\Type;
+use CuyZ\Valinor\Utility\Reflection\Reflection;
 use CuyZ\Valinor\Utility\ValueDumper;
 use LogicException;
+use ReflectionFunctionAbstract;
+use ReflectionParameter;
+use ReflectionProperty;
 
 /** @internal */
 final class UnresolvableType implements Type
@@ -62,7 +66,22 @@ final class UnresolvableType implements Type
         );
     }
 
-    public static function forLocalAlias(string $raw, string $name, ClassType $type, InvalidType $exception): self
+    public static function forDocBlockTypeNotMatchingNative(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection, Type $typeFromDocBlock, Type $typeFromReflection): self
+    {
+        $signature = Reflection::signature($reflection);
+
+        if ($reflection instanceof ReflectionProperty) {
+            $message = "Types for property `$signature` do not match: `{$typeFromDocBlock->toString()}` (docblock) does not accept `{$typeFromReflection->toString()}` (native).";
+        } elseif ($reflection instanceof ReflectionParameter) {
+            $message = "Types for parameter `$signature` do not match: `{$typeFromDocBlock->toString()}` (docblock) does not accept `{$typeFromReflection->toString()}` (native).";
+        } else {
+            $message = "Return types for method `$signature` do not match: `{$typeFromDocBlock->toString()}` (docblock) does not accept `{$typeFromReflection->toString()}` (native).";
+        }
+
+        return new self($typeFromDocBlock->toString(), $message);
+    }
+
+    public static function forLocalAlias(string $raw, string $name, ObjectType $type, InvalidType $exception): self
     {
         return new self(
             $raw,
index bab41b4616d6175a977dbe1e3abd38d3a1552857..b0cf76f72895ab08b5af43bd4beea56f7a33490d 100644 (file)
@@ -3,14 +3,21 @@
 namespace CuyZ\Valinor\Utility\Reflection;
 
 use CuyZ\Valinor\Type\Parser\Exception\Template\DuplicatedTemplateName;
+use CuyZ\Valinor\Type\Parser\Lexer\TokensExtractor;
 use ReflectionClass;
 use ReflectionFunctionAbstract;
 use ReflectionParameter;
 use ReflectionProperty;
 
+use function array_key_exists;
 use function array_merge;
+use function array_search;
+use function array_shift;
+use function array_splice;
+use function assert;
 use function end;
 use function explode;
+use function in_array;
 use function preg_match;
 use function preg_match_all;
 use function str_replace;
@@ -41,11 +48,25 @@ final class DocParser
             return null;
         }
 
-        if (! preg_match("/(?<type>.*)\\$$reflection->name(\s|\z)/s", $doc, $matches)) {
-            return null;
+        $parameters = [];
+
+        $tokens = (new TokensExtractor($doc))->all();
+
+        while (($token = array_shift($tokens)) !== null) {
+            if (! in_array($token, ['@param', '@phpstan-param', '@psalm-param'], true)) {
+                continue;
+            }
+
+            $dollarSignKey = (int)array_search('$', $tokens, true);
+            $name = $tokens[$dollarSignKey + 1] ?? null;
+
+            $parameters[$name][$token] = implode('', array_splice($tokens, 0, $dollarSignKey));
         }
 
-        return self::annotationType($matches['type'], 'param');
+        return $parameters[$reflection->name]['@phpstan-param']
+            ?? $parameters[$reflection->name]['@psalm-param']
+            ?? $parameters[$reflection->name]['@param']
+            ?? null;
     }
 
     public static function functionReturnType(ReflectionFunctionAbstract $reflection): ?string
@@ -132,7 +153,7 @@ final class DocParser
 
     /**
      * @param ReflectionClass<object> $reflection
-     * @return array<string, string>
+     * @return array<non-empty-string, non-empty-string|null>
      */
     public static function classTemplates(ReflectionClass $reflection): array
     {
@@ -147,12 +168,18 @@ final class DocParser
         preg_match_all("/@(phpstan-|psalm-)?template\s+(?<name>\w+)(\s+of\s+(?<type>.+))?/", $doc, $matches);
 
         foreach ($matches['name'] as $key => $name) {
-            /** @var string $name */
-            if (isset($templates[$name])) {
+            /** @var non-empty-string $name */
+            if (array_key_exists($name, $templates)) {
                 throw new DuplicatedTemplateName($reflection->name, $name);
             }
 
-            $templates[$name] = self::findType($matches['type'][$key]);
+            $template = $matches['type'][$key];
+
+            if ($template === '') {
+                $templates[$name] = null;
+            } else {
+                $templates[$name] = self::findType($template);
+            }
         }
 
         return $templates;
@@ -171,6 +198,9 @@ final class DocParser
         return null;
     }
 
+    /**
+     * @return non-empty-string
+     */
     private static function findType(string $string): string
     {
         $operatorsMatrix = [
@@ -207,7 +237,11 @@ final class DocParser
             $type .= $char;
         }
 
-        return trim($type);
+        $type = trim($type);
+
+        assert($type !== '');
+
+        return $type;
     }
 
     private static function sanitizeDocComment(string|false $doc): ?string
@@ -219,7 +253,7 @@ final class DocParser
 
         $doc = preg_replace('#^\s*/\*\*([^/]+)\*/\s*$#', '$1', $doc);
 
-        return preg_replace('/^\s*\*\s*(\S*)/m', '$1', $doc); // @phpstan-ignore-line
+        return preg_replace('/^\s*\*\s*(\S*)/m', '$1', (string)$doc);
     }
 
     /**
index 307f7f51eda644c24a7e78b07fd706136ec09c93..600cf5ee58f41e7c92f4cc5cc814c973cc896c1b 100644 (file)
@@ -4,9 +4,13 @@ declare(strict_types=1);
 
 namespace CuyZ\Valinor\Utility\Reflection;
 
+use Attribute;
 use Closure;
+use Error;
+use ReflectionAttribute;
 use ReflectionClass;
 use ReflectionFunction;
+use ReflectionFunctionAbstract;
 use ReflectionIntersectionType;
 use ReflectionMethod;
 use ReflectionNamedType;
@@ -15,11 +19,15 @@ use ReflectionProperty;
 use ReflectionType;
 use ReflectionUnionType;
 use Reflector;
-use RuntimeException;
+use UnitEnum;
 
+use function array_filter;
+use function array_map;
 use function class_exists;
+use function enum_exists;
 use function implode;
 use function interface_exists;
+use function ltrim;
 use function spl_object_hash;
 use function str_contains;
 
@@ -32,16 +40,31 @@ final class Reflection
     /** @var array<string, ReflectionFunction> */
     private static array $functionReflection = [];
 
+    /** @var array<string, bool> */
+    private static array $classOrInterfaceExists = [];
+
+    /** @var array<string, bool> */
+    private static array $enumExists = [];
+
     /**
      * Case-sensitive implementation of `class_exists` and `interface_exists`.
+     *
+     * @phpstan-assert-if-true class-string $name
      */
     public static function classOrInterfaceExists(string $name): bool
     {
-        if (! class_exists($name) && ! interface_exists($name)) {
-            return false;
-        }
+        // @infection-ignore-all / We don't need to test the cache
+        return self::$classOrInterfaceExists[$name] ??= (class_exists($name) || interface_exists($name))
+            && self::class($name)->name === ltrim($name, '\\');
+    }
 
-        return self::class($name)->name === ltrim($name, '\\');
+    /**
+     * @phpstan-assert-if-true class-string<UnitEnum> $name
+     */
+    public static function enumExists(string $name): bool
+    {
+        // @infection-ignore-all / We don't need to test the cache
+        return self::$enumExists[$name] ??= enum_exists($name);
     }
 
     /**
@@ -60,12 +83,45 @@ final class Reflection
         return self::$functionReflection[spl_object_hash($closure)] ??= new ReflectionFunction($closure);
     }
 
-    public static function signature(Reflector $reflection): string
+    /**
+     * @param ReflectionClass<object>|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflection
+     * @return array<ReflectionAttribute<object>>
+     */
+    public static function attributes(Reflector $reflection): array
     {
-        if ($reflection instanceof ReflectionClass) {
-            return $reflection->name;
-        }
+        $attributes = array_filter(
+            $reflection->getAttributes(),
+            static fn (ReflectionAttribute $attribute) => $attribute->getName() !== Attribute::class,
+        );
+
+        return array_filter(
+            array_map(
+                static function (ReflectionAttribute $attribute) {
+                    try {
+                        $attribute->newInstance();
+
+                        return $attribute;
+                    } catch (Error) {
+                        // Race condition when the attribute is affected to a property/parameter
+                        // that was PROMOTED, in this case the attribute will be applied to both
+                        // ParameterReflection AND PropertyReflection, BUT the target arg inside the attribute
+                        // class is configured to support only ONE of them (parameter OR property)
+                        // https://wiki.php.net/rfc/constructor_promotion#attributes for more details.
+                        // Ignore attribute if the instantiation failed.
+                        return null;
+                    }
+                },
+                $attributes,
+            ),
+        );
+    }
 
+    /**
+     * @param ReflectionClass<object>|ReflectionProperty|ReflectionMethod|ReflectionFunctionAbstract|ReflectionParameter $reflection
+     * @return non-empty-string
+     */
+    public static function signature(ReflectionClass|ReflectionProperty|ReflectionMethod|ReflectionFunctionAbstract|ReflectionParameter $reflection): string
+    {
         if ($reflection instanceof ReflectionProperty) {
             return "{$reflection->getDeclaringClass()->name}::\$$reflection->name";
         }
@@ -74,7 +130,7 @@ final class Reflection
             return "{$reflection->getDeclaringClass()->name}::$reflection->name()";
         }
 
-        if ($reflection instanceof ReflectionFunction) {
+        if ($reflection instanceof ReflectionFunctionAbstract) {
             if (str_contains($reflection->name, '{closure}')) {
                 $startLine = $reflection->getStartLine();
                 $endLine = $reflection->getEndLine();
@@ -100,7 +156,7 @@ final class Reflection
             return $signature;
         }
 
-        throw new RuntimeException('Invalid reflection type `' . $reflection::class . '`.');
+        return $reflection->name;
     }
 
     public static function flattenType(ReflectionType $type): string
index 2e58f74085cfe530de7efbf19d8fa03254a5af0f..e97bd34c97c1afc769015427b7430a85e243857d 100644 (file)
@@ -38,7 +38,7 @@ final class TokenParser
 
         while ($token = $this->next()) {
             if ($currentNamespace === $namespaceName && $token->is(T_USE)) {
-                $statements = array_merge($statements, $this->parseUseStatement());
+                $statements = [...$statements, ...$this->parseUseStatement()];
                 continue;
             }
 
index 13ece3829cfecd5e513bfcd84056b305c7dc20be..38a16e46e7059eb30ca0aaedfbaf22e31bed402a 100644 (file)
@@ -1,6 +1,8 @@
 # laminas-diactoros
 
-[![Build Status](https://github.com/laminas/laminas-diactoros/workflows/Continuous%20Integration/badge.svg)](https://github.com/laminas/laminas-diactoros/actions/workflows/continuous-integration.yml)
+[![Build Status](https://github.com/laminas/laminas-diactoros/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/laminas/laminas-diactoros/actions/workflows/continuous-integration.yml)
+[![type-coverage](https://shepherd.dev/github/laminas/laminas-diactoros/coverage.svg)](https://shepherd.dev/github/laminas/laminas-diactoros)
+[![Psalm level](https://shepherd.dev/github/laminas/laminas-diactoros/level.svg)](https://shepherd.dev/github/laminas/laminas-diactoros)
 
 > ## 🇷🇺 Русским гражданам
 >
index d8082a165bfd06b3f26edc4bd32c5a698dfbc7c4..f15cd1ea28224380015510085e232f4900d049a3 100644 (file)
@@ -46,9 +46,9 @@
         "http-interop/http-factory-tests": "^0.9.0",
         "laminas/laminas-coding-standard": "~2.5.0",
         "php-http/psr7-integration-tests": "^1.3",
-        "phpunit/phpunit": "^9.5.28",
+        "phpunit/phpunit": "^9.6.16",
         "psalm/plugin-phpunit": "^0.18.4",
-        "vimeo/psalm": "^5.15.0"
+        "vimeo/psalm": "^5.22.1"
     },
     "provide": {
         "psr/http-factory-implementation": "^1.1 || ^2.0",
index 3b290f05943662c799decfd18297b7e2e94580f4..5dc49c1c2aa67e720dbdc20c4983c488da2e3c21 100644 (file)
@@ -99,7 +99,7 @@ abstract class AbstractSerializer
                 continue;
             }
 
-            if (! $currentHeader) {
+            if ($currentHeader === false) {
                 throw Exception\DeserializationException::forInvalidHeader();
             }
 
index 11e10df15a4bf04768fbe29205f4c61357ac8425..4e32fc0f7bf2d323fc83eed4eda6f7da236175d4 100644 (file)
@@ -84,7 +84,7 @@ class CallbackStream implements StreamInterface, Stringable
      */
     public function eof(): bool
     {
-        return empty($this->callback);
+        return $this->callback === null;
     }
 
     /**
@@ -149,7 +149,7 @@ class CallbackStream implements StreamInterface, Stringable
     public function getContents(): string
     {
         $callback = $this->detach();
-        $contents = $callback ? $callback() : '';
+        $contents = $callback !== null ? $callback() : '';
         return (string) $contents;
     }
 
index 5a3bc3bad533a085950383142fd174c06ca1c404..318ce8e69be986c4660238b068e6ea3875ba3e9a 100644 (file)
@@ -6,8 +6,7 @@ namespace Laminas\Diactoros\Exception;
 
 use Laminas\Diactoros\ServerRequestFilter\FilterUsingXForwardedHeaders;
 
-use function gettype;
-use function is_object;
+use function get_debug_type;
 use function is_string;
 use function sprintf;
 
@@ -16,7 +15,7 @@ class InvalidForwardedHeaderNameException extends RuntimeException implements Ex
     public static function forHeader(mixed $name): self
     {
         if (! is_string($name)) {
-            $name = sprintf('(value of type %s)', is_object($name) ? $name::class : gettype($name));
+            $name = sprintf('(value of type %s)', get_debug_type($name));
         }
 
         return new self(sprintf(
index 771c7befcc328a937367367aa6e12fc6de7aa4f6..f9bec776d468c8b82262a41ecd4e30af3fbb8a4a 100644 (file)
@@ -4,15 +4,14 @@ declare(strict_types=1);
 
 namespace Laminas\Diactoros\Exception;
 
-use function gettype;
-use function is_object;
+use function get_debug_type;
 use function sprintf;
 
 class InvalidProxyAddressException extends RuntimeException implements ExceptionInterface
 {
     public static function forInvalidProxyArgument(mixed $proxy): self
     {
-        $type = is_object($proxy) ? $proxy::class : gettype($proxy);
+        $type = get_debug_type($proxy);
         return new self(sprintf(
             'Invalid proxy of type "%s" provided;'
             . ' must be a valid IPv4 or IPv6 address, optionally with a subnet mask provided'
index d12486aebe5f4b59d3e3cab537eae34c8baeb149..0951eb99e93249870e57cd05f68259f1df6011da 100644 (file)
@@ -4,10 +4,9 @@ declare(strict_types=1);
 
 namespace Laminas\Diactoros;
 
-use function gettype;
+use function get_debug_type;
 use function in_array;
 use function is_numeric;
-use function is_object;
 use function is_string;
 use function ord;
 use function preg_match;
@@ -128,7 +127,7 @@ final class HeaderSecurity
         if (! is_string($value) && ! is_numeric($value)) {
             throw new Exception\InvalidArgumentException(sprintf(
                 'Invalid header value type; must be a string or numeric; received %s',
-                is_object($value) ? $value::class : gettype($value)
+                get_debug_type($value)
             ));
         }
         if (! self::isValid($value)) {
@@ -151,7 +150,7 @@ final class HeaderSecurity
         if (! is_string($name)) {
             throw new Exception\InvalidArgumentException(sprintf(
                 'Invalid header name type; expected string; received %s',
-                is_object($name) ? $name::class : gettype($name)
+                get_debug_type($name)
             ));
         }
         if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $name)) {
index 4ce4478b0e23dd758b42325ac069510ce61e2338..e8519a3682dc8e59312c4acf10a0cee8736e9c0a 100644 (file)
@@ -252,7 +252,7 @@ trait RequestTrait
         }
 
         $host = $uri->getHost();
-        if ($uri->getPort()) {
+        if ($uri->getPort() !== null) {
             $host .= ':' . $uri->getPort();
         }
 
@@ -294,7 +294,7 @@ trait RequestTrait
     private function getHostFromUri(): string
     {
         $host  = $this->uri->getHost();
-        $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : '';
+        $host .= $this->uri->getPort() !== null ? ':' . $this->uri->getPort() : '';
         return $host;
     }
 }
index 746f3df322b177953d53b7f695e32547bb3f2e1d..0e0aadb7152154bebc85583c351c469cb1aa585f 100644 (file)
@@ -9,8 +9,7 @@ use Laminas\Diactoros\Response;
 use Laminas\Diactoros\Stream;
 use Psr\Http\Message\StreamInterface;
 
-use function gettype;
-use function is_object;
+use function get_debug_type;
 use function is_string;
 use function sprintf;
 
@@ -60,7 +59,7 @@ class HtmlResponse extends Response
         if (! is_string($html)) {
             throw new Exception\InvalidArgumentException(sprintf(
                 'Invalid content (%s) provided to %s',
-                is_object($html) ? $html::class : gettype($html),
+                get_debug_type($html),
                 self::class
             ));
         }
index 0f42ebc92fe019774bd4a67168c2eee19affb623..a6d3bae373458e996693ced6c229d84a4ba70ed5 100644 (file)
@@ -8,8 +8,7 @@ use Laminas\Diactoros\Exception;
 use Laminas\Diactoros\Response;
 use Psr\Http\Message\UriInterface;
 
-use function gettype;
-use function is_object;
+use function get_debug_type;
 use function is_string;
 use function sprintf;
 
@@ -36,7 +35,7 @@ class RedirectResponse extends Response
             throw new Exception\InvalidArgumentException(sprintf(
                 'Uri provided to %s MUST be a string or Psr\Http\Message\UriInterface instance; received "%s"',
                 self::class,
-                is_object($uri) ? $uri::class : gettype($uri)
+                get_debug_type($uri)
             ));
         }
 
index 602984911c4283fbaff33509f80793a763191e55..6e9ff6420385e07566d9d08b0f7dc3be168ac61a 100644 (file)
@@ -9,8 +9,7 @@ use Laminas\Diactoros\Response;
 use Laminas\Diactoros\Stream;
 use Psr\Http\Message\StreamInterface;
 
-use function gettype;
-use function is_object;
+use function get_debug_type;
 use function is_string;
 use function sprintf;
 
@@ -60,7 +59,7 @@ class TextResponse extends Response
         if (! is_string($text)) {
             throw new Exception\InvalidArgumentException(sprintf(
                 'Invalid content (%s) provided to %s',
-                is_object($text) ? $text::class : gettype($text),
+                get_debug_type($text),
                 self::class
             ));
         }
index 8a4d4237e8507be146916ef6927dcd13cb1c832b..a1efe5632f128e66c22de7286164eb6fa807f121 100644 (file)
@@ -9,8 +9,7 @@ use Laminas\Diactoros\Response;
 use Laminas\Diactoros\Stream;
 use Psr\Http\Message\StreamInterface;
 
-use function gettype;
-use function is_object;
+use function get_debug_type;
 use function is_string;
 use function sprintf;
 
@@ -62,7 +61,7 @@ class XmlResponse extends Response
         if (! is_string($xml)) {
             throw new Exception\InvalidArgumentException(sprintf(
                 'Invalid content (%s) provided to %s',
-                is_object($xml) ? $xml::class : gettype($xml),
+                get_debug_type($xml),
                 self::class
             ));
         }
index 2af1bb8bce8a4a5d191b7677d1cbd8436f96cb9c..2c0955de334b62e30b0aed6deacd1fdeda33bd74 100644 (file)
@@ -210,11 +210,11 @@ class Stream implements StreamInterface, Stringable
         $meta = stream_get_meta_data($this->resource);
         $mode = $meta['mode'];
 
-        return strstr($mode, 'x')
-            || strstr($mode, 'w')
-            || strstr($mode, 'c')
-            || strstr($mode, 'a')
-            || strstr($mode, '+');
+        return strstr($mode, 'x') !== false
+            || strstr($mode, 'w') !== false
+            || strstr($mode, 'c') !== false
+            || strstr($mode, 'a') !== false
+            || strstr($mode, '+') !== false;
     }
 
     /**
@@ -251,7 +251,7 @@ class Stream implements StreamInterface, Stringable
         $meta = stream_get_meta_data($this->resource);
         $mode = $meta['mode'];
 
-        return strstr($mode, 'r') || strstr($mode, '+');
+        return strstr($mode, 'r') !== false || strstr($mode, '+') !== false;
     }
 
     /**
index 354b04280d5a456c599a3502e46085a8ef24f7eb..ba32a1c6501bf0365f66e73a2d99ea2270d44cbe 100644 (file)
@@ -50,8 +50,7 @@ class UploadedFile implements UploadedFileInterface
 
     private bool $moved = false;
 
-    /** @var null|StreamInterface */
-    private $stream;
+    private ?StreamInterface $stream = null;
 
     /**
      * @param string|resource|StreamInterface $streamOrFile
@@ -72,7 +71,7 @@ class UploadedFile implements UploadedFileInterface
                 $this->stream = new Stream($streamOrFile);
             }
 
-            if (! $this->file && ! $this->stream) {
+            if ($this->file === null && $this->stream === null) {
                 if (! $streamOrFile instanceof StreamInterface) {
                     throw new Exception\InvalidArgumentException('Invalid stream or file provided for UploadedFile');
                 }
@@ -150,7 +149,10 @@ class UploadedFile implements UploadedFileInterface
 
         $sapi = PHP_SAPI;
         switch (true) {
-            case empty($sapi) || str_starts_with($sapi, 'cli') || str_starts_with($sapi, 'phpdbg') || ! $this->file:
+            case empty($sapi)
+                || str_starts_with($sapi, 'cli')
+                || str_starts_with($sapi, 'phpdbg')
+                || $this->file === null:
                 // Non-SAPI environment, or no filename present
                 $this->writeFile($targetPath);
 
index 31f5a1de86508c5e83abb4df3c4ee4d8a5522eba..1c8c3a504418c87f9a7e80e47c84ea0cd62bd069 100644 (file)
@@ -60,7 +60,7 @@ class UriFactory implements UriFactoryInterface
         [$host, $port] = self::marshalHostAndPort($server, $headers);
         if (! empty($host)) {
             $uri = $uri->withHost($host);
-            if (! empty($port)) {
+            if ($port !== null) {
                 $uri = $uri->withPort($port);
             }
         }
@@ -115,7 +115,7 @@ class UriFactory implements UriFactoryInterface
      * Marshal the host and port from the PHP environment.
      *
      * @param array<string, string|list<string>> $headers
-     * @return array{string, int|null} Array of two items, host and port,
+     * @return array{0:string, 1:int|null} Array of two items, host and port,
      *     in that order (can be passed to a list() operation).
      */
     private static function marshalHostAndPort(array $server, array $headers): array
@@ -162,7 +162,7 @@ class UriFactory implements UriFactoryInterface
     private static function marshalIpv6HostAndPort(array $server, ?int $port): array
     {
         $host             = '[' . (string) $server['SERVER_ADDR'] . ']';
-        $port             = $port ?: 80;
+        $port             = $port ?? 80;
         $portSeparatorPos = strrpos($host, ':');
 
         if (false === $portSeparatorPos) {
index cedddd8633ec96781bc8ed07aaa65418e7114121..d2572e91ce746fc1438f93b173a63c883edd1773 100644 (file)
@@ -11,7 +11,7 @@ this library aims to offer character encoding functions that do not leak
 information about what you are encoding/decoding via processor cache 
 misses. Further reading on [cache-timing attacks](http://blog.ircmaxell.com/2014/11/its-all-about-time.html).
 
-Our fork offers the following enchancements:
+Our fork offers the following enhancements:
 
 * `mbstring.func_overload` resistance
 * Unit tests
index 7508b3df6f59cc4fff77caca98047d746d6d02d7..48d00b9911e097401da1dbe6b3a5a71ca08293f3 100644 (file)
@@ -44,8 +44,11 @@ abstract class Base32 implements EncoderInterface
      * @param bool $strictPadding
      * @return string
      */
-    public static function decode(string $encodedString, bool $strictPadding = false): string
-    {
+    public static function decode(
+        #[\SensitiveParameter]
+        string $encodedString,
+        bool $strictPadding = false
+    ): string {
         return static::doDecode($encodedString, false, $strictPadding);
     }
 
@@ -56,8 +59,11 @@ abstract class Base32 implements EncoderInterface
      * @param bool $strictPadding
      * @return string
      */
-    public static function decodeUpper(string $src, bool $strictPadding = false): string
-    {
+    public static function decodeUpper(
+        #[\SensitiveParameter]
+        string $src,
+        bool $strictPadding = false
+    ): string {
         return static::doDecode($src, true, $strictPadding);
     }
 
@@ -68,10 +74,13 @@ abstract class Base32 implements EncoderInterface
      * @return string
      * @throws TypeError
      */
-    public static function encode(string $binString): string
-    {
+    public static function encode(
+        #[\SensitiveParameter]
+        string $binString
+    ): string {
         return static::doEncode($binString, false, true);
     }
+
     /**
      * Encode into Base32 (RFC 4648)
      *
@@ -79,8 +88,10 @@ abstract class Base32 implements EncoderInterface
      * @return string
      * @throws TypeError
      */
-    public static function encodeUnpadded(string $src): string
-    {
+    public static function encodeUnpadded(
+        #[\SensitiveParameter]
+        string $src
+    ): string {
         return static::doEncode($src, false, false);
     }
 
@@ -91,8 +102,10 @@ abstract class Base32 implements EncoderInterface
      * @return string
      * @throws TypeError
      */
-    public static function encodeUpper(string $src): string
-    {
+    public static function encodeUpper(
+        #[\SensitiveParameter]
+        string $src
+    ): string {
         return static::doEncode($src, true, true);
     }
 
@@ -103,8 +116,10 @@ abstract class Base32 implements EncoderInterface
      * @return string
      * @throws TypeError
      */
-    public static function encodeUpperUnpadded(string $src): string
-    {
+    public static function encodeUpperUnpadded(
+        #[\SensitiveParameter]
+        string $src
+    ): string {
         return static::doEncode($src, true, false);
     }
 
@@ -191,8 +206,11 @@ abstract class Base32 implements EncoderInterface
      * @param bool $upper
      * @return string
      */
-    public static function decodeNoPadding(string $encodedString, bool $upper = false): string
-    {
+    public static function decodeNoPadding(
+        #[\SensitiveParameter]
+        string $encodedString,
+        bool $upper = false
+    ): string {
         $srcLen = Binary::safeStrlen($encodedString);
         if ($srcLen === 0) {
             return '';
@@ -222,9 +240,9 @@ abstract class Base32 implements EncoderInterface
      * @return string
      *
      * @throws TypeError
-     * @psalm-suppress RedundantCondition
      */
     protected static function doDecode(
+        #[\SensitiveParameter]
         string $src,
         bool $upper = false,
         bool $strictPadding = false
@@ -434,8 +452,12 @@ abstract class Base32 implements EncoderInterface
      * @return string
      * @throws TypeError
      */
-    protected static function doEncode(string $src, bool $upper = false, $pad = true): string
-    {
+    protected static function doEncode(
+        #[\SensitiveParameter]
+        string $src,
+        bool $upper = false,
+        $pad = true
+    ): string {
         // We do this to reduce code duplication:
         $method = $upper
             ? 'encode5BitsUpper'
index f5716179f17438770121f39773bc3df493f969d9..2e3ecc8595f30e9727e8a1b218c85a207b244223 100644 (file)
@@ -47,8 +47,10 @@ abstract class Base64 implements EncoderInterface
      *
      * @throws TypeError
      */
-    public static function encode(string $binString): string
-    {
+    public static function encode(
+        #[\SensitiveParameter]
+        string $binString
+    ): string {
         return static::doEncode($binString, true);
     }
 
@@ -62,8 +64,10 @@ abstract class Base64 implements EncoderInterface
      *
      * @throws TypeError
      */
-    public static function encodeUnpadded(string $src): string
-    {
+    public static function encodeUnpadded(
+        #[\SensitiveParameter]
+        string $src
+    ): string {
         return static::doEncode($src, false);
     }
 
@@ -74,8 +78,11 @@ abstract class Base64 implements EncoderInterface
      *
      * @throws TypeError
      */
-    protected static function doEncode(string $src, bool $pad = true): string
-    {
+    protected static function doEncode(
+        #[\SensitiveParameter]
+        string $src,
+        bool $pad = true
+    ): string {
         $dest = '';
         $srcLen = Binary::safeStrlen($src);
         // Main loop (no padding):
@@ -129,10 +136,12 @@ abstract class Base64 implements EncoderInterface
      *
      * @throws RangeException
      * @throws TypeError
-     * @psalm-suppress RedundantCondition
      */
-    public static function decode(string $encodedString, bool $strictPadding = false): string
-    {
+    public static function decode(
+        #[\SensitiveParameter]
+        string $encodedString,
+        bool $strictPadding = false
+    ): string {
         // Remove padding
         $srcLen = Binary::safeStrlen($encodedString);
         if ($srcLen === 0) {
@@ -227,25 +236,21 @@ abstract class Base64 implements EncoderInterface
      * @param string $encodedString
      * @return string
      */
-    public static function decodeNoPadding(string $encodedString): string
-    {
+    public static function decodeNoPadding(
+        #[\SensitiveParameter]
+        string $encodedString
+    ): string {
         $srcLen = Binary::safeStrlen($encodedString);
         if ($srcLen === 0) {
             return '';
         }
         if (($srcLen & 3) === 0) {
-            if ($encodedString[$srcLen - 1] === '=') {
+            // If $strLen is not zero, and it is divisible by 4, then it's at least 4.
+            if ($encodedString[$srcLen - 1] === '=' || $encodedString[$srcLen - 2] === '=') {
                 throw new InvalidArgumentException(
                     "decodeNoPadding() doesn't tolerate padding"
                 );
             }
-            if (($srcLen & 3) > 1) {
-                if ($encodedString[$srcLen - 2] === '=') {
-                    throw new InvalidArgumentException(
-                        "decodeNoPadding() doesn't tolerate padding"
-                    );
-                }
-            }
         }
         return static::decode(
             $encodedString,
index 828f3e0f64a64be4b0d9e6245ea8d943066e8a7e..5368e4bba7ff29c19138eb2eb59cce335fabd535 100644 (file)
@@ -45,8 +45,10 @@ abstract class Binary
      * @param string $str
      * @return int
      */
-    public static function safeStrlen(string $str): int
-    {
+    public static function safeStrlen(
+        #[\SensitiveParameter]
+        string $str
+    ): int {
         if (\function_exists('mb_strlen')) {
             // mb_strlen in PHP 7.x can return false.
             /** @psalm-suppress RedundantCast */
@@ -70,6 +72,7 @@ abstract class Binary
      * @throws TypeError
      */
     public static function safeSubstr(
+        #[\SensitiveParameter]
         string $str,
         int $start = 0,
         $length = null
index 8649f31fc1994aa1575a2d5b18dd240b6e426779..8b7e3878e1e14102837d748fd0c04e16db29cf01 100644 (file)
@@ -40,8 +40,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base32Encode(string $str): string
-    {
+    public static function base32Encode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32::encode($str);
     }
 
@@ -52,8 +54,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base32EncodeUpper(string $str): string
-    {
+    public static function base32EncodeUpper(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32::encodeUpper($str);
     }
 
@@ -64,8 +68,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base32Decode(string $str): string
-    {
+    public static function base32Decode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32::decode($str);
     }
 
@@ -76,8 +82,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base32DecodeUpper(string $str): string
-    {
+    public static function base32DecodeUpper(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32::decodeUpper($str);
     }
 
@@ -88,8 +96,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base32HexEncode(string $str): string
-    {
+    public static function base32HexEncode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32Hex::encode($str);
     }
 
@@ -100,8 +110,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base32HexEncodeUpper(string $str): string
-    {
+    public static function base32HexEncodeUpper(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32Hex::encodeUpper($str);
     }
 
@@ -112,8 +124,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base32HexDecode(string $str): string
-    {
+    public static function base32HexDecode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32Hex::decode($str);
     }
 
@@ -124,8 +138,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base32HexDecodeUpper(string $str): string
-    {
+    public static function base32HexDecodeUpper(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32Hex::decodeUpper($str);
     }
 
@@ -136,8 +152,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base64Encode(string $str): string
-    {
+    public static function base64Encode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base64::encode($str);
     }
 
@@ -148,8 +166,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base64Decode(string $str): string
-    {
+    public static function base64Decode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base64::decode($str);
     }
 
@@ -161,8 +181,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base64EncodeDotSlash(string $str): string
-    {
+    public static function base64EncodeDotSlash(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base64DotSlash::encode($str);
     }
 
@@ -176,8 +198,10 @@ abstract class Encoding
      * @throws \RangeException
      * @throws TypeError
      */
-    public static function base64DecodeDotSlash(string $str): string
-    {
+    public static function base64DecodeDotSlash(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base64DotSlash::decode($str);
     }
 
@@ -189,8 +213,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function base64EncodeDotSlashOrdered(string $str): string
-    {
+    public static function base64EncodeDotSlashOrdered(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base64DotSlashOrdered::encode($str);
     }
 
@@ -204,8 +230,10 @@ abstract class Encoding
      * @throws \RangeException
      * @throws TypeError
      */
-    public static function base64DecodeDotSlashOrdered(string $str): string
-    {
+    public static function base64DecodeDotSlashOrdered(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base64DotSlashOrdered::decode($str);
     }
 
@@ -217,8 +245,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function hexEncode(string $bin_string): string
-    {
+    public static function hexEncode(
+        #[\SensitiveParameter]
+        string $bin_string
+    ): string {
         return Hex::encode($bin_string);
     }
 
@@ -230,8 +260,10 @@ abstract class Encoding
      * @return string (raw binary)
      * @throws \RangeException
      */
-    public static function hexDecode(string $hex_string): string
-    {
+    public static function hexDecode(
+        #[\SensitiveParameter]
+        string $hex_string
+    ): string {
         return Hex::decode($hex_string);
     }
 
@@ -243,8 +275,10 @@ abstract class Encoding
      * @return string
      * @throws TypeError
      */
-    public static function hexEncodeUpper(string $bin_string): string
-    {
+    public static function hexEncodeUpper(
+        #[\SensitiveParameter]
+        string $bin_string
+    ): string {
         return Hex::encodeUpper($bin_string);
     }
 
@@ -255,8 +289,10 @@ abstract class Encoding
      * @param string $bin_string (raw binary)
      * @return string
      */
-    public static function hexDecodeUpper(string $bin_string): string
-    {
+    public static function hexDecodeUpper(
+        #[\SensitiveParameter]
+        string $bin_string
+    ): string {
         return Hex::decode($bin_string);
     }
 }
index a9e058cd3601cd32565fcb0141ab4d945d0e3af1..97c2046f095a2bcd9f6ad98bfbe51d906a97ebcf 100644 (file)
@@ -42,8 +42,10 @@ abstract class Hex implements EncoderInterface
      * @return string
      * @throws TypeError
      */
-    public static function encode(string $binString): string
-    {
+    public static function encode(
+        #[\SensitiveParameter]
+        string $binString
+    ): string {
         $hex = '';
         $len = Binary::safeStrlen($binString);
         for ($i = 0; $i < $len; ++$i) {
@@ -69,8 +71,10 @@ abstract class Hex implements EncoderInterface
      * @return string
      * @throws TypeError
      */
-    public static function encodeUpper(string $binString): string
-    {
+    public static function encodeUpper(
+        #[\SensitiveParameter]
+        string $binString
+    ): string {
         $hex = '';
         $len = Binary::safeStrlen($binString);
 
@@ -99,6 +103,7 @@ abstract class Hex implements EncoderInterface
      * @throws RangeException
      */
     public static function decode(
+        #[\SensitiveParameter]
         string $encodedString,
         bool $strictPadding = false
     ): string {
index f124d65bfd99dd30e4b34d90fb8d0965aa136982..7cd2e9909d911a5771d92f3339f968ebe58061a8 100644 (file)
@@ -46,8 +46,10 @@ abstract class RFC4648
      *
      * @throws TypeError
      */
-    public static function base64Encode(string $str): string
-    {
+    public static function base64Encode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base64::encode($str);
     }
 
@@ -61,8 +63,10 @@ abstract class RFC4648
      *
      * @throws TypeError
      */
-    public static function base64Decode(string $str): string
-    {
+    public static function base64Decode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base64::decode($str, true);
     }
 
@@ -76,8 +80,10 @@ abstract class RFC4648
      *
      * @throws TypeError
      */
-    public static function base64UrlSafeEncode(string $str): string
-    {
+    public static function base64UrlSafeEncode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base64UrlSafe::encode($str);
     }
 
@@ -91,8 +97,10 @@ abstract class RFC4648
      *
      * @throws TypeError
      */
-    public static function base64UrlSafeDecode(string $str): string
-    {
+    public static function base64UrlSafeDecode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base64UrlSafe::decode($str, true);
     }
 
@@ -106,8 +114,10 @@ abstract class RFC4648
      *
      * @throws TypeError
      */
-    public static function base32Encode(string $str): string
-    {
+    public static function base32Encode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32::encodeUpper($str);
     }
 
@@ -121,8 +131,10 @@ abstract class RFC4648
      *
      * @throws TypeError
      */
-    public static function base32Decode(string $str): string
-    {
+    public static function base32Decode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32::decodeUpper($str, true);
     }
 
@@ -136,8 +148,10 @@ abstract class RFC4648
      *
      * @throws TypeError
      */
-    public static function base32HexEncode(string $str): string
-    {
+    public static function base32HexEncode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32::encodeUpper($str);
     }
 
@@ -151,8 +165,10 @@ abstract class RFC4648
      *
      * @throws TypeError
      */
-    public static function base32HexDecode(string $str): string
-    {
+    public static function base32HexDecode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Base32::decodeUpper($str, true);
     }
 
@@ -166,8 +182,10 @@ abstract class RFC4648
      *
      * @throws TypeError
      */
-    public static function base16Encode(string $str): string
-    {
+    public static function base16Encode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Hex::encodeUpper($str);
     }
 
@@ -179,8 +197,10 @@ abstract class RFC4648
      * @param string $str
      * @return string
      */
-    public static function base16Decode(string $str): string
-    {
+    public static function base16Decode(
+        #[\SensitiveParameter]
+        string $str
+    ): string {
         return Hex::decode($str, true);
     }
-}
\ No newline at end of file
+}
index d1bbddeea36716ed0dacba7c9baef1696c67b9d7..82a1d3266b616ed459dfe1483474fb18bf4fec21 100644 (file)
@@ -1,6 +1,6 @@
 {
     "name": "psr/http-factory",
-    "description": "Common interfaces for PSR-7 HTTP message factories",
+    "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
     "keywords": [
         "psr",
         "psr-7",
             "homepage": "https://www.php-fig.org/"
         }
     ],
+    "support": {
+        "source": "https://github.com/php-fig/http-factory"
+    },
     "require": {
-        "php": ">=7.0.0",
+        "php": ">=7.1",
         "psr/http-message": "^1.0 || ^2.0"
     },
     "autoload": {
index 7db4e30af736bffb28181cf8f454ea3328d0e833..d7adbf0e26e3a19a2e13bfc863ec55cd82349edc 100644 (file)
@@ -15,10 +15,10 @@ interface UploadedFileFactoryInterface
      *
      * @param StreamInterface $stream Underlying stream representing the
      *     uploaded file content.
-     * @param int $size in bytes
+     * @param int|null $size in bytes
      * @param int $error PHP file upload error
-     * @param string $clientFilename Filename as provided by the client, if any.
-     * @param string $clientMediaType Media type as provided by the client, if any.
+     * @param string|null $clientFilename Filename as provided by the client, if any.
+     * @param string|null $clientMediaType Media type as provided by the client, if any.
      *
      * @return UploadedFileInterface
      *
@@ -26,9 +26,9 @@ interface UploadedFileFactoryInterface
      */
     public function createUploadedFile(
         StreamInterface $stream,
-        int $size = null,
+        ?int $size = null,
         int $error = \UPLOAD_ERR_OK,
-        string $clientFilename = null,
-        string $clientMediaType = null
+        ?string $clientFilename = null,
+        ?string $clientMediaType = null
     ): UploadedFileInterface;
 }
index a23fd0e6830e9e898659d90d2e84225db9e83a66..302cf1dd1690f4e6d65e4f22faadb1be80bea336 100644 (file)
@@ -1,4 +1,41 @@
-# Revision History
+# Changelog
+
+All notable changes to this project will be documented in this file.
+This project adheres to [Semantic Versioning](https://semver.org/).
+
+## x.y.z
+
+### Added
+
+### Changed
+
+### Deprecated
+
+### Removed
+
+### Fixed
+
+## 8.5.1
+
+### Fixed
+
+- Fix (regression) failure to parse at-rules with strict parsing (#456)
+
+## 8.5.0
+
+### Added
+
+- Add a method to get an import's media queries (#384)
+- Add more unit tests (#381, #382)
+
+### Fixed
+
+- Retain CSSList and Rule comments when rendering CSS (#351)
+- Replace invalid `turns` unit with `turn` (#350)
+- Also allow string values for rules (#348)
+- Fix invalid calc parsing (#169)
+- Handle scientific notation when parsing sizes (#179)
+- Fix PHP 8.1 compatibility in `ParserState::strsplit()` (#344)
 
 ## 8.4.0
 
index 66fb1c65583b3b08aa017f318f5dc7c4a416116e..90428cbf8a692e0a75d6e7a0a2232fb6da122dd6 100644 (file)
@@ -33,7 +33,7 @@ The resulting CSS document structure can be manipulated prior to being output.
 
 #### Charset
 
-The charset option is used only if no `@charset` declaration is found in the CSS file. UTF-8 is the default, so you won’t have to create a settings object at all if you don’t intend to change that.
+The charset option will only be used if the CSS file does not contain an `@charset` declaration. UTF-8 is the default, so you won’t have to create a settings object at all if you don’t intend to change that.
 
 ```php
 $settings = \Sabberworm\CSS\Settings::create()
@@ -43,7 +43,7 @@ $parser = new \Sabberworm\CSS\Parser($css, $settings);
 
 #### Strict parsing
 
-To have the parser choke on invalid rules, supply a thusly configured `\Sabberworm\CSS\Settings` object:
+To have the parser throw an exception when encountering invalid/unknown constructs (as opposed to trying to ignore them and carry on parsing), supply a thusly configured `\Sabberworm\CSS\Settings` object:
 
 ```php
 $parser = new \Sabberworm\CSS\Parser(
@@ -52,6 +52,8 @@ $parser = new \Sabberworm\CSS\Parser(
 );
 ```
 
+Note that this will also disable a workaround for parsing the unquoted variant of the legacy IE-specific `filter` rule.
+
 #### Disable multibyte functions
 
 To achieve faster parsing, you can choose to have PHP-CSS-Parser use regular string functions instead of `mb_*` functions. This should work fine in most cases, even for UTF-8 files, as all the multibyte characters are in string literals. Still it’s not recommended using this with input you have no control over as it’s not thoroughly covered by test cases.
@@ -67,12 +69,9 @@ The resulting data structure consists mainly of five basic types: `CSSList`, `Ru
 
 #### CSSList
 
-`CSSList` represents a generic CSS container, most likely containing declaration blocks (rule sets with a selector), but it may also contain at-rules, charset declarations, etc. `CSSList` has the following concrete subtypes:
-
-* `Document` – representing the root of a CSS file.
-* `MediaQuery` – represents a subsection of a `CSSList` that only applies to an output device matching the contained media query.
+`CSSList` represents a generic CSS container, most likely containing declaration blocks (rule sets with a selector), but it may also contain at-rules, charset declarations, etc.
 
-To access the items stored in a `CSSList` – like the document you got back when calling `$parser->parse()` –, use `getContents()`, then iterate over that collection and use instanceof to check whether you’re dealing with another `CSSList`, a `RuleSet`, a `Import` or a `Charset`.
+To access the items stored in a `CSSList` – like the document you got back when calling `$parser->parse()` –, use `getContents()`, then iterate over that collection and use `instanceof` to check whether you’re dealing with another `CSSList`, a `RuleSet`, a `Import` or a `Charset`.
 
 To append a new item (selector, media query, etc.) to an existing `CSSList`, construct it using the constructor for this class and use the `append($oItem)` method.
 
@@ -80,16 +79,16 @@ To append a new item (selector, media query, etc.) to an existing `CSSList`, con
 
 `RuleSet` is a container for individual rules. The most common form of a rule set is one constrained by a selector. The following concrete subtypes exist:
 
-* `AtRuleSet` – for generic at-rules which do not match the ones specifically mentioned like `@import`, `@charset` or `@media`. A common example for this is `@font-face`.
+* `AtRuleSet` – for generic at-rules for generic at-rules which are not covered by specific classes, i.e., not `@import`, `@charset` or `@media`. A common example for this is `@font-face`.
 * `DeclarationBlock` – a `RuleSet` constrained by a `Selector`; contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the matching elements.
 
 Note: A `CSSList` can contain other `CSSList`s (and `Import`s as well as a `Charset`), while a `RuleSet` can only contain `Rule`s.
 
-If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)` (which accepts either a `Rule` instance or a rule name; optionally suffixed by a dash to remove all related rules).
+If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)` (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
 
 #### Rule
 
-`Rule`s just have a key (the rule) and a value. These values are all instances of a `Value`.
+`Rule`s just have a string key (the rule) and a `Value`.
 
 #### Value
 
@@ -98,19 +97,21 @@ If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `g
 * `Size` – consists of a numeric `size` value and a unit.
 * `Color` – colors can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are always stored as an array of ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form.
 * `CSSString` – this is just a wrapper for quoted strings to distinguish them from keywords; always output with double quotes.
-* `URL` – URLs in CSS; always output in URL("") notation.
+* `URL` – URLs in CSS; always output in `URL("")` notation.
+
+There is another abstract subclass of `Value`, `ValueList`: A `ValueList` represents a lists of `Value`s, separated by some separation character (mostly `,`, whitespace, or `/`).
 
-There is another abstract subclass of `Value`, `ValueList`. A `ValueList` represents a lists of `Value`s, separated by some separation character (mostly `,`, whitespace, or `/`). There are two types of `ValueList`s:
+There are two types of `ValueList`s:
 
-* `RuleValueList` – The default type, used to represent all multi-valued rules like `font: bold 12px/3 Helvetica, Verdana, sans-serif;` (where the value would be a whitespace-separated list of the primitive value `bold`, a slash-separated list and a comma-separated list).
+* `RuleValueList` – The default type, used to represent all multivalued rules like `font: bold 12px/3 Helvetica, Verdana, sans-serif;` (where the value would be a whitespace-separated list of the primitive value `bold`, a slash-separated list and a comma-separated list).
 * `CSSFunction` – A special kind of value that also contains a function name and where the values are the function’s arguments. Also handles equals-sign-separated argument lists like `filter: alpha(opacity=90);`.
 
 #### Convenience methods
 
-There are a few convenience methods on Document to ease finding, manipulating and deleting rules:
+There are a few convenience methods on `Document` to ease finding, manipulating and deleting rules:
 
-* `getAllDeclarationBlocks()` – does what it says; no matter how deeply nested your selectors are. Aliased as `getAllSelectors()`.
-* `getAllRuleSets()` – does what it says; no matter how deeply nested your rule sets are.
+* `getAllDeclarationBlocks()` – does what it says; no matter how deeply nested the selectors are. Aliased as `getAllSelectors()`.
+* `getAllRuleSets()` – does what it says; no matter how deeply nested the rule sets are.
 * `getAllValues()` – finds all `Value` objects inside `Rule`s.
 
 ## To-Do
@@ -156,7 +157,7 @@ $cssDocument = $parser->parse();
 foreach($cssDocument->getAllRuleSets() as $oRuleSet) {
     // Note that the added dash will make this remove all rules starting with
     // `font-` (like `font-size`, `font-weight`, etc.) as well as a potential
-    // `font-rule`.
+    // `font` rule.
     $oRuleSet->removeRule('font-'); 
     $oRuleSet->removeRule('cursor');
 }
@@ -214,7 +215,8 @@ html, body {
 
 ```
 
-#### Structure (`var_dump()`)
+<details>
+  <summary><b>Structure (<code>var_dump()</code>)</b></summary>
 
 ```php
 class Sabberworm\CSS\CSSList\Document#4 (2) {
@@ -435,6 +437,7 @@ class Sabberworm\CSS\CSSList\Document#4 (2) {
 }
 
 ```
+</details>
 
 #### Output (`render()`)
 
@@ -458,7 +461,8 @@ html, body {font-size: 1.6em;}
 
 ```
 
-#### Structure (`var_dump()`)
+<details>
+  <summary><b>Structure (<code>var_dump()</code>)</b></summary>
 
 ```php
 class Sabberworm\CSS\CSSList\Document#4 (2) {
@@ -603,6 +607,7 @@ class Sabberworm\CSS\CSSList\Document#4 (2) {
 }
 
 ```
+</details>
 
 #### Output (`render()`)
 
index e192dd56a183686a0b629c0bea58ecc64595e662..cf15a9442e8c1bb7d61dc46feb8fb6d20d329c2b 100644 (file)
     "authors": [
         {
             "name": "Raphael Schweikert"
+        },
+        {
+            "name": "Oliver Klee",
+            "email": "github@oliverklee.de"
+        },
+        {
+            "name": "Jake Hotson",
+            "email": "jake.github@qzdesign.co.uk"
         }
     ],
     "require": {
@@ -19,8 +27,7 @@
         "ext-iconv": "*"
     },
     "require-dev": {
-        "phpunit/phpunit": "^4.8.36",
-        "codacy/coverage": "^1.4"
+        "phpunit/phpunit": "^5.7.27"
     },
     "suggest": {
         "ext-mbstring": "for parsing UTF-8 CSS"
             "Sabberworm\\CSS\\Tests\\": "tests/"
         }
     },
+    "extra": {
+        "branch-alias": {
+            "dev-main": "9.0.x-dev"
+        }
+    },
     "scripts": {
         "ci": [
             "@ci:static"
index 218adb9a196fa4045b9d2bc113d9d2040d930e54..598fefc12af736adb26a022db2c9fdb2e022406c 100644 (file)
@@ -61,13 +61,14 @@ class AtRuleBlockList extends CSSBlockList implements AtRule
      */
     public function render(OutputFormat $oOutputFormat)
     {
+        $sResult = $oOutputFormat->comments($this);
+        $sResult .= $oOutputFormat->sBeforeAtRuleBlock;
         $sArgs = $this->sArgs;
         if ($sArgs) {
             $sArgs = ' ' . $sArgs;
         }
-        $sResult = $oOutputFormat->sBeforeAtRuleBlock;
         $sResult .= "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{";
-        $sResult .= parent::render($oOutputFormat);
+        $sResult .= $this->renderListContents($oOutputFormat);
         $sResult .= '}';
         $sResult .= $oOutputFormat->sAfterAtRuleBlock;
         return $sResult;
index 946740a4999a5a1e5d1bc39e2f851c63bcd7de31..603f662b170902a8a45cf79018720846596f7429 100644 (file)
@@ -24,10 +24,10 @@ use Sabberworm\CSS\Value\URL;
 use Sabberworm\CSS\Value\Value;
 
 /**
- * A `CSSList` is the most generic container available. Its contents include `RuleSet` as well as other `CSSList`
- * objects.
+ * This is the most generic container available. It can contain `DeclarationBlock`s (rule sets with a selector),
+ * `RuleSet`s as well as other `CSSList` objects.
  *
- * Also, it may contain `Import` and `Charset` objects stemming from at-rules.
+ * It can also contain `Import` and `Charset` objects stemming from at-rules.
  */
 abstract class CSSList implements Renderable, Commentable
 {
@@ -69,8 +69,9 @@ abstract class CSSList implements Renderable, Commentable
             $oParserState = new ParserState($oParserState, Settings::create());
         }
         $bLenientParsing = $oParserState->getSettings()->bLenientParsing;
+        $aComments = [];
         while (!$oParserState->isEnd()) {
-            $comments = $oParserState->consumeWhiteSpace();
+            $aComments = array_merge($aComments, $oParserState->consumeWhiteSpace());
             $oListItem = null;
             if ($bLenientParsing) {
                 try {
@@ -86,11 +87,12 @@ abstract class CSSList implements Renderable, Commentable
                 return;
             }
             if ($oListItem) {
-                $oListItem->setComments($comments);
+                $oListItem->addComments($aComments);
                 $oList->append($oListItem);
             }
-            $oParserState->consumeWhiteSpace();
+            $aComments = $oParserState->consumeWhiteSpace();
         }
+        $oList->addComments($aComments);
         if (!$bIsRoot && !$bLenientParsing) {
             throw new SourceException("Unexpected end of document", $oParserState->currentLine());
         }
@@ -125,22 +127,19 @@ abstract class CSSList implements Renderable, Commentable
                         $oParserState->currentLine()
                     );
                 }
-                $oParserState->setCharset($oAtRule->getCharset()->getString());
+                $oParserState->setCharset($oAtRule->getCharset());
             }
             return $oAtRule;
         } elseif ($oParserState->comes('}')) {
-            if (!$oParserState->getSettings()->bLenientParsing) {
-                throw new UnexpectedTokenException('CSS selector', '}', 'identifier', $oParserState->currentLine());
-            } else {
-                if ($bIsRoot) {
-                    if ($oParserState->getSettings()->bLenientParsing) {
-                        return DeclarationBlock::parse($oParserState);
-                    } else {
-                        throw new SourceException("Unopened {", $oParserState->currentLine());
-                    }
+            if ($bIsRoot) {
+                if ($oParserState->getSettings()->bLenientParsing) {
+                    return DeclarationBlock::parse($oParserState);
                 } else {
-                    return null;
+                    throw new SourceException("Unopened {", $oParserState->currentLine());
                 }
+            } else {
+                // End of list
+                return null;
             }
         } else {
             return DeclarationBlock::parse($oParserState, $oList);
@@ -172,10 +171,10 @@ abstract class CSSList implements Renderable, Commentable
             $oParserState->consumeUntil([';', ParserState::EOF], true, true);
             return new Import($oLocation, $sMediaQuery ?: null, $iIdentifierLineNum);
         } elseif ($sIdentifier === 'charset') {
-            $sCharset = CSSString::parse($oParserState);
+            $oCharsetString = CSSString::parse($oParserState);
             $oParserState->consumeWhiteSpace();
             $oParserState->consumeUntil([';', ParserState::EOF], true, true);
-            return new Charset($sCharset, $iIdentifierLineNum);
+            return new Charset($oCharsetString, $iIdentifierLineNum);
         } elseif (self::identifierIs($sIdentifier, 'keyframes')) {
             $oResult = new KeyFrame($iIdentifierLineNum);
             $oResult->setVendorKeyFrame($sIdentifier);
@@ -272,7 +271,7 @@ abstract class CSSList implements Renderable, Commentable
     }
 
     /**
-     * Appends an item to tje list of contents.
+     * Appends an item to the list of contents.
      *
      * @param RuleSet|CSSList|Import|Charset $oItem
      *
@@ -402,7 +401,7 @@ abstract class CSSList implements Renderable, Commentable
     /**
      * @return string
      */
-    public function render(OutputFormat $oOutputFormat)
+    protected function renderListContents(OutputFormat $oOutputFormat)
     {
         $sResult = '';
         $bIsFirst = true;
@@ -442,6 +441,8 @@ abstract class CSSList implements Renderable, Commentable
     abstract public function isRootList();
 
     /**
+     * Returns the stored items.
+     *
      * @return array<int, RuleSet|Import|Charset|CSSList>
      */
     public function getContents()
index 91ab2c6b2e7cc5384ac8f6dd00a928651da7b8cc..bad998318699beb1db9c33d04cbb2136dd6bab84 100644 (file)
@@ -11,8 +11,8 @@ use Sabberworm\CSS\RuleSet\RuleSet;
 use Sabberworm\CSS\Value\Value;
 
 /**
- * The root `CSSList` of a parsed file. Contains all top-level CSS contents, mostly declaration blocks,
- * but also any at-rules encountered.
+ * This class represents the root of a parsed CSS file. It contains all top-level CSS contents: mostly declaration
+ * blocks, but also any at-rules encountered (`Import` and `Charset`).
  */
 class Document extends CSSBlockList
 {
@@ -37,7 +37,8 @@ class Document extends CSSBlockList
     }
 
     /**
-     * Gets all `DeclarationBlock` objects recursively.
+     * Gets all `DeclarationBlock` objects recursively, no matter how deeply nested the selectors are.
+     * Aliased as `getAllSelectors()`.
      *
      * @return array<int, DeclarationBlock>
      */
@@ -62,7 +63,7 @@ class Document extends CSSBlockList
     }
 
     /**
-     * Returns all `RuleSet` objects found recursively in the tree.
+     * Returns all `RuleSet` objects recursively found in the tree, no matter how deeply nested the rule sets are.
      *
      * @return array<int, RuleSet>
      */
@@ -75,7 +76,7 @@ class Document extends CSSBlockList
     }
 
     /**
-     * Returns all `Value` objects found recursively in the tree.
+     * Returns all `Value` objects found recursively in `Rule`s in the tree.
      *
      * @param CSSList|RuleSet|string $mElement
      *        the `CSSList` or `RuleSet` to start the search from (defaults to the whole document).
@@ -102,7 +103,7 @@ class Document extends CSSBlockList
     }
 
     /**
-     * Returns all `Selector` objects found recursively in the tree.
+     * Returns all `Selector` objects with the requested specificity found recursively in the tree.
      *
      * Note that this does not yield the full `DeclarationBlock` that the selector belongs to
      * (and, currently, there is no way to get to that).
@@ -159,7 +160,7 @@ class Document extends CSSBlockList
         if ($oOutputFormat === null) {
             $oOutputFormat = new OutputFormat();
         }
-        return parent::render($oOutputFormat);
+        return $oOutputFormat->comments($this) . $this->renderListContents($oOutputFormat);
     }
 
     /**
index d9420e9c0d46984066ca428475bdfc5cad8a70fa..caef7b3d1113de2d621843885ec7f4668d9bc738 100644 (file)
@@ -72,8 +72,9 @@ class KeyFrame extends CSSList implements AtRule
      */
     public function render(OutputFormat $oOutputFormat)
     {
-        $sResult = "@{$this->vendorKeyFrame} {$this->animationName}{$oOutputFormat->spaceBeforeOpeningBrace()}{";
-        $sResult .= parent::render($oOutputFormat);
+        $sResult = $oOutputFormat->comments($this);
+        $sResult .= "@{$this->vendorKeyFrame} {$this->animationName}{$oOutputFormat->spaceBeforeOpeningBrace()}{";
+        $sResult .= $this->renderListContents($oOutputFormat);
         $sResult .= '}';
         return $sResult;
     }
index 595d306431c2298f41fbd0ef4be2ecb62b9c2161..96f26e147e689b30ecb311040c61042cb124755b 100644 (file)
@@ -143,6 +143,13 @@ class OutputFormat
      */
     public $bIgnoreExceptions = false;
 
+    /**
+     * Render comments for lists and RuleSets
+     *
+     * @var bool
+     */
+    public $bRenderComments = false;
+
     /**
      * @var OutputFormatter|null
      */
@@ -314,8 +321,12 @@ class OutputFormat
     public static function createCompact()
     {
         $format = self::create();
-        $format->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('')
-            ->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator('');
+        $format->set('Space*Rules', "")
+            ->set('Space*Blocks', "")
+            ->setSpaceAfterRuleName('')
+            ->setSpaceBeforeOpeningBrace('')
+            ->setSpaceAfterSelectorSeparator('')
+            ->setRenderComments(false);
         return $format;
     }
 
@@ -327,8 +338,11 @@ class OutputFormat
     public static function createPretty()
     {
         $format = self::create();
-        $format->set('Space*Rules', "\n")->set('Space*Blocks', "\n")
-            ->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', ['default' => '', ',' => ' ']);
+        $format->set('Space*Rules', "\n")
+            ->set('Space*Blocks', "\n")
+            ->setSpaceBetweenBlocks("\n\n")
+            ->set('SpaceAfterListArgumentSeparator', ['default' => '', ',' => ' '])
+            ->setRenderComments(true);
         return $format;
     }
 }
index 535feca7ff9d1383fd82c8a319415dd9bbc9fe5b..501d15da309778d684ed5cb9d7f20749dad7fbcd 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace Sabberworm\CSS;
 
+use Sabberworm\CSS\Comment\Commentable;
 use Sabberworm\CSS\Parsing\OutputException;
 
 class OutputFormatter
@@ -211,6 +212,29 @@ class OutputFormatter
         return implode(';', $sString);
     }
 
+    /**
+     *
+     * @param array<Commentable> $aComments
+     *
+     * @return string
+     */
+    public function comments(Commentable $oCommentable)
+    {
+        if (!$this->oFormat->bRenderComments) {
+            return '';
+        }
+
+        $sResult = '';
+        $aComments = $oCommentable->getComments();
+        $iLastCommentIndex = count($aComments) - 1;
+
+        foreach ($aComments as $i => $oComment) {
+            $sResult .= $oComment->render($this->oFormat);
+            $sResult .= $i === $iLastCommentIndex ? $this->spaceAfterBlocks() : $this->spaceBetweenBlocks();
+        }
+        return $sResult;
+    }
+
     /**
      * @param string $sSpaceString
      *
index f3b0493a5fd015457481d31fdabef328bd1cf9d9..e582cfab3b16745ee5ebe6551404cd44db899505 100644 (file)
@@ -17,7 +17,7 @@ class Parser
     private $oParserState;
 
     /**
-     * @param string $sText
+     * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file)
      * @param Settings|null $oParserSettings
      * @param int $iLineNo the line number (starting from 1, not from 0)
      */
@@ -30,6 +30,8 @@ class Parser
     }
 
     /**
+     * Sets the charset to be used if the CSS does not contain an `@charset` declaration.
+     *
      * @param string $sCharset
      *
      * @return void
@@ -40,6 +42,8 @@ class Parser
     }
 
     /**
+     * Returns the charset that is used if the CSS does not contain an `@charset` declaration.
+     *
      * @return void
      */
     public function getCharset()
@@ -49,6 +53,8 @@ class Parser
     }
 
     /**
+     * Parses the CSS provided to the constructor and creates a `Document` from it.
+     *
      * @return Document
      *
      * @throws SourceException
diff --git a/wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Parsing/Anchor.php b/wcfsetup/install/files/lib/system/api/sabberworm/php-css-parser/src/Parsing/Anchor.php
new file mode 100644 (file)
index 0000000..93789e2
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Sabberworm\CSS\Parsing;
+
+class Anchor
+{
+    /**
+     * @var int
+     */
+    private $iPosition;
+
+    /**
+     * @var \Sabberworm\CSS\Parsing\ParserState
+     */
+    private $oParserState;
+
+    /**
+     * @param int $iPosition
+     * @param \Sabberworm\CSS\Parsing\ParserState $oParserState
+     */
+    public function __construct($iPosition, ParserState $oParserState)
+    {
+        $this->iPosition = $iPosition;
+        $this->oParserState = $oParserState;
+    }
+
+    /**
+     * @return void
+     */
+    public function backtrack()
+    {
+        $this->oParserState->setPosition($this->iPosition);
+    }
+}
index e7d85ee0f1e37679b786e81cfabeb79f7c32841b..7a99f327be29303503a908eb2f00d055fa5f8980 100644 (file)
@@ -33,6 +33,8 @@ class ParserState
     private $iCurrentPosition;
 
     /**
+     * will only be used if the CSS does not contain an `@charset` declaration
+     *
      * @var string
      */
     private $sCharset;
@@ -48,7 +50,7 @@ class ParserState
     private $iLineNo;
 
     /**
-     * @param string $sText
+     * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file)
      * @param int $iLineNo
      */
     public function __construct($sText, Settings $oParserSettings, $iLineNo = 1)
@@ -61,6 +63,8 @@ class ParserState
     }
 
     /**
+     * Sets the charset to be used if the CSS does not contain an `@charset` declaration.
+     *
      * @param string $sCharset
      *
      * @return void
@@ -75,6 +79,8 @@ class ParserState
     }
 
     /**
+     * Returns the charset that is used if the CSS does not contain an `@charset` declaration.
+     *
      * @return string
      */
     public function getCharset()
@@ -106,6 +112,24 @@ class ParserState
         return $this->oParserSettings;
     }
 
+    /**
+     * @return \Sabberworm\CSS\Parsing\Anchor
+     */
+    public function anchor()
+    {
+        return new Anchor($this->iCurrentPosition, $this);
+    }
+
+    /**
+     * @param int $iPosition
+     *
+     * @return void
+     */
+    public function setPosition($iPosition)
+    {
+        $this->iCurrentPosition = $iPosition;
+    }
+
     /**
      * @param bool $bIgnoreCase
      *
@@ -115,12 +139,15 @@ class ParserState
      */
     public function parseIdentifier($bIgnoreCase = true)
     {
+        if ($this->isEnd()) {
+            throw new UnexpectedEOFException('', '', 'identifier', $this->iLineNo);
+        }
         $sResult = $this->parseCharacter(true);
         if ($sResult === null) {
             throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo);
         }
         $sCharacter = null;
-        while (($sCharacter = $this->parseCharacter(true)) !== null) {
+        while (!$this->isEnd() && ($sCharacter = $this->parseCharacter(true)) !== null) {
             if (preg_match('/[a-zA-Z0-9\x{00A0}-\x{FFFF}_-]/Sux', $sCharacter)) {
                 $sResult .= $sCharacter;
             } else {
@@ -204,7 +231,7 @@ class ParserState
      */
     public function consumeWhiteSpace()
     {
-        $comments = [];
+        $aComments = [];
         do {
             while (preg_match('/\\s/isSu', $this->peek()) === 1) {
                 $this->consume(1);
@@ -214,16 +241,16 @@ class ParserState
                     $oComment = $this->consumeComment();
                 } catch (UnexpectedEOFException $e) {
                     $this->iCurrentPosition = $this->iLength;
-                    return;
+                    return $aComments;
                 }
             } else {
                 $oComment = $this->consumeComment();
             }
             if ($oComment !== false) {
-                $comments[] = $oComment;
+                $aComments[] = $oComment;
             }
         } while ($oComment !== false);
-        return $comments;
+        return $aComments;
     }
 
     /**
index 3ee0c3d042e753b878d80d7bc9b326ece09d5996..26e1b250a6a00647836afa92d590402914878a4c 100644 (file)
@@ -4,6 +4,7 @@ namespace Sabberworm\CSS\Property;
 
 use Sabberworm\CSS\Comment\Comment;
 use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Value\CSSString;
 
 /**
  * Class representing an `@charset` rule.
@@ -16,9 +17,9 @@ use Sabberworm\CSS\OutputFormat;
 class Charset implements AtRule
 {
     /**
-     * @var string
+     * @var CSSString
      */
-    private $sCharset;
+    private $oCharset;
 
     /**
      * @var int
@@ -31,12 +32,12 @@ class Charset implements AtRule
     protected $aComments;
 
     /**
-     * @param string $sCharset
+     * @param CSSString $oCharset
      * @param int $iLineNo
      */
-    public function __construct($sCharset, $iLineNo = 0)
+    public function __construct(CSSString $oCharset, $iLineNo = 0)
     {
-        $this->sCharset = $sCharset;
+        $this->oCharset = $oCharset;
         $this->iLineNo = $iLineNo;
         $this->aComments = [];
     }
@@ -50,13 +51,14 @@ class Charset implements AtRule
     }
 
     /**
-     * @param string $sCharset
+     * @param string|CSSString $oCharset
      *
      * @return void
      */
     public function setCharset($sCharset)
     {
-        $this->sCharset = $sCharset;
+        $sCharset = $sCharset instanceof CSSString ? $sCharset : new CSSString($sCharset);
+        $this->oCharset = $sCharset;
     }
 
     /**
@@ -64,7 +66,7 @@ class Charset implements AtRule
      */
     public function getCharset()
     {
-        return $this->sCharset;
+        return $this->oCharset->getString();
     }
 
     /**
@@ -80,7 +82,7 @@ class Charset implements AtRule
      */
     public function render(OutputFormat $oOutputFormat)
     {
-        return "@charset {$this->sCharset->render($oOutputFormat)};";
+        return "{$oOutputFormat->comments($this)}@charset {$this->oCharset->render($oOutputFormat)};";
     }
 
     /**
@@ -96,7 +98,7 @@ class Charset implements AtRule
      */
     public function atRuleArgs()
     {
-        return $this->sCharset;
+        return $this->oCharset;
     }
 
     /**
index a2253016b0cca872367f1f8ab1decfb991a6c56e..d715a7a041bbbd02ec79e29eb7b4050456ba7a5a 100644 (file)
@@ -83,7 +83,7 @@ class Import implements AtRule
      */
     public function render(OutputFormat $oOutputFormat)
     {
-        return "@import " . $this->oLocation->render($oOutputFormat)
+        return $oOutputFormat->comments($this) . "@import " . $this->oLocation->render($oOutputFormat)
             . ($this->sMediaQuery === null ? '' : ' ' . $this->sMediaQuery) . ';';
     }
 
@@ -134,4 +134,12 @@ class Import implements AtRule
     {
         $this->aComments = $aComments;
     }
+
+    /**
+     * @return string
+     */
+    public function getMediaQuery()
+    {
+        return $this->sMediaQuery;
+    }
 }
index c1ea6df74e5a124bb415f1e67ccefcf03e606700..fc00c8808b5dcb71758579545b3773f5b647d8ac 100644 (file)
@@ -13,8 +13,9 @@ use Sabberworm\CSS\Value\RuleValueList;
 use Sabberworm\CSS\Value\Value;
 
 /**
- * RuleSets contains Rule objects which always have a key and a value.
- * In CSS, Rules are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];”
+ * `Rule`s just have a string key (the rule) and a 'Value'.
+ *
+ * In CSS, `Rule`s are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];”
  */
 class Rule implements Renderable, Commentable
 {
@@ -24,7 +25,7 @@ class Rule implements Renderable, Commentable
     private $sRule;
 
     /**
-     * @var RuleValueList|null
+     * @var RuleValueList|string|null
      */
     private $mValue;
 
@@ -171,7 +172,7 @@ class Rule implements Renderable, Commentable
     }
 
     /**
-     * @return RuleValueList|null
+     * @return RuleValueList|string|null
      */
     public function getValue()
     {
@@ -179,7 +180,7 @@ class Rule implements Renderable, Commentable
     }
 
     /**
-     * @param RuleValueList|null $mValue
+     * @param RuleValueList|string|null $mValue
      *
      * @return void
      */
@@ -346,8 +347,8 @@ class Rule implements Renderable, Commentable
      */
     public function render(OutputFormat $oOutputFormat)
     {
-        $sResult = "{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}";
-        if ($this->mValue instanceof Value) { //Can also be a ValueList
+        $sResult = "{$oOutputFormat->comments($this)}{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}";
+        if ($this->mValue instanceof Value) { // Can also be a ValueList
             $sResult .= $this->mValue->render($oOutputFormat);
         } else {
             $sResult .= $this->mValue;
index 88bc5bd31391a5586b8549bb8062f1b7f1069276..aab6d799d3012f8caab073789637fc903d49d5cd 100644 (file)
@@ -6,7 +6,10 @@ use Sabberworm\CSS\OutputFormat;
 use Sabberworm\CSS\Property\AtRule;
 
 /**
- * A RuleSet constructed by an unknown at-rule. `@font-face` rules are rendered into AtRuleSet objects.
+ * This class represents rule sets for generic at-rules which are not covered by specific classes, i.e., not
+ * `@import`, `@charset` or `@media`.
+ *
+ * A common example for this is `@font-face`.
  */
 class AtRuleSet extends RuleSet implements AtRule
 {
@@ -61,12 +64,13 @@ class AtRuleSet extends RuleSet implements AtRule
      */
     public function render(OutputFormat $oOutputFormat)
     {
+        $sResult = $oOutputFormat->comments($this);
         $sArgs = $this->sArgs;
         if ($sArgs) {
             $sArgs = ' ' . $sArgs;
         }
-        $sResult = "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{";
-        $sResult .= parent::render($oOutputFormat);
+        $sResult .= "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{";
+        $sResult .= $this->renderRules($oOutputFormat);
         $sResult .= '}';
         return $sResult;
     }
index c27cdd4c81b4ddeac298509987aa8ed25621a2d5..b218bd8c4176f320e4c95264370be562dc396901 100644 (file)
@@ -19,7 +19,10 @@ use Sabberworm\CSS\Value\URL;
 use Sabberworm\CSS\Value\Value;
 
 /**
- * Declaration blocks are the parts of a CSS file which denote the rules belonging to a selector.
+ * This class represents a `RuleSet` constrained by a `Selector`.
+ *
+ * It contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the
+ * matching elements.
  *
  * Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`).
  */
@@ -562,6 +565,7 @@ class DeclarationBlock extends RuleSet
     public function createShorthandProperties(array $aProperties, $sShorthand)
     {
         $aRules = $this->getRulesAssoc();
+        $oRule = null;
         $aNewValues = [];
         foreach ($aProperties as $sProperty) {
             if (!isset($aRules[$sProperty])) {
@@ -582,7 +586,7 @@ class DeclarationBlock extends RuleSet
                 $this->removeRule($sProperty);
             }
         }
-        if (count($aNewValues)) {
+        if ($aNewValues !== [] && $oRule instanceof Rule) {
             $oNewRule = new Rule($sShorthand, $oRule->getLineNo(), $oRule->getColNo());
             foreach ($aNewValues as $mValue) {
                 $oNewRule->addValue($mValue);
@@ -812,18 +816,19 @@ class DeclarationBlock extends RuleSet
      */
     public function render(OutputFormat $oOutputFormat)
     {
+        $sResult = $oOutputFormat->comments($this);
         if (count($this->aSelectors) === 0) {
             // If all the selectors have been removed, this declaration block becomes invalid
             throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo);
         }
-        $sResult = $oOutputFormat->sBeforeDeclarationBlock;
+        $sResult .= $oOutputFormat->sBeforeDeclarationBlock;
         $sResult .= $oOutputFormat->implode(
             $oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(),
             $this->aSelectors
         );
         $sResult .= $oOutputFormat->sAfterDeclarationBlockSelectors;
         $sResult .= $oOutputFormat->spaceBeforeOpeningBrace() . '{';
-        $sResult .= parent::render($oOutputFormat);
+        $sResult .= $this->renderRules($oOutputFormat);
         $sResult .= '}';
         $sResult .= $oOutputFormat->sAfterDeclarationBlock;
         return $sResult;
index 9404bb0bdb967efe9c823889e36a8f9376dfbeea..adb9be9225958666c3b07cd2d6184a144f48b896 100644 (file)
@@ -12,8 +12,13 @@ use Sabberworm\CSS\Renderable;
 use Sabberworm\CSS\Rule\Rule;
 
 /**
- * RuleSet is a generic superclass denoting rules. The typical example for rule sets are declaration block.
- * However, unknown At-Rules (like `@font-face`) are also rule sets.
+ * This class is a container for individual 'Rule's.
+ *
+ * The most common form of a rule set is one constrained by a selector, i.e., a `DeclarationBlock`.
+ * However, unknown `AtRule`s (like `@font-face`) are rule sets as well.
+ *
+ * If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
+ * (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
  */
 abstract class RuleSet implements Renderable, Commentable
 {
@@ -266,23 +271,24 @@ abstract class RuleSet implements Renderable, Commentable
     /**
      * @return string
      */
-    public function render(OutputFormat $oOutputFormat)
+    protected function renderRules(OutputFormat $oOutputFormat)
     {
         $sResult = '';
         $bIsFirst = true;
+        $oNextLevel = $oOutputFormat->nextLevel();
         foreach ($this->aRules as $aRules) {
             foreach ($aRules as $oRule) {
-                $sRendered = $oOutputFormat->safely(function () use ($oRule, $oOutputFormat) {
-                    return $oRule->render($oOutputFormat->nextLevel());
+                $sRendered = $oNextLevel->safely(function () use ($oRule, $oNextLevel) {
+                    return $oRule->render($oNextLevel);
                 });
                 if ($sRendered === null) {
                     continue;
                 }
                 if ($bIsFirst) {
                     $bIsFirst = false;
-                    $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules();
+                    $sResult .= $oNextLevel->spaceBeforeRules();
                 } else {
-                    $sResult .= $oOutputFormat->nextLevel()->spaceBetweenRules();
+                    $sResult .= $oNextLevel->spaceBetweenRules();
                 }
                 $sResult .= $sRendered;
             }
index 7b8580962c98006d03989bb7c9729b6078e8ce7e..79d99803cc250050d9cc8afe327e8402411ed32e 100644 (file)
@@ -11,7 +11,8 @@ class Settings
 {
     /**
      * Multi-byte string support.
-     * If true (mbstring extension must be enabled), will use (slower) `mb_strlen`, `mb_convert_case`, `mb_substr`
+     *
+     * If `true` (`mbstring` extension must be enabled), will use (slower) `mb_strlen`, `mb_convert_case`, `mb_substr`
      * and `mb_strpos` functions. Otherwise, the normal (ASCII-Only) functions will be used.
      *
      * @var bool
@@ -19,15 +20,14 @@ class Settings
     public $bMultibyteSupport;
 
     /**
-     * The default charset for the CSS if no `@charset` rule is found. Defaults to utf-8.
+     * The default charset for the CSS if no `@charset` declaration is found. Defaults to utf-8.
      *
      * @var string
      */
     public $sDefaultCharset = 'utf-8';
 
     /**
-     * Lenient parsing. When used (which is true by default), the parser will not choke
-     * on unexpected tokens but simply ignore them.
+     * Whether the parser silently ignore invalid rules instead of choking on them.
      *
      * @var bool
      */
@@ -47,6 +47,11 @@ class Settings
     }
 
     /**
+     * Enables/disables multi-byte string support.
+     *
+     * If `true` (`mbstring` extension must be enabled), will use (slower) `mb_strlen`, `mb_convert_case`, `mb_substr`
+     * and `mb_strpos` functions. Otherwise, the normal (ASCII-Only) functions will be used.
+     *
      * @param bool $bMultibyteSupport
      *
      * @return self fluent interface
@@ -58,6 +63,8 @@ class Settings
     }
 
     /**
+     * Sets the charset to be used if the CSS does not contain an `@charset` declaration.
+     *
      * @param string $sDefaultCharset
      *
      * @return self fluent interface
@@ -69,6 +76,8 @@ class Settings
     }
 
     /**
+     * Configures whether the parser should silently ignore invalid rules.
+     *
      * @param bool $bLenientParsing
      *
      * @return self fluent interface
@@ -80,6 +89,8 @@ class Settings
     }
 
     /**
+     * Configures the parser to choke on invalid rules.
+     *
      * @return self fluent interface
      */
     public function beStrict()
index e6b8c1189f438be32cf75a04469e7c2e414575d9..300dc3ec0a15ea5275ce8f7e6c0fe85607497af5 100644 (file)
@@ -3,7 +3,12 @@
 namespace Sabberworm\CSS\Value;
 
 use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\ParserState;
 
+/**
+ * A `CSSFunction` represents a special kind of value that also contains a function name and where the values are the
+ * function’s arguments. It also handles equals-sign-separated argument lists like `filter: alpha(opacity=90);`.
+ */
 class CSSFunction extends ValueList
 {
     /**
@@ -28,6 +33,26 @@ class CSSFunction extends ValueList
         parent::__construct($aArguments, $sSeparator, $iLineNo);
     }
 
+    /**
+     * @param ParserState $oParserState
+     * @param bool $bIgnoreCase
+     *
+     * @return CSSFunction
+     *
+     * @throws SourceException
+     * @throws UnexpectedEOFException
+     * @throws UnexpectedTokenException
+     */
+    public static function parse(ParserState $oParserState, $bIgnoreCase = false)
+    {
+        $mResult = $oParserState->parseIdentifier($bIgnoreCase);
+        $oParserState->consume('(');
+        $aArguments = Value::parseValue($oParserState, ['=', ' ', ',']);
+        $mResult = new CSSFunction($mResult, $aArguments, ',', $oParserState->currentLine());
+        $oParserState->consume(')');
+        return $mResult;
+    }
+
     /**
      * @return string
      */
index 9fafedd7a462c2a812a256023652cdcf3604251c..da498d41124aca98130e37fe8040cc6c94b3cf16 100644 (file)
@@ -8,6 +8,11 @@ use Sabberworm\CSS\Parsing\SourceException;
 use Sabberworm\CSS\Parsing\UnexpectedEOFException;
 use Sabberworm\CSS\Parsing\UnexpectedTokenException;
 
+/**
+ * This class is a wrapper for quoted strings to distinguish them from keywords.
+ *
+ * `CSSString`s always output with double quotes.
+ */
 class CSSString extends PrimitiveValue
 {
     /**
index 5c92e0c089b5b5a98f2c0f458821138a1a1d90b9..5ffd071f97695af6d635a577b9f954511c76bf55 100644 (file)
@@ -19,20 +19,35 @@ class CalcFunction extends CSSFunction
     const T_OPERATOR = 2;
 
     /**
+     * @param ParserState $oParserState
+     * @param bool $bIgnoreCase
+     *
      * @return CalcFunction
      *
      * @throws UnexpectedTokenException
      * @throws UnexpectedEOFException
      */
-    public static function parse(ParserState $oParserState)
+    public static function parse(ParserState $oParserState, $bIgnoreCase = false)
     {
         $aOperators = ['+', '-', '*', '/'];
-        $sFunction = trim($oParserState->consumeUntil('(', false, true));
+        $sFunction = $oParserState->parseIdentifier();
+        if ($oParserState->peek() != '(') {
+            // Found ; or end of line before an opening bracket
+            throw new UnexpectedTokenException('(', $oParserState->peek(), 'literal', $oParserState->currentLine());
+        } elseif (!in_array($sFunction, ['calc', '-moz-calc', '-webkit-calc'])) {
+            // Found invalid calc definition. Example calc (...
+            throw new UnexpectedTokenException('calc', $sFunction, 'literal', $oParserState->currentLine());
+        }
+        $oParserState->consume('(');
         $oCalcList = new CalcRuleValueList($oParserState->currentLine());
         $oList = new RuleValueList(',', $oParserState->currentLine());
         $iNestingLevel = 0;
         $iLastComponentType = null;
         while (!$oParserState->comes(')') || $iNestingLevel > 0) {
+            if ($oParserState->isEnd() && $iNestingLevel === 0) {
+                break;
+            }
+
             $oParserState->consumeWhiteSpace();
             if ($oParserState->comes('(')) {
                 $iNestingLevel++;
@@ -83,7 +98,9 @@ class CalcFunction extends CSSFunction
             $oParserState->consumeWhiteSpace();
         }
         $oList->addListComponent($oCalcList);
-        $oParserState->consume(')');
+        if (!$oParserState->isEnd()) {
+            $oParserState->consume(')');
+        }
         return new CalcFunction($sFunction, $oList, ',', $oParserState->currentLine());
     }
 }
index 8dc52960cd61af22df7ec018f0d180ca05e693ae..1cf00ccec901f46a6cb1816ca15b1633fea27f6a 100644 (file)
@@ -7,6 +7,10 @@ use Sabberworm\CSS\Parsing\ParserState;
 use Sabberworm\CSS\Parsing\UnexpectedEOFException;
 use Sabberworm\CSS\Parsing\UnexpectedTokenException;
 
+/**
+ * `Color's can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are always stored as an array of
+ * ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form.
+ */
 class Color extends CSSFunction
 {
     /**
@@ -19,12 +23,15 @@ class Color extends CSSFunction
     }
 
     /**
+     * @param ParserState $oParserState
+     * @param bool $bIgnoreCase
+     *
      * @return Color|CSSFunction
      *
      * @throws UnexpectedEOFException
      * @throws UnexpectedTokenException
      */
-    public static function parse(ParserState $oParserState)
+    public static function parse(ParserState $oParserState, $bIgnoreCase = false)
     {
         $aColor = [];
         if ($oParserState->comes('#')) {
index 5d533a7cbe0cea9601942adc89d9e56c76378e20..8fe88b61065516f92fe1fd4c0efbf6e85fe6408d 100644 (file)
@@ -2,6 +2,11 @@
 
 namespace Sabberworm\CSS\Value;
 
+/**
+ * This class is used to represent all multivalued rules like `font: bold 12px/3 Helvetica, Verdana, sans-serif;`
+ * (where the value would be a whitespace-separated list of the primitive value `bold`, a slash-separated list
+ * and a comma-separated list).
+ */
 class RuleValueList extends ValueList
 {
     /**
index b3801dc17dc028fa9be472ca9c1cd3be4a3ff030..36a32381413b7681c5658e556ee3dab9f643434c 100644 (file)
@@ -7,6 +7,9 @@ use Sabberworm\CSS\Parsing\ParserState;
 use Sabberworm\CSS\Parsing\UnexpectedEOFException;
 use Sabberworm\CSS\Parsing\UnexpectedTokenException;
 
+/**
+ * A `Size` consists of a numeric `size` value and a unit.
+ */
 class Size extends PrimitiveValue
 {
     /**
@@ -24,7 +27,7 @@ class Size extends PrimitiveValue
     /**
      * @var array<int, string>
      */
-    const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turns', 'Hz', 'kHz'];
+    const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turn', 'Hz', 'kHz'];
 
     /**
      * @var array<int, array<string, string>>|null
@@ -74,9 +77,16 @@ class Size extends PrimitiveValue
         if ($oParserState->comes('-')) {
             $sSize .= $oParserState->consume('-');
         }
-        while (is_numeric($oParserState->peek()) || $oParserState->comes('.')) {
+        while (is_numeric($oParserState->peek()) || $oParserState->comes('.') || $oParserState->comes('e', true)) {
             if ($oParserState->comes('.')) {
                 $sSize .= $oParserState->consume('.');
+            } elseif ($oParserState->comes('e', true)) {
+                $sLookahead = $oParserState->peek(1, 1);
+                if (is_numeric($sLookahead) || $sLookahead === '+' || $sLookahead === '-') {
+                    $sSize .= $oParserState->consume(2);
+                } else {
+                    break; // Reached the unit part of the number like "em" or "ex"
+                }
             } else {
                 $sSize .= $oParserState->consume(1);
             }
index 1467d505c5cf99a8a1876a91ae11185d0320d7c9..cdb911c38c73451d6a5064d8f2fcb2eadabd8ab6 100644 (file)
@@ -8,6 +8,9 @@ use Sabberworm\CSS\Parsing\SourceException;
 use Sabberworm\CSS\Parsing\UnexpectedEOFException;
 use Sabberworm\CSS\Parsing\UnexpectedTokenException;
 
+/**
+ * This class represents URLs in CSS. `URL`s always output in `URL("")` notation.
+ */
 class URL extends PrimitiveValue
 {
     /**
@@ -33,11 +36,21 @@ class URL extends PrimitiveValue
      */
     public static function parse(ParserState $oParserState)
     {
-        $bUseUrl = $oParserState->comes('url', true);
+        $oAnchor = $oParserState->anchor();
+        $sIdentifier = '';
+        for ($i = 0; $i < 3; $i++) {
+            $sChar = $oParserState->parseCharacter(true);
+            if ($sChar === null) {
+                break;
+            }
+            $sIdentifier .= $sChar;
+        }
+        $bUseUrl = $oParserState->streql($sIdentifier, 'url');
         if ($bUseUrl) {
-            $oParserState->consume('url');
             $oParserState->consumeWhiteSpace();
             $oParserState->consume('(');
+        } else {
+            $oAnchor->backtrack();
         }
         $oParserState->consumeWhiteSpace();
         $oResult = new URL(CSSString::parse($oParserState), $oParserState->currentLine());
index 66cb9fd47ee2d2291e04eaad496fabeaa610986b..ce6d5790a758b440cab03a916553a1c007561a0d 100644 (file)
@@ -8,6 +8,10 @@ use Sabberworm\CSS\Parsing\UnexpectedEOFException;
 use Sabberworm\CSS\Parsing\UnexpectedTokenException;
 use Sabberworm\CSS\Renderable;
 
+/**
+ * Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another
+ * abstract subclass `ValueList`.
+ */
 abstract class Value implements Renderable
 {
     /**
@@ -39,8 +43,9 @@ abstract class Value implements Renderable
         //Build a list of delimiters and parsed values
         while (
             !($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!')
-            || $oParserState->comes(')')
-            || $oParserState->comes('\\'))
+                || $oParserState->comes(')')
+                || $oParserState->comes('\\')
+                || $oParserState->isEnd())
         ) {
             if (count($aStack) > 0) {
                 $bFoundDelimiter = false;
@@ -101,16 +106,25 @@ abstract class Value implements Renderable
      */
     public static function parseIdentifierOrFunction(ParserState $oParserState, $bIgnoreCase = false)
     {
-        $sResult = $oParserState->parseIdentifier($bIgnoreCase);
+        $oAnchor = $oParserState->anchor();
+        $mResult = $oParserState->parseIdentifier($bIgnoreCase);
 
         if ($oParserState->comes('(')) {
-            $oParserState->consume('(');
-            $aArguments = Value::parseValue($oParserState, ['=', ' ', ',']);
-            $sResult = new CSSFunction($sResult, $aArguments, ',', $oParserState->currentLine());
-            $oParserState->consume(')');
+            $oAnchor->backtrack();
+            if ($oParserState->streql('url', $mResult)) {
+                $mResult = URL::parse($oParserState);
+            } elseif (
+                $oParserState->streql('calc', $mResult)
+                || $oParserState->streql('-webkit-calc', $mResult)
+                || $oParserState->streql('-moz-calc', $mResult)
+            ) {
+                $mResult = CalcFunction::parse($oParserState);
+            } else {
+                $mResult = CSSFunction::parse($oParserState, $bIgnoreCase);
+            }
         }
 
-        return $sResult;
+        return $mResult;
     }
 
     /**
@@ -133,13 +147,6 @@ abstract class Value implements Renderable
             $oValue = Size::parse($oParserState);
         } elseif ($oParserState->comes('#') || $oParserState->comes('rgb', true) || $oParserState->comes('hsl', true)) {
             $oValue = Color::parse($oParserState);
-        } elseif ($oParserState->comes('url', true)) {
-            $oValue = URL::parse($oParserState);
-        } elseif (
-            $oParserState->comes('calc', true) || $oParserState->comes('-webkit-calc', true)
-            || $oParserState->comes('-moz-calc', true)
-        ) {
-            $oValue = CalcFunction::parse($oParserState);
         } elseif ($oParserState->comes("'") || $oParserState->comes('"')) {
             $oValue = CSSString::parse($oParserState);
         } elseif ($oParserState->comes("progid:") && $oParserState->getSettings()->bLenientParsing) {
index af5348b9673dcb13cc534ac6b3d099919f8d65ea..a93acc7b618233c4748c5c2ec9309edf2777bda3 100644 (file)
@@ -4,6 +4,12 @@ namespace Sabberworm\CSS\Value;
 
 use Sabberworm\CSS\OutputFormat;
 
+/**
+ * A `ValueList` represents a lists of `Value`s, separated by some separation character
+ * (mostly `,`, whitespace, or `/`).
+ *
+ * There are two types of `ValueList`s: `RuleValueList` and `CSSFunction`
+ */
 abstract class ValueList extends Value
 {
     /**
index 8081b37ffcbbca1bacdbc68b98d4d8b7a0409f13..10c545290867d8bf0fd79962d3933ab8c2a25d7f 100644 (file)
@@ -2,6 +2,12 @@
 
 All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
 
+## [5.1.1] - 2024-03-02
+
+### Changed
+
+* Do not use implicitly nullable parameters
+
 ## [5.1.0] - 2023-12-22
 
 ### Added
@@ -124,6 +130,7 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt
 
 * This component is no longer supported on PHP 5.6
 
+[5.1.1]: https://github.com/sebastianbergmann/diff/compare/5.1.0...5.1.1
 [5.1.0]: https://github.com/sebastianbergmann/diff/compare/5.0.3...5.1.0
 [5.0.3]: https://github.com/sebastianbergmann/diff/compare/5.0.2...5.0.3
 [5.0.2]: https://github.com/sebastianbergmann/diff/compare/5.0.1...5.0.2
index a453252d525f1d20680a63fec7c9cb1b33ac5010..5b4705a48d71526773da1aed0ce84464e4c94880 100644 (file)
@@ -1,6 +1,6 @@
 BSD 3-Clause License
 
-Copyright (c) 2002-2023, Sebastian Bergmann
+Copyright (c) 2002-2024, Sebastian Bergmann
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
index bc6c3a435eb360455ac0f4edeb969e061b1cf746..c6ebec9c283fe8420c6ad9b38eb2a5359ffa4580 100644 (file)
@@ -31,7 +31,7 @@
     },
     "require-dev": {
         "phpunit/phpunit": "^10.0",
-        "symfony/process": "^4.2 || ^5"
+        "symfony/process": "^6.4"
     },
     "autoload": {
         "classmap": [
index 19ccf97c33cd6c9397ef658d5414fbb4ee071e20..801fe02aaf1f00b02f8eab0b3e84e94afb0fd131 100644 (file)
@@ -42,14 +42,14 @@ final class Differ
         $this->outputBuilder = $outputBuilder;
     }
 
-    public function diff(array|string $from, array|string $to, LongestCommonSubsequenceCalculator $lcs = null): string
+    public function diff(array|string $from, array|string $to, ?LongestCommonSubsequenceCalculator $lcs = null): string
     {
         $diff = $this->diffToArray($from, $to, $lcs);
 
         return $this->outputBuilder->getDiff($diff);
     }
 
-    public function diffToArray(array|string $from, array|string $to, LongestCommonSubsequenceCalculator $lcs = null): array
+    public function diffToArray(array|string $from, array|string $to, ?LongestCommonSubsequenceCalculator $lcs = null): array
     {
         if (is_string($from)) {
             $from = $this->splitStringByLines($from);
index 85f066c2607355b5595b00cfc6d4f04c872f15b2..b2abf0cbf37994c0cafe5f95056995698d08c657 100644 (file)
@@ -21,17 +21,17 @@ final class ConfigurationException extends InvalidArgumentException
         string $expected,
         $value,
         int $code = 0,
-        Exception $previous = null
+        ?Exception $previous = null
     ) {
         parent::__construct(
             sprintf(
                 'Option "%s" must be %s, got "%s".',
                 $option,
                 $expected,
-                is_object($value) ? $value::class : (null === $value ? '<null>' : gettype($value) . '#' . $value)
+                is_object($value) ? $value::class : (null === $value ? '<null>' : gettype($value) . '#' . $value),
             ),
             $code,
-            $previous
+            $previous,
         );
     }
 }
index a46de07d6df8700b86aa93885989281f1215b807..b9846c37e7854a31da9e3da73aeac5df8afab52c 100644 (file)
@@ -61,7 +61,7 @@ final class MemoryEfficientLongestCommonSubsequenceCalculator implements Longest
 
         return array_merge(
             $this->calculate($fromStart, $toStart),
-            $this->calculate($fromEnd, $toEnd)
+            $this->calculate($fromEnd, $toEnd),
         );
     }
 
@@ -78,7 +78,11 @@ final class MemoryEfficientLongestCommonSubsequenceCalculator implements Longest
                 if ($from[$i] === $to[$j]) {
                     $current[$j + 1] = $prev[$j] + 1;
                 } else {
-                    // don't use max() to avoid function call overhead
+                    /**
+                     * @noinspection PhpConditionCanBeReplacedWithMinMaxCallInspection
+                     *
+                     * We do not use max() here to avoid the function call overhead
+                     */
                     if ($current[$j] > $prev[$j + 1]) {
                         $current[$j + 1] = $current[$j];
                     } else {
index 3eb7428d7de279b7d3287f431b2a1fd4b97bb991..a2a73b679e5ee3aa0b4d062c3773010eecdf80ff 100644 (file)
@@ -82,7 +82,7 @@ final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface
             $options['fromFile'],
             null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'],
             $options['toFile'],
-            null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate']
+            null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate'],
         );
 
         $this->collapseRanges      = $options['collapseRanges'];
@@ -201,11 +201,11 @@ final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface
                         $fromRange - $cutOff + $contextStartOffset + $this->contextLines,
                         $toStart - $contextStartOffset,
                         $toRange - $cutOff + $contextStartOffset + $this->contextLines,
-                        $output
+                        $output,
                     );
 
                     $fromStart += $fromRange;
-                    $toStart += $toRange;
+                    $toStart   += $toRange;
 
                     $hunkCapture = false;
                     $sameCount   = $toRange = $fromRange = 0;
@@ -251,7 +251,7 @@ final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface
         $contextEndOffset = min($sameCount, $this->contextLines);
 
         $fromRange -= $sameCount;
-        $toRange -= $sameCount;
+        $toRange   -= $sameCount;
 
         $this->writeHunk(
             $diff,
@@ -261,7 +261,7 @@ final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface
             $fromRange + $contextStartOffset + $contextEndOffset,
             $toStart - $contextStartOffset,
             $toRange + $contextStartOffset + $contextEndOffset,
-            $output
+            $output,
         );
     }
 
@@ -302,11 +302,11 @@ final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface
                 $this->changed = true;
                 fwrite($output, $diff[$i][0]);
             }
-            //} elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package
+            // } elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package
             //  skip
-            //} else {
+            // } else {
             //  unknown/invalid
-            //}
+            // }
         }
     }
 
index 1483b3aff6724f20f9cdd47189900a56ed7c7cd2..683ab1b6d6f892bef4ab57fc74449a219e4dafa8 100644 (file)
@@ -18,7 +18,6 @@ use function max;
 use function min;
 use function str_ends_with;
 use function stream_get_contents;
-use function strlen;
 use function substr;
 use SebastianBergmann\Diff\Differ;
 
@@ -67,7 +66,7 @@ final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder
         // This might happen when both the `from` and `to` do not have a trailing linebreak
         $last = substr($diff, -1);
 
-        return 0 !== strlen($diff) && "\n" !== $last && "\r" !== $last
+        return '' !== $diff && "\n" !== $last && "\r" !== $last
             ? $diff . "\n"
             : $diff;
     }
@@ -151,11 +150,11 @@ final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder
                         $fromRange - $cutOff + $contextStartOffset + $this->contextLines,
                         $toStart - $contextStartOffset,
                         $toRange - $cutOff + $contextStartOffset + $this->contextLines,
-                        $output
+                        $output,
                     );
 
                     $fromStart += $fromRange;
-                    $toStart += $toRange;
+                    $toStart   += $toRange;
 
                     $hunkCapture = false;
                     $sameCount   = $toRange = $fromRange = 0;
@@ -199,7 +198,7 @@ final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder
         $contextEndOffset = min($sameCount, $this->contextLines);
 
         $fromRange -= $sameCount;
-        $toRange -= $sameCount;
+        $toRange   -= $sameCount;
 
         $this->writeHunk(
             $diff,
@@ -209,7 +208,7 @@ final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder
             $fromRange + $contextStartOffset + $contextEndOffset,
             $toStart - $contextStartOffset,
             $toRange + $contextStartOffset + $contextEndOffset,
-            $output
+            $output,
         );
     }
 
index 6232c95761a9278c40091a03503fec3398b0ea94..9293fc91949bb148cb7647140bc67c246afe8a7f 100644 (file)
@@ -83,7 +83,7 @@ final class Parser
                     (int) $match['start'],
                     isset($match['startrange']) ? max(0, (int) $match['startrange']) : 1,
                     (int) $match['end'],
-                    isset($match['endrange']) ? max(0, (int) $match['endrange']) : 1
+                    isset($match['endrange']) ? max(0, (int) $match['endrange']) : 1,
                 );
 
                 $chunks[]  = $chunk;
index 39f4d9cc07d59166d06d3bf78cf34f469d02f8a2..76d2078ea3b6f6d2dd4f3d3946b260736350172d 100644 (file)
@@ -26,7 +26,7 @@ class ElementNode extends AbstractNode
     private ?string $namespace;
     private ?string $element;
 
-    public function __construct(string $namespace = null, string $element = null)
+    public function __construct(?string $namespace = null, ?string $element = null)
     {
         $this->namespace = $namespace;
         $this->element = $element;
index 0b09ab5dc70940759396c704a07affc206cc117f..2318b2bf264e161a4d7d827469ec5af951465d57 100644 (file)
@@ -26,7 +26,7 @@ class SelectorNode extends AbstractNode
     private NodeInterface $tree;
     private ?string $pseudoElement;
 
-    public function __construct(NodeInterface $tree, string $pseudoElement = null)
+    public function __construct(NodeInterface $tree, ?string $pseudoElement = null)
     {
         $this->tree = $tree;
         $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null;
index 5313d3435ba9c9b37bb8db2293f3ad9cd0cc5012..309c9b5215c25909d653815d7f3b5e0cd52fe291 100644 (file)
@@ -29,7 +29,7 @@ class Parser implements ParserInterface
 {
     private Tokenizer $tokenizer;
 
-    public function __construct(Tokenizer $tokenizer = null)
+    public function __construct(?Tokenizer $tokenizer = null)
     {
         $this->tokenizer = $tokenizer ?? new Tokenizer();
     }
index 83e855b5c41589fc3889c36903eb21e25800ee0a..9e66ce7ddbd08a3190a053104c9fd04dbdc8dfec 100644 (file)
@@ -48,7 +48,7 @@ class Translator implements TranslatorInterface
     private array $pseudoClassTranslators = [];
     private array $attributeMatchingTranslators = [];
 
-    public function __construct(ParserInterface $parser = null)
+    public function __construct(?ParserInterface $parser = null)
     {
         $this->mainParser = $parser ?? new Parser();
 
index c6d02d874966fe3b7d8f9180389f28727dfb8c23..ceb6c07961052fa006e7f92ba8948d5e5d8ff610 100644 (file)
@@ -25,7 +25,7 @@
     "minimum-stability": "dev",
     "extra": {
         "branch-alias": {
-            "dev-main": "3.4-dev"
+            "dev-main": "3.5-dev"
         },
         "thanks": {
             "name": "symfony/contracts",
index fcd1281a979008571ff370adeade944172c07651..91da117f954e586ea9095a8627e63546db811c24 100644 (file)
@@ -174,6 +174,42 @@ class Php82
 
                 return 0;
             }
+
+            $digits_consumed = $digits;
+            /* Ignore leading whitespace. */
+            while ($digits_consumed < $str_end && false !== strpos($ctype_space, $value[$digits_consumed])) {
+                ++$digits_consumed;
+            }
+            if ($digits_consumed !== $str_end && ('+' === $value[$digits_consumed] || '-' === $value[$digits_consumed])) {
+                ++$digits_consumed;
+            }
+
+            if ('0' === $value[$digits_consumed]) {
+                /* Value is just 0 */
+                if ($digits_consumed + 1 === $str_end) {
+                    goto evaluation;
+                }
+                switch ($value[$digits_consumed + 1]) {
+                    case 'x':
+                    case 'X':
+                    case 'o':
+                    case 'O':
+                    case 'b':
+                    case 'B':
+                        $digits_consumed += 2;
+                        break;
+                }
+            }
+
+            if ($digits !== $digits_consumed) {
+                $message = sprintf(
+                    'Invalid quantity "%s": no digits after base prefix, interpreting as "0" for backwards compatibility',
+                    self::escapeString($value)
+                );
+                trigger_error($message, \E_USER_WARNING);
+
+                return 0;
+            }
         }
 
         evaluation:
@@ -197,16 +233,6 @@ class Php82
 
         $digits_end = $digits;
 
-        // The native function treats 0x0x123 the same as 0x123.  This is a bug which we must replicate.
-        if (
-            16 === $base
-            && $digits_end + 2 < $str_end
-            && '0x' === substr($value, $digits_end, 2)
-            && false !== strpos($allowed_digits, $value[$digits_end + 2])
-        ) {
-            $digits_end += 2;
-        }
-
         while ($digits_end < $str_end && false !== strpos($allowed_digits, $value[$digits_end])) {
             ++$digits_end;
         }
index f875f3947ca1cfb8ec802fe487274781b9a2baee..399504dcbb13fb93c443f7a8575f29f6c954880d 100644 (file)
@@ -15,20 +15,18 @@ if (\PHP_VERSION_ID >= 80200) {
     return;
 }
 
-if (!extension_loaded('odbc')) {
-    return;
-}
-
-if (!function_exists('odbc_connection_string_is_quoted')) {
-    function odbc_connection_string_is_quoted(string $str): bool { return p\Php82::odbc_connection_string_is_quoted($str); }
-}
-
-if (!function_exists('odbc_connection_string_should_quote')) {
-    function odbc_connection_string_should_quote(string $str): bool { return p\Php82::odbc_connection_string_should_quote($str); }
-}
-
-if (!function_exists('odbc_connection_string_quote')) {
-    function odbc_connection_string_quote(string $str): string { return p\Php82::odbc_connection_string_quote($str); }
+if (extension_loaded('odbc')) {
+    if (!function_exists('odbc_connection_string_is_quoted')) {
+        function odbc_connection_string_is_quoted(string $str): bool { return p\Php82::odbc_connection_string_is_quoted($str); }
+    }
+
+    if (!function_exists('odbc_connection_string_should_quote')) {
+        function odbc_connection_string_should_quote(string $str): bool { return p\Php82::odbc_connection_string_should_quote($str); }
+    }
+
+    if (!function_exists('odbc_connection_string_quote')) {
+        function odbc_connection_string_quote(string $str): string { return p\Php82::odbc_connection_string_quote($str); }
+    }
 }
 
 if (!function_exists('ini_parse_quantity')) {
index e0422658ac9b885dfeefb26d2cc3653ad16f33d2..e1f8230c8eae304e36e9fc2276a999e5307e7b90 100644 (file)
@@ -25,9 +25,6 @@
     },
     "minimum-stability": "dev",
     "extra": {
-        "branch-alias": {
-            "dev-main": "1.28-dev"
-        },
         "thanks": {
             "name": "symfony/polyfill",
             "url": "https://github.com/symfony/polyfill"
index 23a9cf389003d72c24948c8099641213b2dfa401..3d94b6c324fde72eb0145de61e7e3009a7597b3e 100644 (file)
@@ -13,6 +13,7 @@ namespace Symfony\Polyfill\Php83;
 
 /**
  * @author Ion Bazan <ion.bazan@gmail.com>
+ * @author Pierre Ambroise <pierre27.ambroise@gmail.com>
  *
  * @internal
  */
@@ -39,7 +40,7 @@ final class Php83
         return \JSON_ERROR_NONE === json_last_error();
     }
 
-    public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, string $encoding = null): string
+    public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
     {
         if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
             throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
@@ -82,4 +83,115 @@ final class Php83
                 return mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
         }
     }
+
+    public static function str_increment(string $string): string
+    {
+        if ('' === $string) {
+            throw new \ValueError('str_increment(): Argument #1 ($string) cannot be empty');
+        }
+
+        if (!preg_match('/^[a-zA-Z0-9]+$/', $string)) {
+            throw new \ValueError('str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters');
+        }
+
+        if (is_numeric($string)) {
+            $offset = stripos($string, 'e');
+            if (false !== $offset) {
+                $char = $string[$offset];
+                ++$char;
+                $string[$offset] = $char;
+                ++$string;
+
+                switch ($string[$offset]) {
+                    case 'f':
+                        $string[$offset] = 'e';
+                        break;
+                    case 'F':
+                        $string[$offset] = 'E';
+                        break;
+                    case 'g':
+                        $string[$offset] = 'f';
+                        break;
+                    case 'G':
+                        $string[$offset] = 'F';
+                        break;
+                }
+
+                return $string;
+            }
+        }
+
+        return ++$string;
+    }
+
+    public static function str_decrement(string $string): string
+    {
+        if ('' === $string) {
+            throw new \ValueError('str_decrement(): Argument #1 ($string) cannot be empty');
+        }
+
+        if (!preg_match('/^[a-zA-Z0-9]+$/', $string)) {
+            throw new \ValueError('str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters');
+        }
+
+        if (preg_match('/\A(?:0[aA0]?|[aA])\z/', $string)) {
+            throw new \ValueError(sprintf('str_decrement(): Argument #1 ($string) "%s" is out of decrement range', $string));
+        }
+
+        if (!\in_array(substr($string, -1), ['A', 'a', '0'], true)) {
+            return implode('', \array_slice(str_split($string), 0, -1)).\chr(\ord(substr($string, -1)) - 1);
+        }
+
+        $carry = '';
+        $decremented = '';
+
+        for ($i = \strlen($string) - 1; $i >= 0; --$i) {
+            $char = $string[$i];
+
+            switch ($char) {
+                case 'A':
+                    if ('' !== $carry) {
+                        $decremented = $carry.$decremented;
+                        $carry = '';
+                    }
+                    $carry = 'Z';
+
+                    break;
+                case 'a':
+                    if ('' !== $carry) {
+                        $decremented = $carry.$decremented;
+                        $carry = '';
+                    }
+                    $carry = 'z';
+
+                    break;
+                case '0':
+                    if ('' !== $carry) {
+                        $decremented = $carry.$decremented;
+                        $carry = '';
+                    }
+                    $carry = '9';
+
+                    break;
+                case '1':
+                    if ('' !== $carry) {
+                        $decremented = $carry.$decremented;
+                        $carry = '';
+                    }
+
+                    break;
+                default:
+                    if ('' !== $carry) {
+                        $decremented = $carry.$decremented;
+                        $carry = '';
+                    }
+
+                    if (!\in_array($char, ['A', 'a', '0'], true)) {
+                        $decremented = \chr(\ord($char) - 1).$decremented;
+                    }
+            }
+        }
+
+        return $decremented;
+    }
 }
index 92516e7a45012a59c9e5dc194498dafbbc24fd21..f298776814d238dc23238ef5baad85b01d3b0630 100644 (file)
@@ -9,7 +9,9 @@ This component provides features added to PHP 8.3 core:
 - [`ldap_exop_sync`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures)
 - [`ldap_connect_wallet`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures)
 - [`stream_context_set_options`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures)
+- [`str_increment` and `str_decrement`](https://wiki.php.net/rfc/saner-inc-dec-operators)
 - [`Date*Exception/Error classes`](https://wiki.php.net/rfc/datetime-exceptions)
+- [`SQLite3Exception`](https://wiki.php.net/rfc/sqlite3_exceptions)
 
 More information can be found in the
 [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
diff --git a/wcfsetup/install/files/lib/system/api/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php b/wcfsetup/install/files/lib/system/api/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php
new file mode 100644 (file)
index 0000000..ecb7c98
--- /dev/null
@@ -0,0 +1,16 @@
+<?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.
+ */
+
+if (\PHP_VERSION_ID < 80300) {
+    class SQLite3Exception extends Exception
+    {
+    }
+}
index b5beb71a6084b117fabbad90d24cc6d310f084d9..f43af17e0e6a72495df05ae82fc43982eccedf51 100644 (file)
@@ -27,12 +27,20 @@ if (!function_exists('stream_context_set_options')) {
     function stream_context_set_options($context, array $options): bool { return stream_context_set_option($context, $options); }
 }
 
+if (!function_exists('str_increment')) {
+    function str_increment(string $string): string { return p\Php83::str_increment($string); }
+}
+
+if (!function_exists('str_decrement')) {
+    function str_decrement(string $string): string { return p\Php83::str_decrement($string); }
+}
+
 if (\PHP_VERSION_ID >= 80100) {
     return require __DIR__.'/bootstrap81.php';
 }
 
 if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
-    function ldap_exop_sync($ldap, string $request_oid, string $request_data = null, array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
+    function ldap_exop_sync($ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
 }
 
 if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) {
index 63a4f7800811958ad8010fd05ef62bcdc2b3e8c3..68395b439d6507197f372fa92cc4189382bb672a 100644 (file)
@@ -14,7 +14,7 @@ if (\PHP_VERSION_ID >= 80300) {
 }
 
 if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
-    function ldap_exop_sync(\LDAP\Connection $ldap, string $request_oid, string $request_data = null, array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
+    function ldap_exop_sync(\LDAP\Connection $ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
 }
 
 if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) {
index 51c81b16a2e0da4d46bd7355f6c88daf9fbf2c50..02a0bf8301f016bb78663c25acf183c74632e6e6 100644 (file)
@@ -16,8 +16,7 @@
         }
     ],
     "require": {
-        "php": ">=7.1",
-        "symfony/polyfill-php80": "^1.14"
+        "php": ">=7.1"
     },
     "autoload": {
         "psr-4": { "Symfony\\Polyfill\\Php83\\": "" },
@@ -26,9 +25,6 @@
     },
     "minimum-stability": "dev",
     "extra": {
-        "branch-alias": {
-            "dev-main": "1.28-dev"
-        },
         "thanks": {
             "name": "symfony/polyfill",
             "url": "https://github.com/symfony/polyfill"