మీరు నెస్టెడ్ ఆబ్జెక్ట్ల (nested objects) ద్వారా వెళ్తూ, ఆటోకంప్లీట్ కోసం డాట్-సెపరేటెడ్ పాత్లను (dot-separated paths) నిర్మించే ఒక టైప్ను రాస్తారు. ఇది ఒక చిన్న టెస్ట్ ఆబ్జెక్ట్పై అద్భుతంగా పనిచేస్తుంది. కానీ మీరు దానిని ఒక రియల్ API పేలోడ్పై ఉపయోగిస్తే, ఎడిటర్ ఫ్రీజ్ అవుతుంది. చివరికి, TypeScript TS2589 ఎర్రర్ను చూపిస్తుంది: Type instantiation is excessively deep and possibly infinite.
ఈ సందేశం మీ కోడ్లో సాంప్రదాయక అర్థంలో ఇన్ఫినిట్ లూప్ (infinite loop) ఉందని అర్థం కాదు. అంటే కంపైలర్ ప్రయత్నాన్ని వదిలేసిందని అర్థం. మీరు లెక్కించమని కోరిన టైప్ నిజంగానే అపరిమితంగా ఉండవచ్చు, లేదా పరిమితమైనప్పటికీ, దానిని లెక్కించడం వల్ల TypeScript యొక్క అంతర్గత పరిమితులు ముగిసిపోవచ్చు. ఇలా జరిగినప్పుడు, మీ IDE హ్యాంగ్ అవ్వకముందే కంపైలర్ ఆగిపోతుంది.
TS2589 ఎప్పుడు కనిపిస్తుంది
రికర్శివ్ టైప్స్ (Recursive types) దీనికి అత్యంత సాధారణ కారణం. TypeScript టైప్లను వేగంగా (eagerly) ఎవాల్యుయేట్ చేస్తుంది, మరియు ఒక యుటిలిటీ టైప్ తనను తాను మళ్ళీ మళ్ళీ పిలుచుకుంటే—ముఖ్యంగా కండిషనల్ లాజిక్ ద్వారా—కంప్యూటేషన్ స్టాక్ వేగంగా పెరుగుతుంది. మీరు సాధారణంగా ఈ క్రింది కొన్ని సందర్భాలలో ఈ సమస్యను ఎదుర్కొంటారు:
- రికర్శివ్ కండిషనల్ టైప్స్ (Recursive conditional types): ఇవి బేస్ కేస్ (base case) చేరుకునే వరకు టపుల్ (tuple), ఆబ్జెక్ట్ లేదా స్ట్రింగ్ టెంప్లేట్ను పదేపదే డీస్ట్రక్చర్ చేస్తాయి.
- డీప్లీ నెస్టెడ్ ఆబ్జెక్ట్ పాత్ జనరేటర్స్ (Deeply nested object path generators): ఇవి
{ user: { address: { street: string } } }వంటి నిర్మాణాలను"user" | "user.address" | "user.address.street"వంటి స్ట్రింగ్ లిటరల్స్ యూనియన్లుగా మారుస్తాయి. - టెంప్లేట్ లిటరల్ టైప్స్ (Template literal types): ఇవి స్ట్రింగ్లను అక్షరం అక్షరం లేదా టోకెన్ టోకెన్గా పార్స్ చేస్తాయి.
- మ్యాప్డ్ టైప్స్ (Mapped types): డజన్ల కొద్దీ కీలు మరియు బహుళ స్థాయిలు ఉన్న ఆబ్జెక్ట్లపై ఇటరేట్ చేస్తాయి.
- కండిషనల్ టైప్స్ (Conditional types): ఇవి పెద్ద యూనియన్లపై డిస్ట్రిబ్యూట్ అయ్యి, ప్రతి మెంబర్పై పనిభారాన్ని నిశ్శబ్దంగా పెంచుతాయి.
నెస్టెడ్ పాత్ ఉదాహరణ ప్రత్యేకంగా ప్రలోభపెట్టేది. ఫామ్ లైబ్రరీలు మరియు స్టేట్-మేనేజ్మెంట్ టూల్స్ ఫీల్డ్ పేర్ల కోసం ఆటోకంప్లీట్ పొందడానికి టైప్డ్ పాత్లను అందించడానికి ఇష్టపడతాయి. ఒక షాలో ఆబ్జెక్ట్ (shallow object) పై, ప్రతి లీగల్ డాట్-పాత్ను స్ట్రింగ్ యూనియన్గా రూపొందించడం చాలా సులభం. కానీ ఒక డీప్ లేదా వైడ్ ఆబ్జెక్ట్ పై, ఆ యూనియన్ విస్ఫోటనం చెందుతుంది. TypeScript ప్రతి పర్ముటేషన్ను ఒకేసారి వర్కింగ్ మెమరీలో ఉంచుకోవాలి. ఒక నిర్దిష్ట లోతు వద్ద, పని తన బడ్జెట్ కంటే ఎక్కువగా ఉందని కంపైలర్ గుర్తించి, ఎమర్జెన్సీ బ్రేక్ వేస్తుంది.
పరిష్కారం 1: హార్డ్ డెప్త్ లిమిట్ను జోడించండి
TS2589ని పరిష్కరించడానికి అత్యంత ప్రత్యక్ష మార్గం ఏమిటంటే, మీ టైప్ అనంతంగా రికర్షన్ చేయగలదని నమ్మడం మానేయడం. సర్క్యూట్ బ్రేకర్గా పనిచేసే ఒక డెప్త్ కౌంటర్ను పరిచయం చేయండి.
ప్రాక్టికల్గా చెప్పాలంటే, టైప్ ప్రతిసారీ రికర్షన్ అయినప్పుడు తగ్గేలా ఒక నంబరిక్ జెనరిక్ పారామీటర్ను (తరచుగా పొడవు తగ్గుతూ ఉండే టపుల్గా సూచించబడుతుంది) జోడించడం అని అర్థం. కౌంటర్ సున్నాకి చేరుకున్నప్పుడు, టైప్ ఇంకా లోతుగా వెళ్లకుండా string వంటి బ్రాడ్ ఫాల్బ్యాక్ను తిరిగి ఇస్తుంది. వినియోగదారులు మొదటి నాలుగు లేదా ఐదు స్థాయిల వరకు ఖచ్చితమైన ఆటోకంప్లీట్ను పొందుతారు, ఇది వాస్తవ ప్రపంచ ఆబ్జెక్ట్లలోని మెజారిటీని కవర్ చేస్తుంది. ఆ తర్వాత, కంపైలర్ కేవలం టైప్ను వైడెన్ (widen) చేసి ముందుకు వెళ్తుంది.
ఈ విధానం మీ యుటిలిటీ టైప్ను ఏ విధంగానూ తప్పుగా చేయదు. ఇది దానిని పరిమితం (bounded) చేస్తుంది. కంపైలర్ను క్రాష్ చేసే టైప్ సిస్టమ్ కంటే, ఒక సహేతుకమైన లోతు తర్వాత సున్నితంగా వెనక్కి తగ్గే టైప్ సిస్టమ్ ఎక్కువ ఉపయోగకరంగా ఉంటుంది.
పరిష్కారం 2: ఒక సమయంలో ఒక పాత్ను మాత్రమే వాలిడేట్ చేయండి
అన్ని సాధ్యమయ్యే పాత్లను ముందే రూపొందించడం ఖరీదైనది అయితే, కాంట్రాక్ట్ను మార్చండి. అన్ని వాలిడ్ స్ట్రింగ్ల భారీ యూనియన్ను రూపొందించడానికి బదులుగా, ఒక నిర్దిష్ట స్ట్రింగ్ వాలిడ్ పాత్ అవునా కాదా అని తనిఖీ చేసే టైప్ను రాయండి.
ప్రతి ఇంగ్లీష్ పదం యొక్క డిక్షనరీని సృష్టించడానికి మరియు ఒకే పదం సరిగ్గా స్పెల్లింగ్ ఉందో లేదో తనిఖీ చేయడానికి మధ్య ఉన్న తేడాను ఆలోచించండి. మొదటిది ఒక భారీ డేటా స్ట్రక్చర్; రెండవది ఒక లైట్వెయిట్ స్కాన్. TypeScript పరంగా చెప్పాలంటే, "user.address.street" | "user.settings.theme" | ... ఇచ్చే Paths<T> యుటిలిటీని ఎగుమతి చేసే బదులు, IsValidPath<T, "user.address.street"> వంటి దానిని ఎగుమతి చేయండి. మీరు పంపిన పాత్ను మాత్రమే కంపైలర్ ఎవాల్యుయేట్ చేస్తుంది.
ఈ మార్పు మీరు APIలను ఎలా డిజైన్ చేస్తారో మారుస్తుంది. మీ ఫంక్షన్ సిగ్నేచర్లు ఒక స్ట్రింగ్ను స్వీకరించవచ్చు మరియు ఆబ్జెక్ట్ షేప్తో దానిని ధృవీకరించడానికి జెనరిక్ కన్స్ట్రెయింట్ను ఉపయోగించవచ్చు. డెవలపర్ తప్పు పాత్ను టైప్ చేస్తే IDE ఇంకా ఫిర్యాదు చేస్తుంది, కానీ టైప్-చెకింగ్ సమయంలో కంపైలర్ అన్ని లీగల్ పాత్లను సృష్టించాల్సిన అవసరం ఉండదు. పెద్ద ఆబ్జెక్ట్ల విషయంలో, పనితీరులో తేడా చాలా స్పష్టంగా ఉంటుంది.
మిమ్మల్ని ముందుకు నడిపించే వేగవంతమైన వ్యూహాలు
ఈ రెండు స్ట్రక్చరల్ ఫిక్స్ల కాకుండా, కొన్ని చిన్న అలవాట్లు రికర్సివ్ టైప్స్ పరిమితిని దాటకుండా చూస్తాయి:
టైప్ పారామీటర్లను టపుల్స్లో (tuples) చుట్టి డిస్ట్రిబ్యూషన్ను నిరోధించండి.
Tఅనేది ఒక యూనియన్ (union) అయినప్పుడు,T extends Foo ? Bar : Bazవంటి కండిషనల్లో ఉండే నేరుగా ఉన్న టైప్ పారామీటర్, ప్రతి మెంబర్పై చెక్ను విడగొడుతుంది (distributes). ఆ యూనియన్లో యాభై మెంబర్లు ఉంటే, TypeScript యాభై వేర్వేరు ఇన్స్టాంటియేషన్లను (instantiations) చేస్తుంది.[T] extends [Foo] ? Bar : Bazఅని రాయడం వల్ల, ఆ కండిషనల్ మొత్తం యూనియన్పై ఒకేసారి ఎవాల్యుయేట్ చేయబడుతుంది. టైప్ ప్రతి యూనియన్ మెంబర్పై విడివిడిగా మ్యాప్ అవ్వాల్సిన అవసరం లేనప్పుడు దీనిని ఉపయోగించండి.డీబగ్గింగ్ చేసేటప్పుడు మీ ఇన్పుట్లను తగ్గించండి. TS2589 ఎర్రర్ వచ్చినప్పుడు, మీ ప్రొడక్షన్ ఆబ్జెక్ట్ టైప్ను రెండు ప్రాపర్టీలు మరియు ఒక లెవల్ నెస్టింగ్ ఉన్న చిన్న స్టబ్ (stub)తో మార్చండి. ఒకవేళ ఎర్రర్ పోతే, సమస్య సింటాక్స్ తప్పు కాదు, డెప్త్ (depth) లేదా కార్డినాలిటీ (cardinality) అని మీరు నిర్ధారించుకోవచ్చు. దీనివల్ల నిర్మాణాత్మకంగా సరిగ్గా ఉన్న లాజిక్ను మళ్ళీ రాయాల్సిన అవసరం ఉండదు.
పబ్లిక్-ఫేసింగ్ API టైప్స్ను కొంత సరళీకరించండి. అంతర్గతంగా (Internally), మీకు అత్యంత ఖచ్చితత్వం అవసరం కావచ్చు. కానీ బాహ్యంగా (Externally), పరిపూర్ణత కోసం చేసే ప్రయత్నం కొన్నిసార్లు లాభం కంటే నష్టమే ఎక్కువ కలిగిస్తుంది. ఎడిటర్లో రెండు సెకన్ల లాగ్ (lag) తగ్గించడానికి కొంచెం వెడల్పాటి (wider) ఆటోకంప్లీట్ టైప్ను వాడటం వల్ల కలిగే ప్రయోజనం చాలా ఎక్కువ. టెస్టింగ్ సమయంలో తప్పు మార్గాలను (bad paths) గుర్తించడానికి, మీరు ఆ లూజ్ టైప్ను రన్టైమ్ వాలిడేటర్తో జత చేయవచ్చు.
TypeScript ఈ పరిమితిని ఎందుకు విధిస్తుంది
TypeScript 'హాల్టింగ్ ప్రాబ్లమ్' (halting problem)ను పరిష్కరించలేదు. మీ రికర్సివ్ టైప్ (recursive type) చివరికి ముగుస్తుందో లేక అనంతంగా కొనసాగుతుందో దానికి తెలియదు. కంపైలర్ లోపల ఇన్ఫినిట్ లూప్ (infinite loop) వచ్చే ప్రమాదం కంటే, అది ఒక పరిమితిని (conservative cutoff) విధిస్తుంది. కొన్నిసార్లు తగినంత సమయం ఇస్తే పూర్తయ్యే టైప్ను కూడా ఆ కటాఫ్ ఆపేయవచ్చు. TS2589 అనేది "తప్పు జరగడం కంటే జాగ్రత్తగా ఉండటమే మేలు" అని కంపైలర్ ఒప్పుకోవడం వంటిది.
ఆ పరిమితిని గౌరవించడం అనేది ప్రొడక్షన్-గ్రేడ్ టైప్స్ను వ్రాయడంలో ఒక భాగం. టైప్ డెఫినిషన్ అనేది కంపైలర్లో రన్ అయ్యే కోడ్, మరియు ఖరీదైన (expensive) కోడ్ వల్ల నిజమైన పరిణామాలు ఉంటాయి. స్లో రన్టైమ్ కోడ్ యూజర్ ఎక్స్పీరియన్స్ను ఎలా దెబ్బతీస్తుందో, స్లో ఆటోకంప్లీట్ కూడా డెవలపర్ వేగాన్ని (velocity) అంతే దెబ్బతీస్తుంది.
అసలైన సారాంశం
TS2589 అనేది మీరు ఒక చెడ్డ టైప్-సిస్టమ్ ప్రోగ్రామర్ అని చెప్పే సంకేతం కాదు. మీ టైప్ ఒకేసారి మరీ ఎక్కువ పని చేస్తోందని చెప్పే సంకేతం మాత్రమే. మీ రికర్షన్ను పరిమితం చేయండి, లేజీగా వాలిడేట్ చేయండి, మరియు అనవసరమైన డిస్ట్రిబ్యూషన్ నుండి రక్షించుకోండి. అడ్వాన్స్డ్ టైప్స్ యొక్క లక్ష్యం కంపైల్ టైమ్లో ప్రతి విషయాన్ని నిరూపించడం కాదు; మీ టీమ్కు వేగవంతమైన, నమ్మదగిన టూలింగ్ను అందించడం. థియరిటికల్గా పర్ఫెక్ట్గా ఉండి లాంగ్వేజ్ సర్వర్ను క్రాష్ చేసే టైప్ కంటే, మిల్లీసెకన్లలో కంపైల్ అయ్యి 95 శాతం కేసులను కవర్ చేసే టైప్ చాలా విలువైనది.
