Failed Conditions
Push — master ( 177394...bf4e7d )
by Vladimir
09:19
created

AST::valueFromASTUntyped()   C

Complexity

Conditions 12
Paths 13

Size

Total Lines 44
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 12.0043

Importance

Changes 0
Metric Value
eloc 32
dl 0
loc 44
rs 6.9666
c 0
b 0
f 0
ccs 31
cts 32
cp 0.9688
cc 12
nc 13
nop 2
crap 12.0043

How to fix   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
namespace GraphQL\Utils;
6
7
use ArrayAccess;
8
use Exception;
9
use GraphQL\Error\Error;
10
use GraphQL\Error\InvariantViolation;
11
use GraphQL\Language\AST\BooleanValueNode;
12
use GraphQL\Language\AST\DocumentNode;
13
use GraphQL\Language\AST\EnumValueNode;
14
use GraphQL\Language\AST\FloatValueNode;
15
use GraphQL\Language\AST\IntValueNode;
16
use GraphQL\Language\AST\ListTypeNode;
17
use GraphQL\Language\AST\ListValueNode;
18
use GraphQL\Language\AST\Location;
19
use GraphQL\Language\AST\NamedTypeNode;
20
use GraphQL\Language\AST\NameNode;
21
use GraphQL\Language\AST\Node;
22
use GraphQL\Language\AST\NodeKind;
23
use GraphQL\Language\AST\NodeList;
24
use GraphQL\Language\AST\NonNullTypeNode;
25
use GraphQL\Language\AST\NullValueNode;
26
use GraphQL\Language\AST\ObjectFieldNode;
27
use GraphQL\Language\AST\ObjectValueNode;
28
use GraphQL\Language\AST\OperationDefinitionNode;
29
use GraphQL\Language\AST\StringValueNode;
30
use GraphQL\Language\AST\ValueNode;
31
use GraphQL\Language\AST\VariableNode;
32
use GraphQL\Type\Definition\EnumType;
33
use GraphQL\Type\Definition\IDType;
34
use GraphQL\Type\Definition\InputObjectType;
35
use GraphQL\Type\Definition\InputType;
36
use GraphQL\Type\Definition\ListOfType;
37
use GraphQL\Type\Definition\NonNull;
38
use GraphQL\Type\Definition\ScalarType;
39
use GraphQL\Type\Definition\Type;
40
use GraphQL\Type\Schema;
41
use stdClass;
42
use Throwable;
43
use Traversable;
44
use function array_combine;
45
use function array_key_exists;
46
use function array_map;
47
use function count;
48
use function floatval;
49
use function intval;
50
use function is_array;
51
use function is_bool;
52
use function is_float;
53
use function is_int;
54
use function is_object;
55
use function is_string;
56
use function iterator_to_array;
57
use function property_exists;
58
59
/**
60
 * Various utilities dealing with AST
61
 */
