1 /** Statically allocated arrays with compile-time known lengths. 2 */ 3 module nxt.fixed_array; 4 5 @safe pure: 6 7 /** Statically allocated `T`-array of fixed pre-allocated length. 8 * 9 * Similar to Rust's `fixedvec`: https://docs.rs/fixedvec/0.2.3/fixedvec/ 10 * Similar to `mir.small_array` at http://mir-algorithm.libmir.org/mir_small_array.html. 11 * 12 * TODO Merge member functions with basic_*_array.d and array_ex.d 13 * 14 * TODO Add @safe nothrow @nogc ctor from static array (of known length) 15 * 16 * TODO use opPostMove (https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1014.md) 17 */ 18 struct FixedArray(T, uint capacity_, bool borrowChecked = false) 19 { 20 // pragma(msg, "T:", T, " capacity_:", capacity_, " borrowChecked:", borrowChecked); 21 import core.exception : onRangeError; 22 import core.lifetime : move, moveEmplace; 23 import std.bitmanip : bitfields; 24 import std.traits : isSomeChar, isAssignable; 25 import core.internal.traits : hasElaborateDestructor; 26 import nxt.container_traits : mustAddGCRange; 27 28 alias capacity = capacity_; // for public use 29 30 /// Store of `capacity` number of elements. 31 T[capacity] _store; // TODO use store constructor 32 33 static if (borrowChecked) 34 { 35 /// Number of bits needed to store number of read borrows. 36 private enum readBorrowCountBits = 3; 37 38 /// Maximum value possible for `_readBorrowCount`. 39 enum readBorrowCountMax = 2^^readBorrowCountBits - 1; 40 41 static if (capacity <= 2^^(8*ubyte.sizeof - 1 - readBorrowCountBits) - 1) 42 { 43 private enum lengthMax = 2^^4 - 1; 44 alias Length = ubyte; 45 // TODO make private: 46 mixin(bitfields!(Length, "_length", 4, /// number of defined elements in `_store` 47 bool, "_writeBorrowed", 1, 48 uint, "_readBorrowCount", readBorrowCountBits, 49 )); 50 } 51 else static if (capacity <= 2^^(8*ushort.sizeof - 1 - readBorrowCountBits) - 1) 52 { 53 alias Length = ushort; 54 private enum lengthMax = 2^^14 - 1; 55 // TODO make private: 56 mixin(bitfields!(Length, "_length", 14, /// number of defined elements in `_store` 57 bool, "_writeBorrowed", 1, 58 uint, "_readBorrowCount", readBorrowCountBits, 59 )); 60 } 61 else 62 { 63 static assert("Too large requested capacity " ~ capacity); 64 } 65 } 66 else 67 { 68 static if (capacity <= ubyte.max) 69 { 70 static if (T.sizeof == 1) 71 alias Length = ubyte; // pack length 72 else 73 alias Length = uint; 74 } 75 else static if (capacity <= ushort.max) 76 { 77 static if (T.sizeof <= 2) 78 alias Length = uint; // pack length 79 else 80 alias Length = uint; 81 } 82 else 83 { 84 static assert("Too large requested capacity " ~ capacity); 85 } 86 Length _length; /// number of defined elements in `_store` 87 } 88 89 /// Is `true` iff `U` can be assign to the element type `T` of `this`. 90 private enum isElementAssignable(U) = isAssignable!(T, U); 91 92 /// Construct from element `values`. 93 this(Us...)(Us values) @trusted 94 if (Us.length <= capacity) 95 { 96 import nxt.container_traits : needsMove; 97 foreach (immutable ix, ref value; values) 98 static if (needsMove!(typeof(value))) 99 moveEmplace(value, _store[ix]); 100 else 101 _store[ix] = value; 102 _length = cast(Length)values.length; 103 static if (borrowChecked) 104 { 105 _writeBorrowed = false; 106 _readBorrowCount = 0; 107 } 108 } 109 110 /// Construct from element `values`. 111 this(U)(U[] values) @trusted 112 if (__traits(isCopyable, U)// && 113 // TODO isElementAssignable!U 114 ) // prevent accidental move of l-value `values` in array calls 115 { 116 version(assert) if (values.length > capacity) onRangeError(); // `Arguments don't fit in array` 117 _store[0 .. values.length] = values; 118 _length = cast(Length)values.length; 119 static if (borrowChecked) 120 { 121 _writeBorrowed = false; 122 _readBorrowCount = 0; 123 } 124 } 125 126 /// Construct from element `values`. 127 static typeof(this) fromValuesUnsafe(U)(U[] values) @system 128 if (__traits(isCopyable, U) && 129 isElementAssignable!U 130 ) // prevent accidental move of l-value `values` in array calls 131 { 132 typeof(return) that; // TODO use Store constructor: 133 134 that._store[0 .. values.length] = values; 135 that._length = cast(Length)values.length; 136 137 static if (borrowChecked) 138 { 139 that._writeBorrowed = false; 140 that._readBorrowCount = 0; 141 } 142 143 return that; 144 } 145 146 static if (borrowChecked || 147 hasElaborateDestructor!T) 148 { 149 /** Destruct. */ 150 ~this() @trusted @nogc 151 { 152 static if (borrowChecked) { assert(!isBorrowed); } 153 static if (hasElaborateDestructor!T) 154 foreach (immutable i; 0 .. length) 155 .destroy(_store.ptr[i]); 156 } 157 } 158 159 /** Add elements `es` to the back. 160 * Throws when array becomes full. 161 * NOTE doesn't invalidate any borrow 162 */ 163 void insertBack(Es...)(Es es) @trusted 164 if (Es.length <= capacity) // TODO use `isAssignable` 165 { 166 version(assert) if (_length + Es.length > capacity) onRangeError(); // `Arguments don't fit in array` 167 foreach (immutable i, ref e; es) 168 moveEmplace(e, _store[_length + i]); // TODO remove `move` when compiler does it for us 169 _length = cast(Length)(_length + Es.length); // TODO better? 170 } 171 /// ditto 172 alias put = insertBack; // `OutputRange` support 173 174 /** Try to add elements `es` to the back. 175 * NOTE doesn't invalidate any borrow 176 * Returns: `true` iff all `es` were pushed, `false` otherwise. 177 */ 178 bool insertBackMaybe(Es...)(Es es) @trusted 179 if (Es.length <= capacity) 180 { 181 if (_length + Es.length > capacity) { return false; } 182 foreach (immutable i, ref e; es) 183 moveEmplace(e, _store[_length + i]); // TODO remove `move` when compiler does it for us 184 _length = cast(Length)(_length + Es.length); // TODO better? 185 return true; 186 } 187 /// ditto 188 alias putMaybe = insertBackMaybe; 189 190 /** Add elements `es` to the back. 191 * NOTE doesn't invalidate any borrow 192 */ 193 void opOpAssign(string op, Us...)(Us values) 194 if (op == "~" && 195 values.length >= 1 && 196 allSatisfy!(isElementAssignable, Us)) 197 { 198 insertBack(values.move()); // TODO remove `move` when compiler does it for 199 } 200 201 import std.traits : isMutable; 202 static if (isMutable!T) 203 { 204 /** Pop first (front) element. */ 205 auto ref popFront() 206 { 207 assert(!empty); 208 static if (borrowChecked) { assert(!isBorrowed); } 209 // TODO is there a reusable Phobos function for this? 210 foreach (immutable i; 0 .. _length - 1) 211 move(_store[i + 1], _store[i]); // like `_store[i] = _store[i + 1];` but more generic 212 _length = cast(typeof(_length))(_length - 1); // TODO better? 213 return this; 214 } 215 } 216 217 /** Pop last (back) element. */ 218 void popBack()() @trusted // template-lazy 219 { 220 assert(!empty); 221 static if (borrowChecked) { assert(!isBorrowed); } 222 _length = cast(Length)(_length - 1); // TODO better? 223 static if (hasElaborateDestructor!T) 224 .destroy(_store.ptr[_length]); 225 else static if (mustAddGCRange!T) 226 _store.ptr[_length] = T.init; // avoid GC mark-phase dereference 227 } 228 229 /** Pop the `n` last (back) elements. */ 230 void popBackN()(size_t n) @trusted // template-lazy 231 { 232 assert(length >= n); 233 static if (borrowChecked) { assert(!isBorrowed); } 234 _length = cast(Length)(_length - n); // TODO better? 235 static if (hasElaborateDestructor!T) 236 foreach (const i; 0 .. n) 237 .destroy(_store.ptr[_length + i]); 238 else static if (mustAddGCRange!T) // avoid GC mark-phase dereference 239 foreach (const i; 0 .. n) 240 _store.ptr[_length + i] = T.init; 241 } 242 243 /** Move element at `index` to return. */ 244 static if (isMutable!T) 245 { 246 /** Pop element at `index`. */ 247 void popAt()(size_t index) // template-lazy 248 @trusted 249 @("complexity", "O(length)") 250 { 251 assert(index < this.length); 252 .destroy(_store.ptr[index]); 253 shiftToFrontAt(index); 254 _length = cast(Length)(_length - 1); 255 } 256 257 T moveAt()(size_t index) // template-lazy 258 @trusted 259 @("complexity", "O(length)") 260 { 261 assert(index < this.length); 262 auto value = _store.ptr[index].move(); 263 shiftToFrontAt(index); 264 _length = cast(Length)(_length - 1); 265 return value; 266 } 267 268 private void shiftToFrontAt()(size_t index) // template-lazy 269 @trusted 270 { 271 foreach (immutable i; 0 .. this.length - (index + 1)) 272 { 273 immutable si = index + i + 1; // source index 274 immutable ti = index + i; // target index 275 moveEmplace(_store.ptr[si], 276 _store.ptr[ti]); 277 } 278 } 279 } 280 281 pragma(inline, true): 282 283 /** Index operator. */ 284 ref inout(T) opIndex(size_t i) inout @trusted return 285 { 286 assert(i < _length); 287 return _store.ptr[i]; 288 } 289 290 /** First (front) element. */ 291 ref inout(T) front() inout @trusted return 292 { 293 assert(!empty); 294 return _store.ptr[0]; 295 } 296 297 /** Last (back) element. */ 298 ref inout(T) back() inout @trusted return 299 { 300 assert(!empty); 301 return _store.ptr[_length - 1]; 302 } 303 304 static if (borrowChecked) 305 { 306 import nxt.borrowed : ReadBorrowed, WriteBorrowed; 307 308 /// Get read-only slice in range `i` .. `j`. 309 auto opSlice(size_t i, size_t j) const return scope { return sliceRO(i, j); } 310 /// Get read-write slice in range `i` .. `j`. 311 auto opSlice(size_t i, size_t j) return scope { return sliceRW(i, j); } 312 313 /// Get read-only full slice. 314 auto opSlice() const return scope { return sliceRO(); } 315 /// Get read-write full slice. 316 auto opSlice() return scope { return sliceRW(); } 317 318 /// Get full read-only slice. 319 ReadBorrowed!(T[], typeof(this)) sliceRO() const @trusted return scope 320 { 321 import core.internal.traits : Unqual; 322 assert(!_writeBorrowed, "Already write-borrowed"); 323 return typeof(return)(_store.ptr[0 .. _length], 324 cast(Unqual!(typeof(this))*)(&this)); // trusted unconst casta 325 } 326 327 /// Get read-only slice in range `i` .. `j`. 328 ReadBorrowed!(T[], typeof(this)) sliceRO(size_t i, size_t j) const @trusted return scope 329 { 330 import core.internal.traits : Unqual; 331 assert(!_writeBorrowed, "Already write-borrowed"); 332 return typeof(return)(_store.ptr[i .. j], 333 cast(Unqual!(typeof(this))*)(&this)); // trusted unconst cast 334 } 335 336 /// Get full read-write slice. 337 WriteBorrowed!(T[], typeof(this)) sliceRW() @trusted return scope 338 { 339 assert(!_writeBorrowed, "Already write-borrowed"); 340 assert(_readBorrowCount == 0, "Already read-borrowed"); 341 return typeof(return)(_store.ptr[0 .. _length], &this); 342 } 343 344 /// Get read-write slice in range `i` .. `j`. 345 WriteBorrowed!(T[], typeof(this)) sliceRW(size_t i, size_t j) @trusted return scope 346 { 347 assert(!_writeBorrowed, "Already write-borrowed"); 348 assert(_readBorrowCount == 0, "Already read-borrowed"); 349 return typeof(return)(_store.ptr[0 .. j], &this); 350 } 351 352 @property 353 { 354 /// Returns: `true` iff `this` is either write or read borrowed. 355 bool isBorrowed() const { return _writeBorrowed || _readBorrowCount >= 1; } 356 357 /// Returns: `true` iff `this` is write borrowed. 358 bool isWriteBorrowed() const { return _writeBorrowed; } 359 360 /// Returns: number of read-only borrowers of `this`. 361 uint readBorrowCount() const { return _readBorrowCount; } 362 } 363 } 364 else 365 { 366 /// Get slice in range `i` .. `j`. 367 inout(T)[] opSlice(size_t i, size_t j) @trusted inout return scope 368 { 369 // assert(i <= j); 370 // assert(j <= _length); 371 return _store[i .. j]; // TODO make .ptr work 372 } 373 374 /// Get full slice. 375 inout(T)[] opSlice() @trusted inout return scope 376 { 377 return _store[0 .. _length]; // TODO make .ptr work 378 } 379 } 380 381 @property 382 { 383 /** Returns: `true` iff `this` is empty, `false` otherwise. */ 384 bool empty() const { return _length == 0; } 385 386 /** Returns: `true` iff `this` is full, `false` otherwise. */ 387 bool full() const { return _length == capacity; } 388 389 /** Get length. */ 390 auto length() const { return _length; } 391 alias opDollar = length; /// ditto 392 393 static if (isSomeChar!T) 394 { 395 /** Get as `string`. */ 396 scope const(T)[] toString() const return 397 { 398 return opSlice(); 399 } 400 } 401 } 402 403 /** Comparison for equality. */ 404 bool opEquals()(const scope auto ref typeof(this) rhs) const 405 { 406 return this[] == rhs[]; 407 } 408 /// ditto 409 bool opEquals(U)(const scope U[] rhs) const 410 if (is(typeof(T[].init == U[].init))) 411 { 412 return this[] == rhs; 413 } 414 } 415 416 /** Stack-allocated string of maximum length of `capacity.` 417 * 418 * Similar to `mir.small_string` at http://mir-algorithm.libmir.org/mir_small_string.html. 419 */ 420 alias StringN(uint capacity, bool borrowChecked = false) = FixedArray!(immutable(char), capacity, borrowChecked); 421 422 /** Stack-allocated wstring of maximum length of `capacity.` */ 423 alias WStringN(uint capacity, bool borrowChecked = false) = FixedArray!(immutable(wchar), capacity, borrowChecked); 424 425 /** Stack-allocated dstring of maximum length of `capacity.` */ 426 alias DStringN(uint capacity, bool borrowChecked = false) = FixedArray!(immutable(dchar), capacity, borrowChecked); 427 428 /** Stack-allocated mutable string of maximum length of `capacity.` */ 429 alias MutableStringN(uint capacity, bool borrowChecked = false) = FixedArray!(char, capacity, borrowChecked); 430 431 /** Stack-allocated mutable wstring of maximum length of `capacity.` */ 432 alias MutableWStringN(uint capacity, bool borrowChecked = false) = FixedArray!(char, capacity, borrowChecked); 433 434 /** Stack-allocated mutable dstring of maximum length of `capacity.` */ 435 alias MutableDStringN(uint capacity, bool borrowChecked = false) = FixedArray!(char, capacity, borrowChecked); 436 437 /// construct from array may throw 438 @safe pure unittest 439 { 440 enum capacity = 3; 441 alias T = int; 442 alias A = FixedArray!(T, capacity); 443 static assert(!mustAddGCRange!A); 444 445 auto a = A([1, 2, 3].s[]); 446 assert(a[] == [1, 2, 3].s); 447 } 448 449 /// unsafe construct from array 450 @trusted pure nothrow @nogc unittest 451 { 452 enum capacity = 3; 453 alias T = int; 454 alias A = FixedArray!(T, capacity); 455 static assert(!mustAddGCRange!A); 456 457 auto a = A.fromValuesUnsafe([1, 2, 3].s); 458 assert(a[] == [1, 2, 3].s); 459 } 460 461 /// construct from scalars is nothrow 462 @safe pure nothrow @nogc unittest 463 { 464 enum capacity = 3; 465 alias T = int; 466 alias A = FixedArray!(T, capacity); 467 static assert(!mustAddGCRange!A); 468 469 auto a = A(1, 2, 3); 470 assert(a[] == [1, 2, 3].s); 471 472 static assert(!__traits(compiles, { auto _ = A(1, 2, 3, 4); })); 473 } 474 475 /// scope checked string 476 @safe pure unittest 477 { 478 enum capacity = 15; 479 foreach (StrN; AliasSeq!(StringN// , WStringN, DStringN 480 )) 481 { 482 alias String15 = StrN!(capacity); 483 484 typeof(String15.init[0])[] xs; 485 auto x = String15("alphas"); 486 487 assert(x[0] == 'a'); 488 assert(x[$ - 1] == 's'); 489 490 assert(x[0 .. 2] == "al"); 491 assert(x[] == "alphas"); 492 493 const y = String15("åäö_åäöå"); // fits in 15 chars 494 } 495 } 496 497 /// scope checked string 498 pure unittest 499 { 500 enum capacity = 15; 501 foreach (Str; AliasSeq!(StringN!capacity, 502 WStringN!capacity, 503 DStringN!capacity)) 504 { 505 static assert(!mustAddGCRange!Str); 506 static if (isDIP1000) 507 { 508 static assert(!__traits(compiles, { 509 auto f() @safe pure 510 { 511 auto x = Str("alphas"); 512 auto y = x[]; 513 return y; // errors with -dip1000 514 } 515 })); 516 } 517 } 518 } 519 520 @safe pure unittest 521 { 522 static assert(mustAddGCRange!(FixedArray!(string, 1, false))); 523 static assert(mustAddGCRange!(FixedArray!(string, 1, true))); 524 static assert(mustAddGCRange!(FixedArray!(string, 2, false))); 525 static assert(mustAddGCRange!(FixedArray!(string, 2, true))); 526 } 527 528 /// 529 @safe pure unittest 530 { 531 import std.exception : assertNotThrown; 532 533 alias T = char; 534 enum capacity = 3; 535 536 alias A = FixedArray!(T, capacity, true); 537 static assert(!mustAddGCRange!A); 538 static assert(A.sizeof == T.sizeof*capacity + 1); 539 540 import std.range.primitives : isOutputRange; 541 static assert(isOutputRange!(A, T)); 542 543 auto ab = A("ab"); 544 assert(!ab.empty); 545 assert(ab[0] == 'a'); 546 assert(ab.front == 'a'); 547 assert(ab.back == 'b'); 548 assert(ab.length == 2); 549 assert(ab[] == "ab"); 550 assert(ab[0 .. 1] == "a"); 551 assertNotThrown(ab.insertBack('_')); 552 assert(ab[] == "ab_"); 553 ab.popBack(); 554 assert(ab[] == "ab"); 555 assert(ab.toString == "ab"); 556 557 ab.popBackN(2); 558 assert(ab.empty); 559 assertNotThrown(ab.insertBack('a', 'b')); 560 561 const abc = A("abc"); 562 assert(!abc.empty); 563 assert(abc.front == 'a'); 564 assert(abc.back == 'c'); 565 assert(abc.length == 3); 566 assert(abc[] == "abc"); 567 assert(ab[0 .. 2] == "ab"); 568 assert(abc.full); 569 static assert(!__traits(compiles, { const abcd = A('a', 'b', 'c', 'd'); })); // too many elements 570 571 assert(ab[] == "ab"); 572 ab.popFront(); 573 assert(ab[] == "b"); 574 575 const xy = A("xy"); 576 assert(!xy.empty); 577 assert(xy[0] == 'x'); 578 assert(xy.front == 'x'); 579 assert(xy.back == 'y'); 580 assert(xy.length == 2); 581 assert(xy[] == "xy"); 582 assert(xy[0 .. 1] == "x"); 583 584 const xyz = A("xyz"); 585 assert(!xyz.empty); 586 assert(xyz.front == 'x'); 587 assert(xyz.back == 'z'); 588 assert(xyz.length == 3); 589 assert(xyz[] == "xyz"); 590 assert(xyz.full); 591 static assert(!__traits(compiles, { const xyzw = A('x', 'y', 'z', 'w'); })); // too many elements 592 } 593 594 /// 595 @safe pure unittest 596 { 597 static void testAsSomeString(T)() 598 { 599 enum capacity = 15; 600 alias A = FixedArray!(immutable(T), capacity); 601 static assert(!mustAddGCRange!A); 602 auto a = A("abc"); 603 assert(a[] == "abc"); 604 assert(a[].equal("abc")); 605 606 import std.conv : to; 607 const x = "a".to!(T[]); 608 } 609 610 foreach (T; AliasSeq!(char// , wchar, dchar 611 )) 612 { 613 testAsSomeString!T(); 614 } 615 } 616 617 /// equality 618 @safe pure unittest 619 { 620 enum capacity = 15; 621 alias S = FixedArray!(int, capacity); 622 static assert(!mustAddGCRange!S); 623 624 assert(S([1, 2, 3].s[]) == 625 S([1, 2, 3].s[])); 626 assert(S([1, 2, 3].s[]) == 627 [1, 2, 3]); 628 } 629 630 @safe pure unittest 631 { 632 class C { int value; } 633 alias S = FixedArray!(C, 2); 634 static assert(mustAddGCRange!S); 635 } 636 637 /// `insertBackMaybe` is nothrow @nogc. 638 @safe pure nothrow @nogc unittest 639 { 640 alias S = FixedArray!(int, 2); 641 S s; 642 assert(s.insertBackMaybe(42)); 643 assert(s.insertBackMaybe(43)); 644 assert(!s.insertBackMaybe(0)); 645 assert(s.length == 2); 646 } 647 648 /// equality 649 @system pure nothrow @nogc unittest 650 { 651 enum capacity = 15; 652 alias S = FixedArray!(int, capacity); 653 654 assert(S.fromValuesUnsafe([1, 2, 3].s) == 655 S.fromValuesUnsafe([1, 2, 3].s)); 656 657 const ax = [1, 2, 3].s; 658 assert(S.fromValuesUnsafe([1, 2, 3].s) == ax); 659 assert(S.fromValuesUnsafe([1, 2, 3].s) == ax[]); 660 661 const cx = [1, 2, 3].s; 662 assert(S.fromValuesUnsafe([1, 2, 3].s) == cx); 663 assert(S.fromValuesUnsafe([1, 2, 3].s) == cx[]); 664 665 immutable ix = [1, 2, 3].s; 666 assert(S.fromValuesUnsafe([1, 2, 3].s) == ix); 667 assert(S.fromValuesUnsafe([1, 2, 3].s) == ix[]); 668 } 669 670 /// assignment from `const` to `immutable` element type 671 @safe pure unittest 672 { 673 enum capacity = 15; 674 alias String15 = StringN!(capacity); 675 static assert(!mustAddGCRange!String15); 676 677 const char[4] _ = ['a', 'b', 'c', 'd']; 678 auto x = String15(_[]); 679 assert(x.length == 4); 680 assert(x[] == "abcd"); 681 } 682 683 /// borrow checking 684 @system pure unittest 685 { 686 enum capacity = 15; 687 alias String15 = StringN!(capacity, true); 688 static assert(String15.readBorrowCountMax == 7); 689 static assert(!mustAddGCRange!String15); 690 691 auto x = String15("alpha"); 692 693 assert(x[].equal("alpha") && 694 x[].equal("alpha")); 695 696 { 697 auto xw1 = x[]; 698 assert(x.isWriteBorrowed); 699 assert(x.isBorrowed); 700 } 701 702 auto xr1 = (cast(const)x)[]; 703 assert(x.readBorrowCount == 1); 704 705 auto xr2 = (cast(const)x)[]; 706 assert(x.readBorrowCount == 2); 707 708 auto xr3 = (cast(const)x)[]; 709 assert(x.readBorrowCount == 3); 710 711 auto xr4 = (cast(const)x)[]; 712 assert(x.readBorrowCount == 4); 713 714 auto xr5 = (cast(const)x)[]; 715 assert(x.readBorrowCount == 5); 716 717 auto xr6 = (cast(const)x)[]; 718 assert(x.readBorrowCount == 6); 719 720 auto xr7 = (cast(const)x)[]; 721 assert(x.readBorrowCount == 7); 722 723 assertThrown!AssertError((cast(const)x)[]); 724 } 725 726 version(unittest) 727 { 728 import std.algorithm.comparison : equal; 729 import std.meta : AliasSeq; 730 import std.exception : assertThrown; 731 import core.exception : AssertError; 732 733 import nxt.array_help : s; 734 import nxt.container_traits : mustAddGCRange; 735 import nxt.dip_traits : isDIP1000; 736 }