آپ ایک ایسی ٹائپ لکھتے ہیں جو نییسٹڈ (nested) آبجیکٹس میں سفر کرتی ہے، تاکہ آٹو مکمل (autocomplete) کے لیے ڈاٹ سے الگ کیے گئے (dot-separated) راستے بنائے جا سکیں۔ یہ ایک چھوٹے ٹیسٹ آبجیکٹ پر بہترین کام کرتی ہے۔ پھر آپ اسے ایک حقیقی API پے لوڈ (payload) پر استعمال کرتے ہیں، اور ایڈیٹر فریز ہو جاتا ہے۔ آخر کار، TypeScript یہ ایرر دیتا ہے: TS2589: Type instantiation is excessively deep and possibly infinite.
اس پیغام کا مطلب یہ نہیں ہے کہ آپ کے کوڈ میں روایتی معنوں میں کوئی انفینٹ لوپ (infinite loop) ہے۔ اس کا مطلب یہ ہے کہ کمپائلر نے ہار مان لی۔ جس ٹائپ کو آپ نے کیلکولیٹ کرنے کے لیے کہا تھا، وہ یا تو واقعی غیر محدود (unbounded) تھی، یا اتنی بڑی تھی کہ اس کا جائزہ لینے سے TypeScript کی اندرونی حدود ختم ہو جاتیں۔ جب ایسا ہوتا ہے، تو کمپائلر آپ کے IDE کو ہینگ ہونے سے بچانے کے لیے کام روک دیتا ہے۔
جب TS2589 ظاہر ہو
ریکرسو ٹائپس (Recursive types) اس کی سب سے عام وجہ ہیں۔ TypeScript ٹائپس کا جائزہ تیزی سے لیتا ہے، اور اگر کوئی یوٹیلیٹی ٹائپ بار بار خود کو کال کرتی رہے—خاص طور پر کنڈیشنل لاجک کے ذریعے—تو کمپیوٹیشن اسٹیک (computation stack) تیزی سے بڑھ جاتا ہے۔ آپ عام طور پر ان مخصوص حالات میں اس رکاوٹ کا سامنا کریں گے:
- ریکرسو کنڈیشنل ٹائپس (Recursive conditional types) جو کسی ٹیپل (tuple)، آبجیکٹ، یا اسٹرنگ ٹیمپلیٹ کو بیس کیس (base case) تک پہنچنے تک بار بار ڈی اسٹرکچر کرتی ہیں۔
- گہرائی میں موجود نییسٹڈ آبجیکٹ پاتھ جنریٹرز (Deeply nested object path generators)، جو
{ user: { address: { street: string } } }جیسی ساختوں کو"user" | "user.address" | "user.address.street"جیسے اسٹرنگ لٹرلز کے یونین (unions) میں تبدیل کر دیتے ہیں۔ - ٹیمپلیٹ لٹرل ٹائپس (Template literal types) جو اسٹرنگز کو ایک ایک کر کے یا ایک ایک ٹوکن کے حساب سے پارس کرتی ہیں۔
- میپڈ ٹائپس (Mapped types) جو درجنوں کیز (keys) اور متعدد لیولز والے آبجیکٹس پر عمل کرتی ہیں۔
- کنڈیشنل ٹائپس (Conditional types) جو بڑے یونینز پر تقسیم (distribute) ہوتی ہیں، جس سے ہر ممبر پر کام کا بوجھ خاموشی سے بڑھ جاتا ہے۔
نییسٹڈ پاتھ کی مثال خاص طور پر پرکشش ہے۔ فارم لائبریریز اور اسٹیٹ مینجمنٹ ٹولز فیلڈ کے ناموں کے لیے ٹائپڈ پاتھ (typed paths) پیش کرنا پسند کرتے ہیں تاکہ آپ کو آٹو مکمل مل سکے۔ ایک سطحی (shallow) آبجیکٹ پر، ہر قانونی ڈاٹ-پاتھ کو اسٹرنگ یونین کے طور پر بنانا معمولی کام ہے۔ لیکن ایک گہرے یا وسیع آبجیکٹ پر، وہ یونین بہت زیادہ بڑھ جاتا ہے۔ TypeScript کو ایک ہی وقت میں ہر ممکنہ ترتیب (permutation) کو ورکنگ میموری میں رکھنا پڑتا ہے۔ ایک خاص گہرائی پر، کمپائلر محسوس کرتا ہے کہ کام اس کے بجٹ سے باہر جا رہا ہے اور وہ ایمرجنسی بریک لگا دیتا ہے۔
حل نمبر ایک: ایک سخت گہرائی کی حد (Hard Depth Limit) مقرر کریں
TS2589 کو حل کرنے کا سب سے براہ راست طریقہ یہ ہے کہ آپ یہ دکھاوا کرنا چھوڑ دیں کہ آپ کی ٹائپ ہمیشہ کے لیے ریکرس (recurse) کر سکتی ہے۔ ایک ڈیپتھ کاؤنٹر (depth counter) متعارف کروائیں جو سرکٹ بریکر کے طور پر کام کرے۔
عملی طور پر، اس کا مطلب ایک نیومیرک جینیرک پیرامیٹر شامل کرنا ہے—جسے اکثر ایک ٹیپل کے طور پر ظاہر کیا جاتا ہے جس کی لمبائی کم ہوتی جاتی ہے—جو ہر بار ٹائپ کے ریکرس ہونے پر کم ہو جاتا ہے۔ جب کاؤنٹر صفر پر پہنچتا ہے، تو ٹائپ مزید گہرائی میں جانے کے بجائے string جیسا کوئی وسیع متبادل (fallback) واپس کر دیتی ہے۔ صارفین کو پہلے چار یا پانچ لیولز کے لیے اب بھی درست آٹو مکمل ملتا ہے، جو کہ حقیقی دنیا کے آبجیکٹس کے بڑے حصے کو کور کرتا ہے۔ اس کے بعد، کمپائلر صرف ٹائپ کو وسیع کر دیتا ہے اور آگے بڑھ جاتا ہے۔
یہ طریقہ آپ کی یوٹیلیٹی ٹائپ کو کسی بھی معنی میں غلط نہیں بناتا۔ یہ اسے محدود (bounded) بنا دیتا ہے۔ ایک ایسا ٹائپ سسٹم جو کمپائلر کو کریش کر دے، وہ اس سسٹم سے زیادہ مفید نہیں ہے جو ایک مناسب گہرائی کے بعد باوقار طریقے سے ہار مان لے۔
حل نمبر دو: ایک وقت میں صرف ایک پاتھ کی تصدیق کریں
اگر تمام ممکنہ راستوں کو پہلے سے بنانا بہت مہنگا ہے، تو معاہدے (contract) کو بدل دیں۔ تمام درست اسٹرنگز کا ایک بہت بڑا یونین بنانے کے بجائے، ایک ایسی ٹائپ لکھیں جو یہ چیک کرے کہ آیا کوئی ایک مخصوص اسٹرنگ ایک درست پاتھ ہے یا نہیں۔
ہر انگریزی لفظ کی ڈکشنری بنانے اور ایک لفظ کے درست ہجے (spelling) چیک کرنے کے درمیان فرق کے بارے میں سوچیں۔ پہلا ایک بہت بڑا ڈیٹا اسٹرکچر ہے؛ دوسرا ایک ہلکا پھلکا اسکین ہے۔ TypeScript کی اصطلاح میں، ایک Paths<T> یوٹیلیٹی ایکسپورٹ کرنے کے بجائے جو "user.address.street" | "user.settings.theme" | ... فراہم کرے، آپ IsValidPath<T, "user.address.street"> جیسی چیز ایکسپورٹ کریں۔ کمپائلر صرف اسی پاتھ کا جائزہ لیتا ہے جو آپ اصل میں پاس کرتے ہیں۔
یہ تبدیلی آپ کے API ڈیزائن کرنے کے طریقے کو بدل دیتی ہے۔ آپ کے فنکشن سگنیچر (function signatures) ایک اسٹرنگ قبول کر سکتے ہیں اور پھر آبجیکٹ کی ساخت کے خلاف اس کی تصدیق کے لیے جینیرک کنسٹرینٹ (generic constraint) کا استعمال کر سکتے ہیں۔ اگر ڈویلپر غلط پاتھ ٹائپ کرتا ہے تو IDE اب بھی شکایت کرے گا، لیکن کمپائلر کو ٹائپ چیکنگ کے دوران قانونی راستوں کا مکمل سیٹ بنانے کی ضرورت نہیں پڑے گی۔ بڑے آبجیکٹس کے لیے، کارکردگی کا فرق بہت زیادہ ہوتا ہے۔
فوری حکمت عملی جو آپ کو آگے بڑھاتی ہے
ان دو ساختی حلوں کے علاوہ، چند چھوٹی عادتیں ریکرسو ٹائپس کو حد عبور کرنے سے روک سکتی ہیں:
ٹائپ پیرامیٹرز کو ٹپلز (tuples) میں لپیٹیں تاکہ تقسیم (distribution) کو روکا جا سکے۔ ایک کنڈیشنل میں ننگا (naked) ٹائپ پیرامیٹر، جیسے کہ
T extends Foo ? Bar : Baz، جبTایک یونین (union) ہو تو چیک کو ہر ممبر پر تقسیم کر دیتا ہے۔ اگر اس یونین میں پچاس ممبرز ہوں، تو TypeScript پچاس الگ الگ instantiations کرتا ہے۔[T] extends [Foo] ? Bar : Bazلکھنے سے کنڈیشنل کا جائزہ پوری یونین کے خلاف صرف ایک بار لیا جاتا ہے۔ اس کا استعمال تب کریں جب آپ کو حقیقت میں ٹائپ کو ہر یونین ممبر پر انفرادی طور پر میپ (map) کرنے کی ضرورت نہ ہو۔ڈیبگنگ کے دوران اپنے ان پٹس کو چھوٹا کریں۔ جب TS2589 ظاہر ہو، تو اپنے پروڈکشن آبجیکٹ ٹائپ کو دو پراپرٹیز اور ایک لیول کی نیسٹنگ (nesting) والے ایک چھوٹے سے اسٹب (stub) سے بدل دیں۔ اگر غلطی ختم ہو جائے، تو آپ نے اس بات کی تصدیق کر لی ہے کہ مسئلہ گہرائی (depth) یا کارڈینالٹی (cardinality) کا ہے، نہ کہ سنٹیکس کی غلطی کا۔ یہ آپ کو ایسی لاجک کو دوبارہ لکھنے سے بچاتا ہے جو اصل میں ساختی طور پر ٹھیک تھی۔
عوامی سطح پر نظر آنے والی API ٹائپس کو نرم رکھیں۔ اندرونی طور پر، آپ کو انتہائی درستگی کی ضرورت ہو سکتی ہے۔ بیرونی طور پر، کبھی کبھی کمال (perfection) حاصل کرنے کی قیمت اس کے فائدے سے زیادہ ہوتی ہے۔ اگر ایک تھوڑی سی وسیع آٹو کمپلیٹ (autocomplete) ٹائپ ایڈیٹر میں دو سیکنڈ کی تاخیر کو روکتی ہے، تو یہ سودا عام طور پر فائدہ مند ہوتا ہے۔ آپ ٹیسٹنگ کے دوران غلط راستوں کو پکڑنے کے لیے اس لوز (looser) ٹائپ کو رن ٹائم ویلیڈیٹر (runtime validator) کے ساتھ جوڑ سکتے ہیں۔
TypeScript اس حد کو کیوں نافذ کرتا ہے
TypeScript ہالٹنگ پرابلم (halting problem) کو حل نہیں کر سکتا۔ اسے یہ معلوم نہیں ہوتا کہ آپ کی ریکرسیو ٹائپ (recursive type) آخر کار ختم ہو جائے گی یا ہمیشہ کے لیے گھومتی رہے گی۔ کمپائلر کے اندر انفینٹ لوپ (infinite loop) کا خطرہ مول لینے کے بجائے، یہ ایک محتاط کٹ آف (cutoff) نافذ کرتا ہے۔ کبھی کبھی وہ کٹ آف ایسی ٹائپ کو روک دیتا ہے جو اگر کافی وقت ملتا تو مکمل ہو جاتی۔ TS2589 دراصل کمپائلر کا یہ اعتراف ہے کہ وہ پچھتاوے سے بہتر حفاظت کو ترجیح دیتا ہے۔
اس حد کا احترام کرنا پروڈکشن گریڈ (production-grade) ٹائپس لکھنے کا حصہ ہے۔ ٹائپ کی تعریف وہ کوڈ ہے جو کمپائلر میں چلتا ہے، اور مہنگا کوڈ حقیقی نتائج رکھتا ہے۔ سست آٹو کمپلیٹ ڈویلپر کی رفتار (velocity) کو اسی طرح متاثر کرتی ہے جیسے سست رن ٹائم کوڈ صارف کے تجربے (user experience) کو نقصان پہنچاتا ہے۔
اصل سبق
TS2589 اس بات کا اشارہ نہیں ہے کہ آپ ایک برے ٹائپ سسٹم پروگرامر ہیں۔ یہ اس بات کا اشارہ ہے کہ آپ کی ٹائپ ایک ہی وقت میں بہت زیادہ کام کر رہی ہے۔ اپنی ریکرشن (recursion) کو محدود کریں، lazy طریقے سے ویلیڈیٹ کریں، اور غیر ضروری تقسیم (distribution) سے بچیں۔ ایڈوانسڈ ٹائپس کا مقصد کمپائل ٹائم پر ہر ممکن حقیقت کو ثابت کرنا نہیں ہے؛ بلکہ اس کا مقصد اپنی ٹیم کو تیز اور قابل اعتماد ٹولنگ (tooling) فراہم کرنا ہے۔ ایک ایسی ٹائپ جو ملی سیکنڈز میں کمپائل ہو جائے اور پچانوے فیصد کیسز کا احاطہ کرے، اس ٹائپ سے کہیں زیادہ قیمتی ہے جو نظریاتی طور پر مکمل ہو لیکن لینگویج سرور کو کریش کر دے۔
