ExpressionTypeChecker::testNullabilityMatch()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 2
nop 4
dl 0
loc 21
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AlgoWeb\ODataMetadata\Util;
6
7
use AlgoWeb\ODataMetadata\Edm\Validation\EdmError;
8
use AlgoWeb\ODataMetadata\Edm\Validation\EdmErrorCode;
9
use AlgoWeb\ODataMetadata\EdmUtil;
10
use AlgoWeb\ODataMetadata\Enums\ExpressionKind;
11
use AlgoWeb\ODataMetadata\Enums\PrimitiveTypeKind;
12
use AlgoWeb\ODataMetadata\Enums\ValueKind;
13
use AlgoWeb\ODataMetadata\Helpers\EdmElementComparer;
14
use AlgoWeb\ODataMetadata\Helpers\ToTraceString;
15
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IApplyExpression;
16
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IAssertTypeExpression;
17
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IBinaryConstantExpression;
18
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IBooleanConstantExpression;
19
use AlgoWeb\ODataMetadata\Interfaces\Expressions\ICollectionExpression;
20
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IDateTimeConstantExpression;
21
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IDateTimeOffsetConstantExpression;
22
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IDecimalConstantExpression;
23
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IExpression;
24
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IFloatingConstantExpression;
25
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IGuidConstantExpression;
26
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IIfExpression;
27
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IIntegerConstantExpression;
28
use AlgoWeb\ODataMetadata\Interfaces\Expressions\ILabeledExpression;
29
use AlgoWeb\ODataMetadata\Interfaces\Expressions\ILabeledExpressionReferenceExpression;
30
use AlgoWeb\ODataMetadata\Interfaces\Expressions\INullExpression;
31
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IPathExpression;
32
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IRecordExpression;
33
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IStringConstantExpression;
34
use AlgoWeb\ODataMetadata\Interfaces\Expressions\ITimeConstantExpression;
35
use AlgoWeb\ODataMetadata\Interfaces\IFunctionBase;
36
use AlgoWeb\ODataMetadata\Interfaces\ILocation;
37
use AlgoWeb\ODataMetadata\Interfaces\IPrimitiveType;
38
use AlgoWeb\ODataMetadata\Interfaces\IStructuredType;
39
use AlgoWeb\ODataMetadata\Interfaces\IType;
40
use AlgoWeb\ODataMetadata\Interfaces\ITypeReference;
41
use AlgoWeb\ODataMetadata\Interfaces\Values\IPrimitiveValue;
42
use AlgoWeb\ODataMetadata\Library\Core\EdmCoreModel;
43
use AlgoWeb\ODataMetadata\StringConst;
44
use AlgoWeb\ODataMetadata\Structure\HashSetInternal;
45
46
/**
47
 * Collection of methods to assert that an expression is of the required type.
48
 *
49
 * @package AlgoWeb\ODataMetadata\Util
50
 */