62
class AST
63
{
64
    /**
65
     * Convert representation of AST as an associative array to instance of GraphQL\Language\AST\Node.
66
     *
67
     * For example:
68
     *
69
     * ```php
70
     * AST::fromArray([
71
     *     'kind' => 'ListValue',
72
     *     'values' => [
73
     *         ['kind' => 'StringValue', 'value' => 'my str'],
74
     *         ['kind' => 'StringValue', 'value' => 'my other str']
75
     *     ],
76
     *     'loc' => ['start' => 21, 'end' => 25]
77
     * ]);
78
     * ```
79
     *
80
     * Will produce instance of `ListValueNode` where `values` prop is a lazily-evaluated `NodeList`
81
     * returning instances of `StringValueNode` on access.
82
     *
83
     * This is a reverse operation for AST::toArray($node)
84
     *
85
     * @param mixed[] $node
86
     *
87
     * @api
88
     */
89 2
    public static function fromArray(array $node) : Node
90
    {
91 2
        if (! isset($node['kind']) || ! isset(NodeKind::$classMap[$node['kind']])) {
92
            throw new InvariantViolation('Unexpected node structure: ' . Utils::printSafeJson($node));
93
        }
94
95 2
        $kind     = $node['kind'] ?? null;
96 2
        $class    = NodeKind::$classMap[$kind];
97 2
        $instance = new $class([]);
98
99 2
        if (isset($node['loc'], $node['loc']['start'], $node['loc']['end'])) {
100 1
            $instance->loc = Location::create($node['loc']['start'], $node['loc']['end']);
101
        }
102
103 2
        foreach ($node as $key => $value) {
104 2
            if ($key === 'loc' || $key === 'kind') {
105 2
                continue;
106
            }
107 2
            if (is_array($value)) {
108 2
                if (isset($value[0]) || empty($value)) {
109 2
                    $value = new NodeList($value);
110
                } else {
111 2
                    $value = self::fromArray($value);
112
                }
113
            }
114 2
            $instance->{$key} = $value;
115
        }
116
117 2
        return $instance;
118
    }
119
120
    /**
121
     * Convert AST node to serializable array
122
     *
123
     * @return mixed[]
124
     *
125
     * @api
126
     */
127
    public static function toArray(Node $node)
128
    {
129
        return $node->toArray(true);
130
    }
131
132
    /**
133
     * Produces a GraphQL Value AST given a PHP value.
134
     *
135
     * Optionally, a GraphQL type may be provided, which will be used to
136
     * disambiguate between value primitives.
137
     *
138
     * | PHP Value     | GraphQL Value        |
139
     * | ------------- | -------------------- |
140
     * | Object        | Input Object         |
141
     * | Assoc Array   | Input Object         |
142
     * | Array         | List                 |
143
     * | Boolean       | Boolean              |
144
     * | String        | String / Enum Value  |
145
     * | Int           | Int                  |
146
     * | Float         | Int / Float          |
147
     * | Mixed         | Enum Value           |
148
     * | null          | NullValue            |
149
     *
150
     * @param Type|mixed|null $value
151
     *
152
     * @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode
153
     *
154
     * @api
155
     */
156 27
    public static function astFromValue($value, InputType $type)
157
    {
158 27
        if ($type instanceof NonNull) {
159 4
            $astValue = self::astFromValue($value, $type->getWrappedType());
0 ignored issues
show
Bug introduced by
$type->getWrappedType() of type GraphQL\Type\Definition\Type is incompatible with the type GraphQL\Type\Definition\InputType expected by parameter $type of GraphQL\Utils\AST::astFromValue(). ( Ignorable by Annotation )

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

159
            $astValue = self::astFromValue($value, /** @scrutinizer ignore-type */ $type->getWrappedType());
Loading history...
160 4
            if ($astValue instanceof NullValueNode) {
161 4
                return null;
162
            }
163
164 1
            return $astValue;
165
        }
166
167 27
        if ($value === null) {
168 7
            return new NullValueNode([]);
169
        }
170
171
        // Convert PHP array to GraphQL list. If the GraphQLType is a list, but
172
        // the value is not an array, convert the value using the list's item type.
173 25
        if ($type instanceof ListOfType) {
174 2
            $itemType = $type->getWrappedType();
175 2
            if (is_array($value) || ($value instanceof Traversable)) {
176 1
                $valuesNodes = [];
177 1
                foreach ($value as $item) {
178 1
                    $itemNode = self::astFromValue($item, $itemType);
179 1
                    if (! $itemNode) {
180
                        continue;
181
                    }
182
183 1
                    $valuesNodes[] = $itemNode;
184
                }
185
186 1
                return new ListValueNode(['values' => new NodeList($valuesNodes)]);
187
            }
188
189 1
            return self::astFromValue($value, $itemType);
0 ignored issues
show
Bug introduced by
It seems like $itemType can also be of type GraphQL\Type\Definition\InterfaceType and GraphQL\Type\Definition\ObjectType and GraphQL\Type\Definition\UnionType; however, parameter $type of GraphQL\Utils\AST::astFromValue() does only seem to accept GraphQL\Type\Definition\InputType, 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

189
            return self::astFromValue($value, /** @scrutinizer ignore-type */ $itemType);
Loading history...
190
        }
191
192
        // Populate the fields of the input object by creating ASTs from each value
193
        // in the PHP object according to the fields in the input type.
194 25
        if ($type instanceof InputObjectType) {
195 2
            $isArray     = is_array($value);
196 2
            $isArrayLike = $isArray || $value instanceof ArrayAccess;
197 2
            if ($value === null || (! $isArrayLike && ! is_object($value))) {
198
                return null;
199
            }
200 2
            $fields     = $type->getFields();
201 2
            $fieldNodes = [];
202 2
            foreach ($fields as $fieldName => $field) {
203 2
                if ($isArrayLike) {
204 2
                    $fieldValue = $value[$fieldName] ?? null;
205
                } else {
206 1
                    $fieldValue = $value->{$fieldName} ?? null;
207
                }
208
209
                // Have to check additionally if key exists, since we differentiate between
210
                // "no key" and "value is null":
211 2
                if ($fieldValue !== null) {
212 1
                    $fieldExists = true;
213 1
                } elseif ($isArray) {
214 1
                    $fieldExists = array_key_exists($fieldName, $value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type ArrayAccess and GraphQL\Type\Definition\Type and GraphQL\Type\Definition\Type&ArrayAccess; however, parameter $search of array_key_exists() does only seem to accept array, 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

214
                    $fieldExists = array_key_exists($fieldName, /** @scrutinizer ignore-type */ $value);
Loading history...
215
                } elseif ($isArrayLike) {
216
                    /** @var ArrayAccess $value */
217
                    $fieldExists = $value->offsetExists($fieldName);
218
                } else {
219
                    $fieldExists = property_exists($value, $fieldName);
220
                }
221
222 2
                if (! $fieldExists) {
223 1
                    continue;
224
                }
225
226 2
                $fieldNode = self::astFromValue($fieldValue, $field->getType());
227
228 2
                if (! $fieldNode) {
229
                    continue;
230
                }
231
232 2
                $fieldNodes[] = new ObjectFieldNode([
233 2
                    'name'  => new NameNode(['value' => $fieldName]),
234 2
                    'value' => $fieldNode,
235
                ]);
236
            }
237
238 2
            return new ObjectValueNode(['fields' => new NodeList($fieldNodes)]);
239
        }
240
241 24
        if ($type instanceof ScalarType || $type instanceof EnumType) {
242
            // Since value is an internally represented value, it must be serialized
243
            // to an externally represented value before converting into an AST.
244
            try {
245 24
                $serialized = $type->serialize($value);
246 3
            } catch (Exception $error) {
247 3
                if ($error instanceof Error && $type instanceof EnumType) {
248 1
                    return null;
249
                }
250 2
                throw $error;
251
            } catch (Throwable $error) {
252
                if ($error instanceof Error && $type instanceof EnumType) {
253
                    return null;
254
                }
255
                throw $error;
256
            }
257
258
            // Others serialize based on their corresponding PHP scalar types.
259 22
            if (is_bool($serialized)) {
260 6
                return new BooleanValueNode(['value' => $serialized]);
261
            }
262 20
            if (is_int($serialized)) {
263 5
                return new IntValueNode(['value' => $serialized]);
264
            }
265 15
            if (is_float($serialized)) {
266
                // int cast with == used for performance reasons
267
                // phpcs:ignore
268 2
                if ((int) $serialized == $serialized) {
269 2
                    return new IntValueNode(['value' => $serialized]);
270
                }
271
272 1
                return new FloatValueNode(['value' => $serialized]);
273
            }
274 14
            if (is_string($serialized)) {
275
                // Enum types use Enum literals.
276 14
                if ($type instanceof EnumType) {
277 4
                    return new EnumValueNode(['value' => $serialized]);
278
                }
279
280
                // ID types can use Int literals.
281 12
                $asInt = (int) $serialized;
282 12
                if ($type instanceof IDType && (string) $asInt === $serialized) {
283 1
                    return new IntValueNode(['value' => $serialized]);
284
                }
285
286
                // Use json_encode, which uses the same string encoding as GraphQL,
287
                // then remove the quotes.
288 12
                return new StringValueNode(['value' => $serialized]);
289
            }
290
291
            throw new InvariantViolation('Cannot convert value to AST: ' . Utils::printSafe($serialized));
292
        }
293
294
        throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
295
    }
296
297
    /**
298
     * Produces a PHP value given a GraphQL Value AST.
299
     *
300
     * A GraphQL type must be provided, which will be used to interpret different
301
     * GraphQL Value literals.
302
     *
303
     * Returns `null` when the value could not be validly coerced according to
304
     * the provided type.
305
     *
306
     * | GraphQL Value        | PHP Value     |
307
     * | -------------------- | ------------- |
308
     * | Input Object         | Assoc Array   |
309
     * | List                 | Array         |
310
     * | Boolean              | Boolean       |
311
     * | String               | String        |
312
     * | Int / Float          | Int / Float   |
313
     * | Enum Value           | Mixed         |
314
     * | Null Value           | null          |
315
     *
316
     * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $valueNode
317
     * @param mixed[]|null                                                                                                                             $variables
318
     *
319
     * @return mixed[]|stdClass|null
320
     *
321
     * @throws Exception
322
     *
323
     * @api
324
     */
325 103
    public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $variables = null)
326
    {
327 103
        $undefined = Utils::undefined();
328
329 103
        if ($valueNode === null) {
330
            // When there is no AST, then there is also no value.
331
            // Importantly, this is different from returning the GraphQL null value.
332 1
            return $undefined;
333
        }
334
335 102
        if ($type instanceof NonNull) {
336 38
            if ($valueNode instanceof NullValueNode) {
337
                // Invalid: intentionally return no value.
338 5
                return $undefined;
339
            }
340
341 37
            return self::valueFromAST($valueNode, $type->getWrappedType(), $variables);
342
        }
343
344 102
        if ($valueNode instanceof NullValueNode) {
345
            // This is explicitly returning the value null.
346 9
            return null;
347
        }
348
349 100
        if ($valueNode instanceof VariableNode) {
350 11
            $variableName = $valueNode->name->value;
351
352 11
            if (! $variables || ! array_key_exists($variableName, $variables)) {
353
                // No valid return value.
354 1
                return $undefined;
355
            }
356
357 11
            $variableValue = $variables[$variableName] ?? null;
358 11
            if ($variableValue === null && $type instanceof NonNull) {
359
                return $undefined; // Invalid: intentionally return no value.
360
            }
361
362
            // Note: This does no further checking that this variable is correct.
363
            // This assumes that this query has been validated and the variable
364
            // usage here is of the correct type.
365 11
            return $variables[$variableName];
366
        }
367
368 91
        if ($type instanceof ListOfType) {
369 7
            $itemType = $type->getWrappedType();
370
371 7
            if ($valueNode instanceof ListValueNode) {
372 7
                $coercedValues = [];
373 7
                $itemNodes     = $valueNode->values;
374 7
                foreach ($itemNodes as $itemNode) {
375 7
                    if (self::isMissingVariable($itemNode, $variables)) {
376
                        // If an array contains a missing variable, it is either coerced to
377
                        // null or if the item type is non-null, it considered invalid.
378 1
                        if ($itemType instanceof NonNull) {
379
                            // Invalid: intentionally return no value.
380 1
                            return $undefined;
381
                        }
382 1
                        $coercedValues[] = null;
383
                    } else {
384 7
                        $itemValue = self::valueFromAST($itemNode, $itemType, $variables);
385 7
                        if ($undefined === $itemValue) {
386
                            // Invalid: intentionally return no value.
387 4
                            return $undefined;
388
                        }
389 7
                        $coercedValues[] = $itemValue;
390
                    }
391
                }
392
393 7
                return $coercedValues;
394
            }
395 5
            $coercedValue = self::valueFromAST($valueNode, $itemType, $variables);
396 5
            if ($undefined === $coercedValue) {
397
                // Invalid: intentionally return no value.
398 4
                return $undefined;
399
            }
400
401 5
            return [$coercedValue];
402
        }
403
404 90
        if ($type instanceof InputObjectType) {
405 4
            if (! $valueNode instanceof ObjectValueNode) {
406
                // Invalid: intentionally return no value.
407 2
                return $undefined;
408
            }
409
410 4
            $coercedObj = [];
411 4
            $fields     = $type->getFields();
412 4
            $fieldNodes = Utils::keyMap(
413 4
                $valueNode->fields,
414
                static function ($field) {
415 4
                    return $field->name->value;
416 4
                }
417
            );
418 4
            foreach ($fields as $field) {
419
                /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $fieldNode */
420 4
                $fieldName = $field->name;
421 4
                $fieldNode = $fieldNodes[$fieldName] ?? null;
422
423 4
                if ($fieldNode === null || self::isMissingVariable($fieldNode->value, $variables)) {
424 4
                    if ($field->defaultValueExists()) {
425 2
                        $coercedObj[$fieldName] = $field->defaultValue;
426 4
                    } elseif ($field->getType() instanceof NonNull) {
427
                        // Invalid: intentionally return no value.
428 2
                        return $undefined;
429
                    }
430 4
                    continue;
431
                }
432
433 4
                $fieldValue = self::valueFromAST(
434 4
                    $fieldNode !== null ? $fieldNode->value : null,
435 4
                    $field->getType(),
436 4
                    $variables
437
                );
438
439 4
                if ($undefined === $fieldValue) {
440
                    // Invalid: intentionally return no value.
441 1
                    return $undefined;
442
                }
443 4
                $coercedObj[$fieldName] = $fieldValue;
444
            }
445
446 4
            return $coercedObj;
447
        }
448
449 90
        if ($type instanceof EnumType) {
450 6
            if (! $valueNode instanceof EnumValueNode) {
451 1
                return $undefined;
452
            }
453 6
            $enumValue = $type->getValue($valueNode->value);
454 6
            if (! $enumValue) {
455
                return $undefined;
456
            }
457
458 6
            return $enumValue->value;
459
        }
460
461 85
        if ($type instanceof ScalarType) {
462
            // Scalars fulfill parsing a literal value via parseLiteral().
463
            // Invalid values represent a failure to parse correctly, in which case
464
            // no value is returned.
465
            try {
466 85
                return $type->parseLiteral($valueNode, $variables);
467 8
            } catch (Exception $error) {
468 8
                return $undefined;
469
            } catch (Throwable $error) {
470
                return $undefined;
471
            }
472
        }
473
474
        throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
475
    }
476
477
    /**
478
     * Returns true if the provided valueNode is a variable which is not defined
479
     * in the set of variables.
480
     *
481
     * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $valueNode
482
     * @param mixed[]                                                                                                                             $variables
483
     *
484
     * @return bool
485
     */
486 9
    private static function isMissingVariable(ValueNode $valueNode, $variables)
487
    {
488 9
        return $valueNode instanceof VariableNode &&
489 9
            (count($variables) === 0 || ! array_key_exists($valueNode->name->value, $variables));
490
    }
491
492
    /**
493
     * Produces a PHP value given a GraphQL Value AST.
494
     *
495
     * Unlike `valueFromAST()`, no type is provided. The resulting PHP value
496
     * will reflect the provided GraphQL value AST.
497
     *
498
     * | GraphQL Value        | PHP Value     |
499
     * | -------------------- | ------------- |
500
     * | Input Object         | Assoc Array   |
501
     * | List                 | Array         |
502
     * | Boolean              | Boolean       |
503
     * | String               | String        |
504
     * | Int / Float          | Int / Float   |
505
     * | Enum                 | Mixed         |
506
     * | Null                 | null          |
507
     *
508
     * @param Node         $valueNode
509
     * @param mixed[]|null $variables
510
     *
511
     * @return mixed
512
     *
513
     * @throws Exception
514
     *
515
     * @api
516
     */
517 6
    public static function valueFromASTUntyped($valueNode, ?array $variables = null)
518
    {
519
        switch (true) {
520 6
            case $valueNode instanceof NullValueNode:
521 2
                return null;
522 6
            case $valueNode instanceof IntValueNode:
523 3
                return intval($valueNode->value, 10);
524 5
            case $valueNode instanceof FloatValueNode:
525 2
                return floatval($valueNode->value);
526 5
            case $valueNode instanceof StringValueNode:
527 5
            case $valueNode instanceof EnumValueNode:
528 5
            case $valueNode instanceof BooleanValueNode:
529 4
                return $valueNode->value;
530 4
            case $valueNode instanceof ListValueNode:
531 4
                return array_map(
532
                    static function ($node) use ($variables) {
533 4
                        return self::valueFromASTUntyped($node, $variables);
534 4
                    },
535 4
                    iterator_to_array($valueNode->values)
536
                );
537 2
            case $valueNode instanceof ObjectValueNode:
538 2
                return array_combine(
539 2
                    array_map(
540
                        static function ($field) {
541 2
                            return $field->name->value;
542 2
                        },
543 2
                        iterator_to_array($valueNode->fields)
544
                    ),
545 2
                    array_map(
546
                        static function ($field) use ($variables) {
547 2
                            return self::valueFromASTUntyped($field->value, $variables);
548 2
                        },
549 2
                        iterator_to_array($valueNode->fields)
550
                    )
551
                );
552 1
            case $valueNode instanceof VariableNode:
553 1
                $variableName = $valueNode->name->value;
554
555 1
                return $variables && isset($variables[$variableName])
556 1
                    ? $variables[$variableName]
557 1
                    : null;
558
        }
559
560
        throw new Error('Unexpected value kind: ' . $valueNode->kind . '.');
561
    }
562
563
    /**
564
     * Returns type definition for given AST Type node
565
     *
566
     * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
567
     *
568
     * @return Type|null
569
     *
570
     * @throws Exception
571
     *
572
     * @api
573
     */
574 340
    public static function typeFromAST(Schema $schema, $inputTypeNode)
575
    {
576 340
        if ($inputTypeNode instanceof ListTypeNode) {
577 19
            $innerType = self::typeFromAST($schema, $inputTypeNode->type);
578
579 19
            return $innerType ? new ListOfType($innerType) : null;
580
        }
581 340
        if ($inputTypeNode instanceof NonNullTypeNode) {
582 42
            $innerType = self::typeFromAST($schema, $inputTypeNode->type);
583
584 42
            return $innerType ? new NonNull($innerType) : null;
585
        }
586 340
        if ($inputTypeNode instanceof NamedTypeNode) {
0 ignored issues
show
introduced by
$inputTypeNode is always a sub-type of GraphQL\Language\AST\NamedTypeNode.
Loading history...
587 340
            return $schema->getType($inputTypeNode->name->value);
588
        }
589
590
        throw new Error('Unexpected type kind: ' . $inputTypeNode->kind . '.');
591
    }
592
593
    /**
594
     * Returns operation type ("query", "mutation" or "subscription") given a document and operation name
595
     *
596
     * @param string $operationName
597
     *
598
     * @return bool
599
     *
600
     * @api
601
     */
602 23
    public static function getOperation(DocumentNode $document, $operationName = null)
603
    {
604 23
        if ($document->definitions) {
605 23
            foreach ($document->definitions as $def) {
606 23
                if (! ($def instanceof OperationDefinitionNode)) {
607
                    continue;
608
                }
609
610 23
                if (! $operationName || (isset($def->name->value) && $def->name->value === $operationName)) {
611 23
                    return $def->operation;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $def->operation returns the type string which is incompatible with the documented return type boolean.
Loading history...
612
                }
613
            }
614
        }
615
616
        return false;
617
    }
618
}
619