Failed Conditions
Push — master ( 2b713c...735d0a )
by Alex
16s queued 13s
created

ExpressionTypeChecker::tryAssertPrimitiveAsType()   C

Complexity

Conditions 12
Paths 12

Size

Total Lines 56
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 45
nc 12
nop 3
dl 0
loc 56
rs 6.9666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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