51
abstract class ExpressionTypeChecker
52
{
53
    private static $promotionMap = null;
54
55
    private static function getPromotionMap(): array
56
    {
57
        return self::$promotionMap ?? self::$promotionMap = [
58
            PrimitiveTypeKind::Byte()->getValue() => [
59
                PrimitiveTypeKind::Int16()->getValue() => true,
60
                PrimitiveTypeKind::Int32()->getValue() => true,
61
                PrimitiveTypeKind::Int64()->getValue() => true,
62
            ],
63
            PrimitiveTypeKind::SByte()->getValue() => [
64
                PrimitiveTypeKind::Int16()->getValue() => true,
65
                PrimitiveTypeKind::Int32()->getValue() => true,
66
                PrimitiveTypeKind::Int64()->getValue() => true,
67
            ],
68
            PrimitiveTypeKind::Int16()->getValue() => [
69
                PrimitiveTypeKind::Int32()->getValue() => true,
70
                PrimitiveTypeKind::Int64()->getValue() => true,
71
            ],
72
            PrimitiveTypeKind::Int32()->getValue() => [
73
                PrimitiveTypeKind::Int64()->getValue() => true,
74
            ],
75
            PrimitiveTypeKind::Single()->getValue() => [
76
                PrimitiveTypeKind::Double()->getValue() => true,
77
            ],
78
            PrimitiveTypeKind::GeographyCollection()->getValue() => [
79
                PrimitiveTypeKind::Geography()->getValue() => true,
80
            ],
81
            PrimitiveTypeKind::GeographyLineString()->getValue() => [
82
                PrimitiveTypeKind::Geography()->getValue() => true,
83
            ],
84
            PrimitiveTypeKind::GeographyMultiLineString()->getValue() => [
85
                PrimitiveTypeKind::Geography()->getValue() => true,
86
            ],
87
            PrimitiveTypeKind::GeographyMultiPoint()->getValue() => [
88
                PrimitiveTypeKind::Geography()->getValue() => true,
89
            ],
90
            PrimitiveTypeKind::GeographyMultiPolygon()->getValue() => [
91
                PrimitiveTypeKind::Geography()->getValue() => true,
92
            ],
93
            PrimitiveTypeKind::GeographyPoint()->getValue() => [
94
                PrimitiveTypeKind::Geography()->getValue() => true,
95
            ],
96
            PrimitiveTypeKind::GeographyPolygon()->getValue() => [
97
                PrimitiveTypeKind::Geography()->getValue() => true,
98
            ],
99
            PrimitiveTypeKind::GeometryCollection()->getValue() => [
100
                PrimitiveTypeKind::Geometry()->getValue() => true,
101
            ],
102
            PrimitiveTypeKind::GeometryLineString()->getValue() => [
103
                PrimitiveTypeKind::Geometry()->getValue() => true,
104
            ],
105
            PrimitiveTypeKind::GeometryMultiLineString()->getValue() => [
106
                PrimitiveTypeKind::Geometry()->getValue() => true,
107
            ],
108
            PrimitiveTypeKind::GeometryMultiPoint()->getValue() => [
109
                PrimitiveTypeKind::Geometry()->getValue() => true,
110
            ],
111
            PrimitiveTypeKind::GeometryMultiPolygon()->getValue() => [
112
                PrimitiveTypeKind::Geometry()->getValue() => true,
113
            ],
114
            PrimitiveTypeKind::GeometryPoint()->getValue() => [
115
                PrimitiveTypeKind::Geometry()->getValue() => true,
116
            ],
117
            PrimitiveTypeKind::GeometryPolygon()->getValue() => [
118
                PrimitiveTypeKind::Geometry()->getValue() => true,
119
            ],
120
        ];
121
    }
122
123
    /**
124
     * Determines if the type of an expression is compatible with the provided type.
125
     *
126
     * If the expression has an associated type, this function will check that it matches the expected type and stop
127
     * looking further. If an expression claims a type, it must be validated that the type is valid for the expression.
128
     * If the expression does not claim a type this method will attempt to check the validity of the expression itself
129
     * with the asserted type.
130
     *
131
     * @param  IExpression|null    $expression       the expression to assert the type of
132
     * @param  ITypeReference|null $type             the type to assert the expression as
133
     * @param  IType|null          $context          the context paths are to be evaluated in
134
     * @param  bool                $matchExactly     Must the expression must match the asserted type exactly, or simply be compatible?
135
     * @param  iterable            $discoveredErrors errors produced if the expression does not match the specified type
136
     * @return bool                a value indicating whether the expression is valid for the given type or not
137
     */
138
    public static function tryAssertType(
139
        IExpression $expression = null,
140
        ITypeReference $type = null,
141
        IType $context = null,
142
        bool $matchExactly = false,
143
        iterable &$discoveredErrors = []
144
    ): bool {
145
        EdmUtil::checkArgumentNull($expression, 'expression');
146
147
        // If we don't have a type to assert this passes vacuously.
148
        if (null === $type || $type->typeKind()->isNone()) {
149
            $discoveredErrors = [];
150
            return true;
151
        }
152
153
        switch ($expression->/* @scrutinizer ignore-call */getExpressionKind()) {
154
            case ExpressionKind::IntegerConstant():
155
            case ExpressionKind::StringConstant():
156
            case ExpressionKind::BinaryConstant():
157
            case ExpressionKind::BooleanConstant():
158
            case ExpressionKind::DateTimeConstant():
159
            case ExpressionKind::DateTimeOffsetConstant():
160
            case ExpressionKind::DecimalConstant():
161
            case ExpressionKind::FloatingConstant():
162
            case ExpressionKind::GuidConstant():
163
            case ExpressionKind::TimeConstant():
164
                /** @var IPrimitiveValue $primitiveValue */
165
                $primitiveValue = $expression;
166
                assert($primitiveValue instanceof IPrimitiveValue);
167
                if (null !== $primitiveValue->getType()) {
168
                    return self::testTypeReferenceMatch(
169
                        $primitiveValue->getType(),
170
                        $type,
171
                        $expression->/* @scrutinizer ignore-call */ location(),
172
                        $matchExactly,
173
                        $discoveredErrors
174
                    );
175
                }
176
                return self::tryAssertPrimitiveAsType($primitiveValue, $type, $discoveredErrors);
177
            case ExpressionKind::Null():
178
                assert($expression instanceof INullExpression);
179
                return self::tryAssertNullAsType($expression, $type, $discoveredErrors);
180
            case ExpressionKind::Path():
181
                assert($expression instanceof IPathExpression);
182
                EdmUtil::checkArgumentNull($context, 'context');
183
                return self::tryAssertPathAsType($expression, $type, $context, $matchExactly, $discoveredErrors);
184
            case ExpressionKind::FunctionApplication():
185
                /** @var IApplyExpression $applyExpression */
186
                $applyExpression = $expression;
187
                assert($applyExpression instanceof IApplyExpression);
188
                if (null !== $applyExpression->getAppliedFunction()) {
189
                    $function = $applyExpression->getAppliedFunction();
190
                    if (null !== $function && $function instanceof IFunctionBase) {
191
                        EdmUtil::checkArgumentNull($function->getReturnType(), 'function->getReturnType');
192
                        EdmUtil::checkArgumentNull($applyExpression->location(), 'expression->Location');
193
                        return self::testTypeReferenceMatch(
194
                            $function->getReturnType(),
195
                            $type,
196
                            $applyExpression->location(),
197
                            $matchExactly,
198
                            $discoveredErrors
199
                        );
200
                    }
201
                }
202
203
                // If we don't have the applied function we just assume that it will work.
204
                $discoveredErrors = [];
205
                return true;
206
            case ExpressionKind::If():
207
                assert($expression instanceof IIfExpression);
208
                return self::tryAssertIfAsType($expression, $type, $context, $matchExactly, $discoveredErrors);
0 ignored issues
show
Bug introduced by
It seems like $context can also be of type null; however, parameter $context of AlgoWeb\ODataMetadata\Ut...er::tryAssertIfAsType() does only seem to accept AlgoWeb\ODataMetadata\Interfaces\IType, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

208
                return self::tryAssertIfAsType($expression, $type, /** @scrutinizer ignore-type */ $context, $matchExactly, $discoveredErrors);
Loading history...
209
            case ExpressionKind::IsType():
210
                $coreModel = EdmCoreModel::getInstance();
211
                $boolean   = $coreModel->getBoolean(false);
212
                EdmUtil::checkArgumentNull($expression->location(), 'expression->Location');
213
                return self::testTypeReferenceMatch(
214
                    $boolean,
215
                    $type,
216
                    $expression->location(),
217
                    $matchExactly,
218
                    $discoveredErrors
219
                );
220
            case ExpressionKind::Record():
221
                /** @var IRecordExpression $recordExpression */
222
                $recordExpression = $expression;
223
                assert($recordExpression instanceof IRecordExpression);
224
                if (null !== $recordExpression->getDeclaredType()) {
225
                    return self::testTypeReferenceMatch(
226
                        $recordExpression->getDeclaredType(),
227
                        $type,
228
                        $expression->location(),
229
                        $matchExactly,
230
                        $discoveredErrors
231
                    );
232
                }
233
234
                return self::tryAssertRecordAsType(
235
                    $recordExpression,
236
                    $type,
237
                    $context,
238
                    $matchExactly,
239
                    $discoveredErrors
240
                );
241
            case ExpressionKind::Collection():
242
                /** @var ICollectionExpression $collectionExpression */
243
                $collectionExpression = $expression;
244
                EdmUtil::checkArgumentNull($context, 'context');
245
                assert($collectionExpression instanceof ICollectionExpression);
246
                if (null !== $collectionExpression->getDeclaredType()) {
247
                    return self::testTypeReferenceMatch(
248
                        $collectionExpression->getDeclaredType(),
249
                        $type,
250
                        $expression->location(),
251
                        $matchExactly,
252
                        $discoveredErrors
253
                    );
254
                }
255
256
                return self::tryAssertCollectionAsType(
257
                    $collectionExpression,
258
                    $type,
259
                    $context,
260
                    $matchExactly,
261
                    $discoveredErrors
262
                );
263
            case ExpressionKind::Labeled():
264
                assert($expression instanceof ILabeledExpression);
265
                return self::tryAssertType(
266
                    $expression->getExpression(),
267
                    $type,
268
                    $context,
269
                    $matchExactly,
270
                    $discoveredErrors
271
                );
272
            case ExpressionKind::AssertType():
273
                assert($expression instanceof IAssertTypeExpression);
274
                return self::testTypeReferenceMatch(
275
                    $expression->getType(),
276
                    $type,
277
                    $expression->location(),
278
                    $matchExactly,
279
                    $discoveredErrors
280
                );
281
            case ExpressionKind::LabeledExpressionReference():
282
                assert($expression instanceof ILabeledExpressionReferenceExpression);
283
                return self::tryAssertType(
284
                    $expression->getReferencedLabeledExpression(),
285
                    $type,
286
                    null,
287
                    false,
288
                    $discoveredErrors
289
                );
290
            default:
291
                $discoveredErrors = [
292
                    new EdmError(
293
                        $expression->location(),
294
                        EdmErrorCode::ExpressionNotValidForTheAssertedType(),
295
                        StringConst::EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType()
296
                    )
297
                ];
298
                return false;
299
        }
300
    }
301
302
    public static function tryAssertPrimitiveAsType(
303
        IPrimitiveValue $expression,
304
        ITypeReference $type,
305
        iterable &$discoveredErrors
306
    ): bool {
307
        if (!$type->isPrimitive()) {
308
            $discoveredErrors =  [
309
                new EdmError(
310
                    $expression->location(),
311
                    EdmErrorCode::PrimitiveConstantExpressionNotValidForNonPrimitiveType(),
312
                    StringConst::EdmModel_Validator_Semantic_PrimitiveConstantExpressionNotValidForNonPrimitiveType()
313
                )
314
            ];
315
            return false;
316
        }
317
318
        switch ($expression->getValueKind()) {
319
            case ValueKind::Binary():
320
                assert($expression instanceof IBinaryConstantExpression);
321
                return self::tryAssertBinaryConstantAsType($expression, $type, $discoveredErrors);
322
            case ValueKind::Boolean():
323
                assert($expression instanceof IBooleanConstantExpression);
324
                return self::tryAssertBooleanConstantAsType($expression, $type, $discoveredErrors);
325
            case ValueKind::DateTime():
326
                assert($expression instanceof IDateTimeConstantExpression);
327
                return self::tryAssertDateTimeConstantAsType($expression, $type, $discoveredErrors);
328
            case ValueKind::DateTimeOffset():
329
                assert($expression instanceof IDateTimeOffsetConstantExpression);
330
                return self::tryAssertDateTimeOffsetConstantAsType($expression, $type, $discoveredErrors);
331
            case ValueKind::Decimal():
332
                assert($expression instanceof IDecimalConstantExpression);
333
                return self::tryAssertDecimalConstantAsType($expression, $type, $discoveredErrors);
334
            case ValueKind::Floating():
335
                assert($expression instanceof IFloatingConstantExpression);
336
                return self::tryAssertFloatingConstantAsType($expression, $type, $discoveredErrors);
337
            case ValueKind::Guid():
338
                assert($expression instanceof IGuidConstantExpression);
339
                return self::tryAssertGuidConstantAsType($expression, $type, $discoveredErrors);
340
            case ValueKind::Integer():
341
                assert($expression instanceof IIntegerConstantExpression);
342
                return self::tryAssertIntegerConstantAsType($expression, $type, $discoveredErrors);
343
            case ValueKind::String():
344
                assert($expression instanceof IStringConstantExpression);
345
                return self::tryAssertStringConstantAsType($expression, $type, $discoveredErrors);
346
            case ValueKind::Time():
347
                assert($expression instanceof ITimeConstantExpression);
348
                return self::tryAssertTimeConstantAsType($expression, $type, $discoveredErrors);
349
            default:
350
                $discoveredErrors = [
351
                    new EdmError(
352
                        $expression->location(),
353
                        EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
354
                        StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
355
                    )
356
                ];
357
                return false;
358
        }
359
    }
360
361
    protected static function tryAssertNullAsType(
362
        INullExpression $expression,
363
        ITypeReference $type,
364
        iterable &$discoveredErrors
365
    ): bool {
366
        if (!$type->getNullable()) {
367
            $discoveredErrors = [
368
                new EdmError(
369
                    $expression->location(),
370
                    EdmErrorCode::NullCannotBeAssertedToBeANonNullableType(),
371
                    StringConst::EdmModel_Validator_Semantic_NullCannotBeAssertedToBeANonNullableType()
372
                )
373
            ];
374
            return false;
375
        }
376
377
        $discoveredErrors = [];
378
        return true;
379
    }
380
381
    protected static function tryAssertPathAsType(
382
        IPathExpression $expression,
383
        ITypeReference $type,
384
        IType $context,
385
        bool $matchExactly,
386
        iterable &$discoveredErrors
387
    ): bool {
388
        $structuredContext = $context;
389
        assert($structuredContext instanceof IStructuredType);
390
391
        $result = $context;
392
        $loc    = $expression->location();
393
        EdmUtil::checkArgumentNull($loc, 'expression->Location');
394
        EdmUtil::checkArgumentNull($type->getDefinition(), 'type->getDefinition');
395
396
        foreach ($expression->getPath() as $segment) {
397
            $structuredResult = $result;
398
            if (!$structuredResult instanceof IStructuredType) {
399
                $discoveredErrors = [
400
                    new EdmError(
401
                        $loc,
402
                        EdmErrorCode::PathIsNotValidForTheGivenContext(),
403
                        StringConst::EdmModel_Validator_Semantic_PathIsNotValidForTheGivenContext($segment)
404
                    )
405
                ];
406
                return false;
407
            }
408
409
            $resultProperty = $structuredResult->findProperty($segment);
410
            $result         = (null !== $resultProperty) ? $resultProperty->getType()->getDefinition() : null;
411
412
            // If the path is not resolved, it could refer to an open type, and we can't assert its type.
413
            if (null === $result) {
414
                $discoveredErrors = [];
415
                return true;
416
            }
417
        }
418
419
        return self::testTypeMatch(
420
            $result,
421
            $type->getDefinition(),
422
            $loc,
423
            $matchExactly,
424
            $discoveredErrors
425
        );
426
    }
427
428
    protected static function tryAssertIfAsType(
429
        IIfExpression $expression,
430
        ITypeReference $type,
431
        IType $context,
432
        bool $matchExactly,
433
        &$discoveredErrors
434
    ): bool {
435
        $ifTrueErrors  = [];
436
        $ifFalseErrors = [];
437
        $success       = self::tryAssertType($expression->getTrueExpression(), $type, $context, $matchExactly, $ifTrueErrors);
438
        $success &= self::tryAssertType(
439
            $expression->getFalseExpression(),
440
            $type,
441
            $context,
442
            $matchExactly,
443
            $ifFalseErrors
444
        );
445
        if (!$success) {
446
            $discoveredErrors = array_merge($ifTrueErrors, $ifFalseErrors);
447
        } else {
448
            $discoveredErrors = [];
449
        }
450
451
        return boolval($success);
452
    }
453
454
    public static function tryAssertRecordAsType(
455
        IRecordExpression $expression,
456
        ITypeReference $type,
457
        ?IType $context,
458
        bool $matchExactly,
459
        iterable &$discoveredErrors
460
    ): bool {
461
        EdmUtil::checkArgumentNull($expression, 'expression');
462
        EdmUtil::checkArgumentNull($type, 'type');
463
464
        if (!$type->isStructured()) {
465
            $discoveredErrors = [
466
                new EdmError(
467
                    $expression->location(),
468
                    EdmErrorCode::RecordExpressionNotValidForNonStructuredType(),
469
                    StringConst::EdmModel_Validator_Semantic_RecordExpressionNotValidForNonStructuredType()
470
                )
471
            ];
472
            return false;
473
        }
474
475
        $foundProperties = new HashSetInternal();
476
        $errors          = [];
477
478
        $structuredType = $type->asStructured();
479
        $definition     = $structuredType->getDefinition();
480
        assert($definition instanceof IStructuredType);
481
        foreach ($definition->properties() as $typeProperty) {
482
            $expressionProperty = null;
483
            foreach ($expression->getProperties() as $p) {
484
                if ($p->getName() === $typeProperty->getName()) {
485
                    $expressionProperty = $p;
486
                    break;
487
                }
488
            }
489
            if (null === $expressionProperty) {
490
                $errors[] = new EdmError(
491
                    $expression->location(),
492
                    EdmErrorCode::RecordExpressionMissingRequiredProperty(),
493
                    StringConst::EdmModel_Validator_Semantic_RecordExpressionMissingProperty($typeProperty->getName())
494
                );
495
            } else {
496
                $recursiveErrors = [];
497
                if (!self::tryAssertType(
498
                    $expressionProperty->getValue(),
499
                    $typeProperty->getType(),
500
                    $context,
501
                    $matchExactly,
502
                    $recursiveErrors
503
                )) {
504
                    foreach ($recursiveErrors as $error) {
505
                        $errors[] = $error;
506
                    }
507
                }
508
509
                $foundProperties[] = $typeProperty->getName();
510
            }
511
        }
512
        $definition = $structuredType->getDefinition();
513
        assert($definition instanceof IStructuredType);
514
        if (!$definition->isOpen()) {
515
            foreach ($expression->getProperties() as $property) {
516
                if (!$foundProperties->contains($property->getName())) {
517
                    $errors[] = new EdmError(
518
                        $expression->location(),
519
                        EdmErrorCode::RecordExpressionHasExtraProperties(),
520
                        StringConst::EdmModel_Validator_Semantic_RecordExpressionHasExtraProperties(
521
                            $property->getName()
522
                        )
523
                    );
524
                }
525
            }
526
        }
527
528
        if (count($errors) > 0 || $errors[0]) {
529
            $discoveredErrors = $errors;
530
            return false;
531
        }
532
533
        $discoveredErrors = [];
534
        return true;
535
    }
536
537
    public static function tryAssertCollectionAsType(
538
        ICollectionExpression $expression,
539
        ITypeReference $type,
540
        IType $context,
541
        bool $matchExactly,
542
        &$discoveredErrors
543
    ): bool {
544
        if (!$type->isCollection()) {
545
            $discoveredErrors = [
546
                new EdmError(
547
                    $expression->location(),
548
                    EdmErrorCode::CollectionExpressionNotValidForNonCollectionType(),
549
                    StringConst::EdmModel_Validator_Semantic_CollectionExpressionNotValidForNonCollectionType()
550
                )
551
            ];
552
            return false;
553
        }
554
555
        $collectionElementType = $type->asCollection()->elementType();
556
        $success               = true;
557
        $errors                = [];
558
        $recursiveErrors       = [];
559
        foreach ($expression->getElements() as $element) {
560
            $result = self::tryAssertType(
561
                $element,
562
                $collectionElementType,
563
                $context,
564
                $matchExactly,
565
                $recursiveErrors
566
            );
567
            $success &= boolval($result);
568
            $errors = array_merge($errors, $recursiveErrors);
569
        }
570
571
        $discoveredErrors = $errors;
572
        return boolval($success);
573
    }
574
575
    private static function tryAssertGuidConstantAsType(
576
        IGuidConstantExpression $expression,
577
        ITypeReference $type,
578
        &$discoveredErrors
579
    ): bool {
580
        if (!$type->isGuid()) {
581
            $discoveredErrors = [
582
                new EdmError(
583
                    $expression->location(),
584
                    EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
585
                    StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
586
                )
587
            ];
588
            return false;
589
        }
590
591
        $discoveredErrors = [];
592
        return true;
593
    }
594
595
    private static function tryAssertFloatingConstantAsType(
596
        IFloatingConstantExpression $expression,
597
        ITypeReference $type,
598
        &$discoveredErrors
599
    ): bool {
600
        if (!$type->isFloating()) {
601
            $discoveredErrors = [
602
                new EdmError(
603
                    $expression->location(),
604
                    EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
605
                    StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
606
                )
607
            ];
608
            return false;
609
        }
610
611
        $discoveredErrors = [];
612
        return true;
613
    }
614
615
    private static function tryAssertDecimalConstantAsType(
616
        IDecimalConstantExpression $expression,
617
        ITypeReference $type,
618
        &$discoveredErrors
619
    ): bool {
620
        if (!$type->isDecimal()) {
621
            $discoveredErrors = [
622
                new EdmError(
623
                    $expression->location(),
624
                    EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
625
                    StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
626
                )
627
            ];
628
            return false;
629
        }
630
631
        $discoveredErrors = [];
632
        return true;
633
    }
634
635
    private static function tryAssertDateTimeOffsetConstantAsType(
636
        IDateTimeOffsetConstantExpression $expression,
637
        ITypeReference $type,
638
        &$discoveredErrors
639
    ): bool {
640
        if (!$type->isDateTimeOffset()) {
641
            $discoveredErrors = [
642
                new EdmError(
643
                    $expression->location(),
644
                    EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
645
                    StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
646
                )
647
            ];
648
            return false;
649
        }
650
651
        $discoveredErrors = [];
652
        return true;
653
    }
654
655
    private static function tryAssertDateTimeConstantAsType(
656
        IDateTimeConstantExpression $expression,
657
        ITypeReference $type,
658
        &$discoveredErrors
659
    ): bool {
660
        if (!$type->isDateTime()) {
661
            $discoveredErrors = [
662
                new EdmError(
663
                    $expression->location(),
664
                    EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
665
                    StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
666
                )
667
            ];
668
            return false;
669
        }
670
671
        $discoveredErrors = [];
672
        return true;
673
    }
674
675
    private static function tryAssertTimeConstantAsType(
676
        ITimeConstantExpression $expression,
677
        ITypeReference $type,
678
        &$discoveredErrors
679
    ): bool {
680
        if (!$type->isTime()) {
681
            $discoveredErrors = [
682
                new EdmError(
683
                    $expression->location(),
684
                    EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
685
                    StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
686
                )
687
            ];
688
            return false;
689
        }
690
691
        $discoveredErrors = [];
692
        return true;
693
    }
694
695
    private static function tryAssertBooleanConstantAsType(
696
        IBooleanConstantExpression $expression,
697
        ITypeReference $type,
698
        &$discoveredErrors
699
    ): bool {
700
        if (!$type->isBoolean()) {
701
            $discoveredErrors = [
702
                new EdmError(
703
                    $expression->location(),
704
                    EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
705
                    StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
706
                )
707
            ];
708
            return false;
709
        }
710
711
        $discoveredErrors = [];
712
        return true;
713
    }
714
715
    private static function tryAssertStringConstantAsType(
716
        IStringConstantExpression $expression,
717
        ITypeReference $type,
718
        &$discoveredErrors
719
    ) {
720
        if (!$type->isString()) {
721
            $discoveredErrors = [
722
                new EdmError(
723
                    $expression->location(),
724
                    EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
725
                    StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
726
                )
727
            ];
728
            return false;
729
        }
730
731
        EdmUtil::checkArgumentNull($expression->getValue(), 'expression->getValue');
732
        $stringType = $type->asString();
733
        if (null !== $stringType->getMaxLength() && mb_strlen($expression->getValue()) > $stringType->getMaxLength()) {
734
            $discoveredErrors = [
735
                new EdmError(
736
                    $expression->location(),
737
                    EdmErrorCode::StringConstantLengthOutOfRange(),
738
                    StringConst::EdmModel_Validator_Semantic_StringConstantLengthOutOfRange(
739
                        mb_strlen($expression->getValue()),
740
                        $stringType->getMaxLength()
741
                    )
742
                )
743
            ];
744
            return false;
745
        }
746
747
        $discoveredErrors = [];
748
        return true;
749
    }
750
751
    private static function tryAssertIntegerConstantAsType(
752
        IIntegerConstantExpression $expression,
753
        ITypeReference $type,
754
        &$discoveredErrors
755
    ): bool {
756
        if (!$type->isIntegral()) {
757
            $discoveredErrors = [
758
                new EdmError(
759
                    $expression->location(),
760
                    EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
761
                    StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
762
                )
763
            ];
764
            return false;
765
        }
766
767
        switch ($type->primitiveKind()) {
768
            case PrimitiveTypeKind::Int64():
769
                return self::tryAssertIntegerConstantInRange(
770
                    $expression,
771
                    (int)-9223372036854775808,
772
                    (int)9223372036854775807,
773
                    $discoveredErrors
774
                );
775
            case PrimitiveTypeKind::Int32():
776
                return self::tryAssertIntegerConstantInRange(
777
                    $expression,
778
                    -2147483648,
779
                    2147483647,
780
                    $discoveredErrors
781
                );
782
            case PrimitiveTypeKind::Int16():
783
                return self::tryAssertIntegerConstantInRange(
784
                    $expression,
785
                    -32768,
786
                    32767,
787
                    $discoveredErrors
788
                );
789
            case PrimitiveTypeKind::Byte():
790
                return self::tryAssertIntegerConstantInRange(
791
                    $expression,
792
                    0,
793
                    255,
794
                    $discoveredErrors
795
                );
796
            case PrimitiveTypeKind::SByte():
797
                return self::tryAssertIntegerConstantInRange(
798
                    $expression,
799
                    -128,
800
                    127,
801
                    $discoveredErrors
802
                );
803
            default:
804
                $discoveredErrors =  [
805
                    new EdmError(
806
                        $expression->location(),
807
                        EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
808
                        StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
809
                    )
810
                ];
811
                return false;
812
        }
813
    }
814
815
    private static function tryAssertIntegerConstantInRange(
816
        IIntegerConstantExpression $expression,
817
        int $min,
818
        int $max,
819
        &$discoveredErrors
820
    ): bool {
821
        if ($expression->getValue() < $min || $expression->getValue() > $max) {
822
            $discoveredErrors = [
823
                new EdmError(
824
                    $expression->location(),
825
                    EdmErrorCode::IntegerConstantValueOutOfRange(),
826
                    StringConst::EdmModel_Validator_Semantic_IntegerConstantValueOutOfRange()
827
                )
828
            ];
829
            return false;
830
        }
831
832
        $discoveredErrors = [];
833
        return true;
834
    }
835
836
    private static function tryAssertBinaryConstantAsType(
837
        IBinaryConstantExpression $expression,
838
        ITypeReference $type,
839
        &$discoveredErrors
840
    ): bool {
841
        if (!$type->isBinary()) {
842
            $discoveredErrors = [
843
                new EdmError(
844
                    $expression->location(),
845
                    EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
846
                    StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType()
847
                )
848
            ];
849
            return false;
850
        }
851
852
        EdmUtil::checkArgumentNull($expression->getValue(), 'expression->getValue');
853
        $binaryType = $type->asBinary();
854
        if (null !== $binaryType->getMaxLength() && count($expression->getValue()) > $binaryType->getMaxLength()) {
855
            $discoveredErrors = [
856
                new EdmError(
857
                    $expression->location(),
858
                    EdmErrorCode::BinaryConstantLengthOutOfRange(),
859
                    StringConst::EdmModel_Validator_Semantic_BinaryConstantLengthOutOfRange(
860
                        implode('', $expression->getValue()),
861
                        $binaryType->getMaxLength()
862
                    )
863
                )
864
            ];
865
            return false;
866
        }
867
868
        $discoveredErrors = [];
869
        return true;
870
    }
871
872
    private static function testTypeReferenceMatch(
873
        ITypeReference $expressionType,
874
        ITypeReference $assertedType,
875
        ?ILocation $location,
876
        bool $matchExactly,
877
        &$discoveredErrors
878
    ): bool {
879
        if (!self::testNullabilityMatch($expressionType, $assertedType, $location, $discoveredErrors)) {
880
            return false;
881
        }
882
883
        // A bad type reference matches anything (so as to avoid generating spurious errors).
884
        if (0 !== count($expressionType->getErrors())) {
885
            $discoveredErrors = [];
886
            return true;
887
        }
888
889
        EdmUtil::checkArgumentNull($expressionType->getDefinition(), 'expressionType->getDefinition');
890
        EdmUtil::checkArgumentNull($assertedType->getDefinition(), 'assertedType->getDefinition');
891
892
        return self::testTypeMatch(
893
            $expressionType->getDefinition(),
894
            $assertedType->getDefinition(),
895
            $location,
896
            $matchExactly,
897
            $discoveredErrors
898
        );
899
    }
900
901
    private static function testTypeMatch(
902
        IType $expressionType,
903
        IType $assertedType,
904
        ?ILocation $location,
905
        bool $matchExactly,
906
        &$discoveredErrors
907
    ): bool {
908
        if ($matchExactly) {
909
            if (!EdmElementComparer::isEquivalentTo($expressionType, $assertedType)) {
910
                $discoveredErrors = [
911
                    new EdmError(
912
                        $location,
913
                        EdmErrorCode::ExpressionNotValidForTheAssertedType(),
914
                        StringConst::EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType()
915
                    )
916
                ];
917
                return false;
918
            }
919
        } else {
920
            // A bad type matches anything (so as to avoid generating spurious errors).
921
            if ($expressionType->getTypeKind()->isNone() || 0 !== count($expressionType->getErrors())) {
922
                $discoveredErrors = [];
923
                return true;
924
            }
925
926
            if ($expressionType->getTypeKind()->isPrimitive() && $assertedType->getTypeKind()->isPrimitive()) {
927
                $primitiveExpressionType = $expressionType;
928
                $primitiveAssertedType   = $assertedType ;
929
                assert($primitiveExpressionType instanceof IPrimitiveType);
930
                assert($primitiveAssertedType instanceof IPrimitiveType);
931
                if (!self::promotesTo(
932
                    $primitiveExpressionType->getPrimitiveKind(),
933
                    $primitiveAssertedType->getPrimitiveKind()
934
                )) {
935
                    $discoveredErrors = [
936
                        new EdmError(
937
                            $location,
938
                            EdmErrorCode::ExpressionPrimitiveKindNotValidForAssertedType(),
939
                            StringConst::EdmModel_Validator_Semantic_ExpressionPrimitiveKindCannotPromoteToAssertedType(
940
                                ToTraceString::toTraceString(
941
                                    $expressionType
942
                                ),
943
                                ToTraceString::toTraceString(
944
                                    $assertedType
945
                                )
946
                            )
947
                        )
948
                    ];
949
                    return false;
950
                }
951
            } else {
952
                assert($expressionType instanceof IType);
953
                if (!$expressionType->isOrInheritsFrom($assertedType)) {
954
                    $discoveredErrors = [
955
                        new EdmError(
956
                            $location,
957
                            EdmErrorCode::ExpressionNotValidForTheAssertedType(),
958
                            StringConst::EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType()
959
                        )
960
                    ];
961
                    return false;
962
                }
963
            }
964
        }
965
966
        $discoveredErrors = [];
967
        return true;
968
    }
969
970
    private static function testNullabilityMatch(
971
        ITypeReference $expressionType,
972
        ITypeReference $assertedType,
973
        ?ILocation $location,
974
        &$discoveredErrors
975
    ): bool {
976
        if (!$assertedType->getNullable() && $expressionType->getNullable()) {
977
            $discoveredErrors = [
978
                new EdmError(
979
                    $location,
980
                    EdmErrorCode::CannotAssertNullableTypeAsNonNullableType(),
981
                    StringConst::EdmModel_Validator_Semantic_CannotAssertNullableTypeAsNonNullableType(
982
                        $expressionType->fullName()
983
                    )
984
                )
985
            ];
986
            return false;
987
        }
988
989
        $discoveredErrors = [];
990
        return true;
991
    }
992
993
    private static function promotesTo(PrimitiveTypeKind $startingKind, PrimitiveTypeKind $target): bool
994
    {
995
        $promotionMap = self::getPromotionMap();
996
        return $startingKind === $target ||
997
            (
998
                isset($promotionMap[$startingKind->getValue()]) &&
999
                isset($promotionMap[$startingKind->getValue()][$target->getValue()]) &&
1000
                $promotionMap[$startingKind->getValue()][$target->getValue()]
1001
            );
1002
    }
1003
}
1004