1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Php\PhpVersion;
6: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
7: use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
8: use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
9: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
10: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
11: use PHPStan\Reflection\InitializerExprTypeResolver;
12: use PHPStan\TrinaryLogic;
13: use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType;
14: use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
15: use PHPStan\Type\Constant\ConstantBooleanType;
16: use PHPStan\Type\Constant\ConstantIntegerType;
17: use function array_filter;
18: use function array_map;
19: use function assert;
20: use function ceil;
21: use function count;
22: use function floor;
23: use function get_class;
24: use function is_float;
25: use function is_int;
26: use function max;
27: use function min;
28: use function sprintf;
29: use const PHP_INT_MAX;
30: use const PHP_INT_MIN;
31:
32: /** @api */
33: class IntegerRangeType extends IntegerType implements CompoundType
34: {
35:
36: private function __construct(private ?int $min, private ?int $max)
37: {
38: parent::__construct();
39: assert($min === null || $max === null || $min <= $max);
40: assert($min !== null || $max !== null);
41: }
42:
43: public static function fromInterval(?int $min, ?int $max, int $shift = 0): Type
44: {
45: if ($min !== null && $max !== null) {
46: if ($min > $max) {
47: return new NeverType();
48: }
49: if ($min === $max) {
50: return new ConstantIntegerType($min + $shift);
51: }
52: }
53:
54: if ($min === null && $max === null) {
55: return new IntegerType();
56: }
57:
58: $range = (new self($min, $max))->shift($shift);
59: if (!$range instanceof self) {
60: return $range;
61: }
62:
63: // Nothing is smaller than the smallest integer, and nothing is bigger than the biggest one,
64: // so an unbounded side that reaches either one holds a single value.
65: if ($range->min === null && $range->max === PHP_INT_MIN) {
66: return new ConstantIntegerType(PHP_INT_MIN);
67: }
68: if ($range->min === PHP_INT_MAX && $range->max === null) {
69: return new ConstantIntegerType(PHP_INT_MAX);
70: }
71:
72: return $range;
73: }
74:
75: protected static function isDisjoint(?int $minA, ?int $maxA, ?int $minB, ?int $maxB, bool $touchingIsDisjoint = true): bool
76: {
77: $offset = $touchingIsDisjoint ? 0 : 1;
78: return $minA !== null && $maxB !== null && $minA > $maxB + $offset
79: || $maxA !== null && $minB !== null && $maxA + $offset < $minB;
80: }
81:
82: /**
83: * Return the range of integers smaller than the given value
84: *
85: * @param int|float $value
86: */
87: public static function createAllSmallerThan($value): Type
88: {
89: if (is_int($value)) {
90: return self::fromInterval(null, $value, -1);
91: }
92:
93: if ($value > PHP_INT_MAX) {
94: return new IntegerType();
95: }
96:
97: if ($value <= PHP_INT_MIN) {
98: return new NeverType();
99: }
100:
101: return self::fromInterval(null, (int) ceil($value), -1);
102: }
103:
104: /**
105: * Return the range of integers smaller than or equal to the given value
106: *
107: * @param int|float $value
108: */
109: public static function createAllSmallerThanOrEqualTo($value): Type
110: {
111: if (is_int($value)) {
112: return self::fromInterval(null, $value);
113: }
114:
115: if ($value >= PHP_INT_MAX) {
116: return new IntegerType();
117: }
118:
119: if ($value < PHP_INT_MIN) {
120: return new NeverType();
121: }
122:
123: return self::fromInterval(null, (int) floor($value));
124: }
125:
126: /**
127: * Return the range of integers greater than the given value
128: *
129: * @param int|float $value
130: */
131: public static function createAllGreaterThan($value): Type
132: {
133: if (is_int($value)) {
134: return self::fromInterval($value, null, 1);
135: }
136:
137: if ($value < PHP_INT_MIN) {
138: return new IntegerType();
139: }
140:
141: if ($value >= PHP_INT_MAX) {
142: return new NeverType();
143: }
144:
145: return self::fromInterval((int) floor($value), null, 1);
146: }
147:
148: /**
149: * Return the range of integers greater than or equal to the given value
150: *
151: * @param int|float $value
152: */
153: public static function createAllGreaterThanOrEqualTo($value): Type
154: {
155: if (is_int($value)) {
156: return self::fromInterval($value, null);
157: }
158:
159: if ($value <= PHP_INT_MIN) {
160: return new IntegerType();
161: }
162:
163: if ($value > PHP_INT_MAX) {
164: return new NeverType();
165: }
166:
167: return self::fromInterval((int) ceil($value), null);
168: }
169:
170: public function getMin(): ?int
171: {
172: return $this->min;
173: }
174:
175: public function getMax(): ?int
176: {
177: return $this->max;
178: }
179:
180: public function describe(VerbosityLevel $level): string
181: {
182: return sprintf('int<%s, %s>', $this->min ?? 'min', $this->max ?? 'max');
183: }
184:
185: public function shift(int $amount): Type
186: {
187: if ($amount === 0) {
188: return $this;
189: }
190:
191: $min = $this->min;
192: $max = $this->max;
193:
194: if ($amount < 0) {
195: if ($max !== null) {
196: if ($max < PHP_INT_MIN - $amount) {
197: return new NeverType();
198: }
199: $max += $amount;
200: }
201: if ($min !== null) {
202: $min = $min < PHP_INT_MIN - $amount ? null : $min + $amount;
203: }
204: } else {
205: if ($min !== null) {
206: if ($min > PHP_INT_MAX - $amount) {
207: return new NeverType();
208: }
209: $min += $amount;
210: }
211: if ($max !== null) {
212: $max = $max > PHP_INT_MAX - $amount ? null : $max + $amount;
213: }
214: }
215:
216: return self::fromInterval($min, $max);
217: }
218:
219: public function accepts(Type $type, bool $strictTypes): AcceptsResult
220: {
221: if ($type instanceof parent) {
222: return $this->isSuperTypeOf($type)->toAcceptsResult();
223: }
224:
225: if ($type instanceof CompoundType) {
226: return $type->isAcceptedBy($this, $strictTypes);
227: }
228:
229: return AcceptsResult::createNo();
230: }
231:
232: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
233: {
234: if ($type instanceof self || $type instanceof ConstantIntegerType) {
235: if ($type instanceof self) {
236: $typeMin = $type->min;
237: $typeMax = $type->max;
238: } else {
239: $typeMin = $type->getValue();
240: $typeMax = $type->getValue();
241: }
242:
243: if (self::isDisjoint($this->min, $this->max, $typeMin, $typeMax)) {
244: return IsSuperTypeOfResult::createNo();
245: }
246:
247: if (
248: ($this->min === null || $typeMin !== null && $this->min <= $typeMin)
249: && ($this->max === null || $typeMax !== null && $this->max >= $typeMax)
250: ) {
251: return IsSuperTypeOfResult::createYes();
252: }
253:
254: return IsSuperTypeOfResult::createMaybe();
255: }
256:
257: if ($type instanceof parent) {
258: return IsSuperTypeOfResult::createMaybe();
259: }
260:
261: if ($type instanceof CompoundType) {
262: return $type->isSubTypeOf($this);
263: }
264:
265: return IsSuperTypeOfResult::createNo();
266: }
267:
268: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
269: {
270: if ($otherType instanceof parent) {
271: return $otherType->isSuperTypeOf($this);
272: }
273:
274: if ($otherType instanceof UnionType) {
275: return $this->isSubTypeOfUnionWithReason($otherType);
276: }
277:
278: if ($otherType instanceof IntersectionType) {
279: return $otherType->isSuperTypeOf($this);
280: }
281:
282: return IsSuperTypeOfResult::createNo();
283: }
284:
285: private function isSubTypeOfUnionWithReason(UnionType $otherType): IsSuperTypeOfResult
286: {
287: if ($this->min !== null && $this->max !== null) {
288: $matchingConstantIntegers = array_filter(
289: $otherType->getTypes(),
290: fn (Type $type): bool => $type instanceof ConstantIntegerType && $type->getValue() >= $this->min && $type->getValue() <= $this->max,
291: );
292:
293: if (count($matchingConstantIntegers) === ($this->max - $this->min + 1)) {
294: return IsSuperTypeOfResult::createYes();
295: }
296: }
297:
298: return IsSuperTypeOfResult::createNo()->or(...array_map(fn (Type $innerType) => $this->isSubTypeOf($innerType), $otherType->getTypes()));
299: }
300:
301: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
302: {
303: return $this->isSubTypeOf($acceptingType)->toAcceptsResult();
304: }
305:
306: public function equals(Type $type): bool
307: {
308: return $type instanceof self && $this->min === $type->min && $this->max === $type->max;
309: }
310:
311: public function generalize(GeneralizePrecision $precision): Type
312: {
313: return new IntegerType();
314: }
315:
316: public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
317: {
318: if ($this->min === null) {
319: $minIsSmaller = TrinaryLogic::createYes();
320: } else {
321: $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThan($otherType, $phpVersion);
322: }
323:
324: if ($this->max === null) {
325: $maxIsSmaller = TrinaryLogic::createNo();
326: } else {
327: $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThan($otherType, $phpVersion);
328: }
329:
330: // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
331: $zeroInt = new ConstantIntegerType(0);
332: if (!$zeroInt->isSuperTypeOf($this)->no()) {
333: return TrinaryLogic::extremeIdentity(
334: $zeroInt->isSmallerThan($otherType, $phpVersion),
335: $minIsSmaller,
336: $maxIsSmaller,
337: );
338: }
339:
340: return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
341: }
342:
343: public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
344: {
345: if ($this->min === null) {
346: $minIsSmaller = TrinaryLogic::createYes();
347: } else {
348: $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThanOrEqual($otherType, $phpVersion);
349: }
350:
351: if ($this->max === null) {
352: $maxIsSmaller = TrinaryLogic::createNo();
353: } else {
354: $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThanOrEqual($otherType, $phpVersion);
355: }
356:
357: // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
358: $zeroInt = new ConstantIntegerType(0);
359: if (!$zeroInt->isSuperTypeOf($this)->no()) {
360: return TrinaryLogic::extremeIdentity(
361: $zeroInt->isSmallerThanOrEqual($otherType, $phpVersion),
362: $minIsSmaller,
363: $maxIsSmaller,
364: );
365: }
366:
367: return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
368: }
369:
370: public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
371: {
372: if ($this->min === null) {
373: $minIsSmaller = TrinaryLogic::createNo();
374: } else {
375: $minIsSmaller = $otherType->isSmallerThan(new ConstantIntegerType($this->min), $phpVersion);
376: }
377:
378: if ($this->max === null) {
379: $maxIsSmaller = TrinaryLogic::createYes();
380: } else {
381: $maxIsSmaller = $otherType->isSmallerThan(new ConstantIntegerType($this->max), $phpVersion);
382: }
383:
384: // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
385: $zeroInt = new ConstantIntegerType(0);
386: if (!$zeroInt->isSuperTypeOf($this)->no()) {
387: return TrinaryLogic::extremeIdentity(
388: $otherType->isSmallerThan($zeroInt, $phpVersion),
389: $minIsSmaller,
390: $maxIsSmaller,
391: );
392: }
393:
394: return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
395: }
396:
397: public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic
398: {
399: if ($this->min === null) {
400: $minIsSmaller = TrinaryLogic::createNo();
401: } else {
402: $minIsSmaller = $otherType->isSmallerThanOrEqual(new ConstantIntegerType($this->min), $phpVersion);
403: }
404:
405: if ($this->max === null) {
406: $maxIsSmaller = TrinaryLogic::createYes();
407: } else {
408: $maxIsSmaller = $otherType->isSmallerThanOrEqual(new ConstantIntegerType($this->max), $phpVersion);
409: }
410:
411: // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti
412: $zeroInt = new ConstantIntegerType(0);
413: if (!$zeroInt->isSuperTypeOf($this)->no()) {
414: return TrinaryLogic::extremeIdentity(
415: $otherType->isSmallerThanOrEqual($zeroInt, $phpVersion),
416: $minIsSmaller,
417: $maxIsSmaller,
418: );
419: }
420:
421: return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller);
422: }
423:
424: public function getSmallerType(PhpVersion $phpVersion): Type
425: {
426: $subtractedTypes = [
427: new ConstantBooleanType(true),
428: ];
429:
430: if ($this->max !== null) {
431: $subtractedTypes[] = self::createAllGreaterThanOrEqualTo($this->max);
432: }
433:
434: return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes));
435: }
436:
437: public function getSmallerOrEqualType(PhpVersion $phpVersion): Type
438: {
439: $subtractedTypes = [];
440:
441: if ($this->max !== null) {
442: $subtractedTypes[] = self::createAllGreaterThan($this->max);
443: }
444:
445: return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes));
446: }
447:
448: public function getGreaterType(PhpVersion $phpVersion): Type
449: {
450: $subtractedTypes = [
451: new NullType(),
452: new ConstantBooleanType(false),
453: ];
454:
455: if ($this->min !== null) {
456: $subtractedTypes[] = self::createAllSmallerThanOrEqualTo($this->min);
457: }
458:
459: if ($this->min !== null && $this->min > 0 || $this->max !== null && $this->max < 0) {
460: $subtractedTypes[] = new ConstantBooleanType(true);
461: }
462:
463: return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes));
464: }
465:
466: public function getGreaterOrEqualType(PhpVersion $phpVersion): Type
467: {
468: $subtractedTypes = [];
469:
470: if ($this->min !== null) {
471: $subtractedTypes[] = self::createAllSmallerThan($this->min);
472: }
473:
474: if ($this->min !== null && $this->min > 0 || $this->max !== null && $this->max < 0) {
475: $subtractedTypes[] = new NullType();
476: $subtractedTypes[] = new ConstantBooleanType(false);
477: }
478:
479: return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes));
480: }
481:
482: public function toBoolean(): BooleanType
483: {
484: $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($this);
485: if ($isZero->no()) {
486: return new ConstantBooleanType(true);
487: }
488:
489: if ($isZero->maybe()) {
490: return new BooleanType();
491: }
492:
493: return new ConstantBooleanType(false);
494: }
495:
496: public function toAbsoluteNumber(): Type
497: {
498: if ($this->min !== null && $this->min >= 0) {
499: return $this;
500: }
501:
502: // Negating the smallest integer overflows, so its absolute value is treated as unbounded,
503: // the same way an unbounded lower bound is. This keeps abs(int<min, 0>) and
504: // abs(int<-9223372036854775808, 0>) in agreement.
505: $inversedMin = $this->min !== null && $this->min !== PHP_INT_MIN ? -$this->min : null;
506:
507: if ($this->max === null || $this->max >= 0) {
508: return self::fromInterval(0, $inversedMin !== null && $this->max !== null ? max($inversedMin, $this->max) : null);
509: }
510:
511: return self::fromInterval(-$this->max, $inversedMin);
512: }
513:
514: public function toString(): Type
515: {
516: $finiteTypes = $this->getFiniteTypes();
517: if ($finiteTypes !== []) {
518: return TypeCombinator::union(...$finiteTypes)->toString();
519: }
520:
521: $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($this);
522: if ($isZero->no()) {
523: return new IntersectionType([
524: new StringType(),
525: new AccessoryDecimalIntegerStringType(),
526: new AccessoryNonFalsyStringType(),
527: ]);
528: }
529:
530: return new IntersectionType([
531: new StringType(),
532: new AccessoryDecimalIntegerStringType(),
533: ]);
534: }
535:
536: /**
537: * Return the union with another type, but only if it can be expressed in a simpler way than using UnionType
538: *
539: */
540: public function tryUnion(Type $otherType): ?Type
541: {
542: if ($otherType instanceof self || $otherType instanceof ConstantIntegerType) {
543: if ($otherType instanceof self) {
544: $otherMin = $otherType->min;
545: $otherMax = $otherType->max;
546: } else {
547: $otherMin = $otherType->getValue();
548: $otherMax = $otherType->getValue();
549: }
550:
551: if (self::isDisjoint($this->min, $this->max, $otherMin, $otherMax, false)) {
552: return null;
553: }
554:
555: return self::fromInterval(
556: $this->min !== null && $otherMin !== null ? min($this->min, $otherMin) : null,
557: $this->max !== null && $otherMax !== null ? max($this->max, $otherMax) : null,
558: );
559: }
560:
561: if (get_class($otherType) === parent::class) {
562: return $otherType;
563: }
564:
565: return null;
566: }
567:
568: /**
569: * Return the intersection with another type, but only if it can be expressed in a simpler way than using
570: * IntersectionType
571: *
572: */
573: public function tryIntersect(Type $otherType): ?Type
574: {
575: if ($otherType instanceof self || $otherType instanceof ConstantIntegerType) {
576: if ($otherType instanceof self) {
577: $otherMin = $otherType->min;
578: $otherMax = $otherType->max;
579: } else {
580: $otherMin = $otherType->getValue();
581: $otherMax = $otherType->getValue();
582: }
583:
584: if (self::isDisjoint($this->min, $this->max, $otherMin, $otherMax, false)) {
585: return new NeverType();
586: }
587:
588: if ($this->min === null) {
589: $newMin = $otherMin;
590: } elseif ($otherMin === null) {
591: $newMin = $this->min;
592: } else {
593: $newMin = max($this->min, $otherMin);
594: }
595:
596: if ($this->max === null) {
597: $newMax = $otherMax;
598: } elseif ($otherMax === null) {
599: $newMax = $this->max;
600: } else {
601: $newMax = min($this->max, $otherMax);
602: }
603:
604: return self::fromInterval($newMin, $newMax);
605: }
606:
607: if (get_class($otherType) === parent::class) {
608: return $this;
609: }
610:
611: return null;
612: }
613:
614: /**
615: * Return the different with another type, or null if it cannot be represented.
616: *
617: */
618: public function tryRemove(Type $typeToRemove): ?Type
619: {
620: if (get_class($typeToRemove) === parent::class) {
621: return new NeverType();
622: }
623:
624: if ($typeToRemove instanceof self || $typeToRemove instanceof ConstantIntegerType) {
625: if ($typeToRemove instanceof self) {
626: $removeMin = $typeToRemove->min;
627: $removeMax = $typeToRemove->max;
628: } else {
629: $removeMin = $typeToRemove->getValue();
630: $removeMax = $typeToRemove->getValue();
631: }
632:
633: if (
634: $this->min !== null && $removeMax !== null && $removeMax < $this->min
635: || $this->max !== null && $removeMin !== null && $this->max < $removeMin
636: ) {
637: return $this;
638: }
639:
640: if ($removeMin !== null && $removeMin !== PHP_INT_MIN) {
641: $lowerPart = self::fromInterval($this->min, $removeMin - 1);
642: } else {
643: $lowerPart = null;
644: }
645: if ($removeMax !== null && $removeMax !== PHP_INT_MAX) {
646: $upperPart = self::fromInterval($removeMax + 1, $this->max);
647: } else {
648: $upperPart = null;
649: }
650:
651: if ($lowerPart !== null && $upperPart !== null) {
652: return TypeCombinator::union($lowerPart, $upperPart);
653: }
654:
655: return $lowerPart ?? $upperPart;
656: }
657:
658: return null;
659: }
660:
661: public function exponentiate(Type $exponent): Type
662: {
663: if ($exponent instanceof UnionType) {
664: $results = [];
665: foreach ($exponent->getTypes() as $unionType) {
666: $results[] = $this->exponentiate($unionType);
667: }
668: return TypeCombinator::union(...$results);
669: }
670:
671: if ($exponent instanceof IntegerRangeType) {
672: $min = null;
673: $max = null;
674: if ($this->getMin() !== null && $exponent->getMin() !== null) {
675: $min = $this->getMin() ** $exponent->getMin();
676: }
677: if ($this->getMax() !== null && $exponent->getMax() !== null) {
678: $max = $this->getMax() ** $exponent->getMax();
679: }
680:
681: if (($min !== null || $max !== null) && !is_float($min) && !is_float($max)) {
682: return self::fromInterval($min, $max);
683: }
684: }
685:
686: if ($exponent instanceof ConstantScalarType) {
687: $exponentValue = $exponent->getValue();
688: if (is_int($exponentValue)) {
689: $min = null;
690: $max = null;
691: if ($this->getMin() !== null) {
692: $min = $this->getMin() ** $exponentValue;
693: }
694: if ($this->getMax() !== null) {
695: $max = $this->getMax() ** $exponentValue;
696: }
697:
698: if (!is_float($min) && !is_float($max)) {
699: return self::fromInterval($min, $max);
700: }
701: }
702: }
703:
704: return parent::exponentiate($exponent);
705: }
706:
707: /**
708: * @return list<ConstantIntegerType>
709: */
710: public function getFiniteTypes(): array
711: {
712: if ($this->min === null || $this->max === null) {
713: return [];
714: }
715:
716: $size = $this->max - $this->min;
717: if ($size > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) {
718: return [];
719: }
720:
721: $types = [];
722: for ($i = $this->min; $i <= $this->max; $i++) {
723: $types[] = new ConstantIntegerType($i);
724: }
725:
726: return $types;
727: }
728:
729: public function toPhpDocNode(): TypeNode
730: {
731: if ($this->min === null) {
732: $min = new IdentifierTypeNode('min');
733: } else {
734: $min = new ConstTypeNode(new ConstExprIntegerNode((string) $this->min));
735: }
736:
737: if ($this->max === null) {
738: $max = new IdentifierTypeNode('max');
739: } else {
740: $max = new ConstTypeNode(new ConstExprIntegerNode((string) $this->max));
741: }
742:
743: return new GenericTypeNode(new IdentifierTypeNode('int'), [$min, $max]);
744: }
745:
746: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
747: {
748: $zeroInt = new ConstantIntegerType(0);
749: if ($zeroInt->isSuperTypeOf($this)->no()) {
750: if ($type->isTrue()->yes()) {
751: return new ConstantBooleanType(true);
752: }
753: if ($type->isFalse()->yes()) {
754: return new ConstantBooleanType(false);
755: }
756: }
757:
758: if (
759: $this->isSmallerThan($type, $phpVersion)->yes()
760: || $this->isGreaterThan($type, $phpVersion)->yes()
761: ) {
762: return new ConstantBooleanType(false);
763: }
764:
765: return parent::looseCompare($type, $phpVersion);
766: }
767:
768: }
769: