1 //===-- DeclarationName.cpp - Declaration names implementation --*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // This file implements the DeclarationName and DeclarationNameTable
11 // classes.
12 //
13 //===----------------------------------------------------------------------===//
14 #include "clang/AST/ASTContext.h"
15 #include "clang/AST/Decl.h"
16 #include "clang/AST/DeclarationName.h"
17 #include "clang/AST/Type.h"
18 #include "clang/AST/TypeLoc.h"
19 #include "clang/AST/TypeOrdering.h"
20 #include "clang/Basic/IdentifierTable.h"
21 #include "llvm/ADT/DenseMap.h"
22 #include "llvm/ADT/FoldingSet.h"
23 #include "llvm/Support/ErrorHandling.h"
24 #include "llvm/Support/raw_ostream.h"
25 using namespace clang;
26 
27 namespace clang {
28 /// CXXSpecialName - Records the type associated with one of the
29 /// "special" kinds of declaration names in C++, e.g., constructors,
30 /// destructors, and conversion functions.
31 class CXXSpecialName
32   : public DeclarationNameExtra, public llvm::FoldingSetNode {
33 public:
34   /// Type - The type associated with this declaration name.
35   QualType Type;
36 
37   /// FETokenInfo - Extra information associated with this declaration
38   /// name that can be used by the front end.
39   void *FETokenInfo;
40 
Profile(llvm::FoldingSetNodeID & ID)41   void Profile(llvm::FoldingSetNodeID &ID) {
42     ID.AddInteger(ExtraKindOrNumArgs);
43     ID.AddPointer(Type.getAsOpaquePtr());
44   }
45 };
46 
47 /// CXXOperatorIdName - Contains extra information for the name of an
48 /// overloaded operator in C++, such as "operator+.
49 class CXXOperatorIdName : public DeclarationNameExtra {
50 public:
51   /// FETokenInfo - Extra information associated with this operator
52   /// name that can be used by the front end.
53   void *FETokenInfo;
54 };
55 
56 /// CXXLiteralOperatorName - Contains the actual identifier that makes up the
57 /// name.
58 ///
59 /// This identifier is stored here rather than directly in DeclarationName so as
60 /// to allow Objective-C selectors, which are about a million times more common,
61 /// to consume minimal memory.
62 class CXXLiteralOperatorIdName
63   : public DeclarationNameExtra, public llvm::FoldingSetNode {
64 public:
65   IdentifierInfo *ID;
66 
67   /// FETokenInfo - Extra information associated with this operator
68   /// name that can be used by the front end.
69   void *FETokenInfo;
70 
Profile(llvm::FoldingSetNodeID & FSID)71   void Profile(llvm::FoldingSetNodeID &FSID) {
72     FSID.AddPointer(ID);
73   }
74 };
75 
compareInt(unsigned A,unsigned B)76 static int compareInt(unsigned A, unsigned B) {
77   return (A < B ? -1 : (A > B ? 1 : 0));
78 }
79 
compare(DeclarationName LHS,DeclarationName RHS)80 int DeclarationName::compare(DeclarationName LHS, DeclarationName RHS) {
81   if (LHS.getNameKind() != RHS.getNameKind())
82     return (LHS.getNameKind() < RHS.getNameKind() ? -1 : 1);
83 
84   switch (LHS.getNameKind()) {
85   case DeclarationName::Identifier: {
86     IdentifierInfo *LII = LHS.getAsIdentifierInfo();
87     IdentifierInfo *RII = RHS.getAsIdentifierInfo();
88     if (!LII) return RII ? -1 : 0;
89     if (!RII) return 1;
90 
91     return LII->getName().compare(RII->getName());
92   }
93 
94   case DeclarationName::ObjCZeroArgSelector:
95   case DeclarationName::ObjCOneArgSelector:
96   case DeclarationName::ObjCMultiArgSelector: {
97     Selector LHSSelector = LHS.getObjCSelector();
98     Selector RHSSelector = RHS.getObjCSelector();
99     unsigned LN = LHSSelector.getNumArgs(), RN = RHSSelector.getNumArgs();
100     for (unsigned I = 0, N = std::min(LN, RN); I != N; ++I) {
101       switch (LHSSelector.getNameForSlot(I).compare(
102                                                RHSSelector.getNameForSlot(I))) {
103       case -1: return true;
104       case 1: return false;
105       default: break;
106       }
107     }
108 
109     return compareInt(LN, RN);
110   }
111 
112   case DeclarationName::CXXConstructorName:
113   case DeclarationName::CXXDestructorName:
114   case DeclarationName::CXXConversionFunctionName:
115     if (QualTypeOrdering()(LHS.getCXXNameType(), RHS.getCXXNameType()))
116       return -1;
117     if (QualTypeOrdering()(RHS.getCXXNameType(), LHS.getCXXNameType()))
118       return 1;
119     return 0;
120 
121   case DeclarationName::CXXOperatorName:
122     return compareInt(LHS.getCXXOverloadedOperator(),
123                       RHS.getCXXOverloadedOperator());
124 
125   case DeclarationName::CXXLiteralOperatorName:
126     return LHS.getCXXLiteralIdentifier()->getName().compare(
127                                    RHS.getCXXLiteralIdentifier()->getName());
128 
129   case DeclarationName::CXXUsingDirective:
130     return 0;
131   }
132 
133   llvm_unreachable("Invalid DeclarationName Kind!");
134 }
135 
operator <<(raw_ostream & OS,DeclarationName N)136 raw_ostream &operator<<(raw_ostream &OS, DeclarationName N) {
137   switch (N.getNameKind()) {
138   case DeclarationName::Identifier:
139     if (const IdentifierInfo *II = N.getAsIdentifierInfo())
140       OS << II->getName();
141     return OS;
142 
143   case DeclarationName::ObjCZeroArgSelector:
144   case DeclarationName::ObjCOneArgSelector:
145   case DeclarationName::ObjCMultiArgSelector:
146     N.getObjCSelector().print(OS);
147     return OS;
148 
149   case DeclarationName::CXXConstructorName: {
150     QualType ClassType = N.getCXXNameType();
151     if (const RecordType *ClassRec = ClassType->getAs<RecordType>())
152       return OS << *ClassRec->getDecl();
153     LangOptions LO;
154     LO.CPlusPlus = true;
155     return OS << ClassType.getAsString(PrintingPolicy(LO));
156   }
157 
158   case DeclarationName::CXXDestructorName: {
159     OS << '~';
160     QualType Type = N.getCXXNameType();
161     if (const RecordType *Rec = Type->getAs<RecordType>())
162       return OS << *Rec->getDecl();
163     LangOptions LO;
164     LO.CPlusPlus = true;
165     return OS << Type.getAsString(PrintingPolicy(LO));
166   }
167 
168   case DeclarationName::CXXOperatorName: {
169     static const char* const OperatorNames[NUM_OVERLOADED_OPERATORS] = {
170       nullptr,
171 #define OVERLOADED_OPERATOR(Name,Spelling,Token,Unary,Binary,MemberOnly) \
172       Spelling,
173 #include "clang/Basic/OperatorKinds.def"
174     };
175     const char *OpName = OperatorNames[N.getCXXOverloadedOperator()];
176     assert(OpName && "not an overloaded operator");
177 
178     OS << "operator";
179     if (OpName[0] >= 'a' && OpName[0] <= 'z')
180       OS << ' ';
181     return OS << OpName;
182   }
183 
184   case DeclarationName::CXXLiteralOperatorName:
185     return OS << "operator \"\" " << N.getCXXLiteralIdentifier()->getName();
186 
187   case DeclarationName::CXXConversionFunctionName: {
188     OS << "operator ";
189     QualType Type = N.getCXXNameType();
190     if (const RecordType *Rec = Type->getAs<RecordType>())
191       return OS << *Rec->getDecl();
192     LangOptions LO;
193     LO.CPlusPlus = true;
194     LO.Bool = true;
195     return OS << Type.getAsString(PrintingPolicy(LO));
196   }
197   case DeclarationName::CXXUsingDirective:
198     return OS << "<using-directive>";
199   }
200 
201   llvm_unreachable("Unexpected declaration name kind");
202 }
203 
204 } // end namespace clang
205 
getNameKind() const206 DeclarationName::NameKind DeclarationName::getNameKind() const {
207   switch (getStoredNameKind()) {
208   case StoredIdentifier:          return Identifier;
209   case StoredObjCZeroArgSelector: return ObjCZeroArgSelector;
210   case StoredObjCOneArgSelector:  return ObjCOneArgSelector;
211 
212   case StoredDeclarationNameExtra:
213     switch (getExtra()->ExtraKindOrNumArgs) {
214     case DeclarationNameExtra::CXXConstructor:
215       return CXXConstructorName;
216 
217     case DeclarationNameExtra::CXXDestructor:
218       return CXXDestructorName;
219 
220     case DeclarationNameExtra::CXXConversionFunction:
221       return CXXConversionFunctionName;
222 
223     case DeclarationNameExtra::CXXLiteralOperator:
224       return CXXLiteralOperatorName;
225 
226     case DeclarationNameExtra::CXXUsingDirective:
227       return CXXUsingDirective;
228 
229     default:
230       // Check if we have one of the CXXOperator* enumeration values.
231       if (getExtra()->ExtraKindOrNumArgs <
232             DeclarationNameExtra::CXXUsingDirective)
233         return CXXOperatorName;
234 
235       return ObjCMultiArgSelector;
236     }
237   }
238 
239   // Can't actually get here.
240   llvm_unreachable("This should be unreachable!");
241 }
242 
isDependentName() const243 bool DeclarationName::isDependentName() const {
244   QualType T = getCXXNameType();
245   return !T.isNull() && T->isDependentType();
246 }
247 
getAsString() const248 std::string DeclarationName::getAsString() const {
249   std::string Result;
250   llvm::raw_string_ostream OS(Result);
251   OS << *this;
252   return OS.str();
253 }
254 
getCXXNameType() const255 QualType DeclarationName::getCXXNameType() const {
256   if (CXXSpecialName *CXXName = getAsCXXSpecialName())
257     return CXXName->Type;
258   else
259     return QualType();
260 }
261 
getCXXOverloadedOperator() const262 OverloadedOperatorKind DeclarationName::getCXXOverloadedOperator() const {
263   if (CXXOperatorIdName *CXXOp = getAsCXXOperatorIdName()) {
264     unsigned value
265       = CXXOp->ExtraKindOrNumArgs - DeclarationNameExtra::CXXConversionFunction;
266     return static_cast<OverloadedOperatorKind>(value);
267   } else {
268     return OO_None;
269   }
270 }
271 
getCXXLiteralIdentifier() const272 IdentifierInfo *DeclarationName::getCXXLiteralIdentifier() const {
273   if (CXXLiteralOperatorIdName *CXXLit = getAsCXXLiteralOperatorIdName())
274     return CXXLit->ID;
275   else
276     return nullptr;
277 }
278 
getFETokenInfoAsVoidSlow() const279 void *DeclarationName::getFETokenInfoAsVoidSlow() const {
280   switch (getNameKind()) {
281   case Identifier:
282     llvm_unreachable("Handled by getFETokenInfo()");
283 
284   case CXXConstructorName:
285   case CXXDestructorName:
286   case CXXConversionFunctionName:
287     return getAsCXXSpecialName()->FETokenInfo;
288 
289   case CXXOperatorName:
290     return getAsCXXOperatorIdName()->FETokenInfo;
291 
292   case CXXLiteralOperatorName:
293     return getAsCXXLiteralOperatorIdName()->FETokenInfo;
294 
295   default:
296     llvm_unreachable("Declaration name has no FETokenInfo");
297   }
298 }
299 
setFETokenInfo(void * T)300 void DeclarationName::setFETokenInfo(void *T) {
301   switch (getNameKind()) {
302   case Identifier:
303     getAsIdentifierInfo()->setFETokenInfo(T);
304     break;
305 
306   case CXXConstructorName:
307   case CXXDestructorName:
308   case CXXConversionFunctionName:
309     getAsCXXSpecialName()->FETokenInfo = T;
310     break;
311 
312   case CXXOperatorName:
313     getAsCXXOperatorIdName()->FETokenInfo = T;
314     break;
315 
316   case CXXLiteralOperatorName:
317     getAsCXXLiteralOperatorIdName()->FETokenInfo = T;
318     break;
319 
320   default:
321     llvm_unreachable("Declaration name has no FETokenInfo");
322   }
323 }
324 
getUsingDirectiveName()325 DeclarationName DeclarationName::getUsingDirectiveName() {
326   // Single instance of DeclarationNameExtra for using-directive
327   static const DeclarationNameExtra UDirExtra =
328     { DeclarationNameExtra::CXXUsingDirective };
329 
330   uintptr_t Ptr = reinterpret_cast<uintptr_t>(&UDirExtra);
331   Ptr |= StoredDeclarationNameExtra;
332 
333   return DeclarationName(Ptr);
334 }
335 
dump() const336 void DeclarationName::dump() const {
337   llvm::errs() << *this << '\n';
338 }
339 
DeclarationNameTable(const ASTContext & C)340 DeclarationNameTable::DeclarationNameTable(const ASTContext &C) : Ctx(C) {
341   CXXSpecialNamesImpl = new llvm::FoldingSet<CXXSpecialName>;
342   CXXLiteralOperatorNames = new llvm::FoldingSet<CXXLiteralOperatorIdName>;
343 
344   // Initialize the overloaded operator names.
345   CXXOperatorNames = new (Ctx) CXXOperatorIdName[NUM_OVERLOADED_OPERATORS];
346   for (unsigned Op = 0; Op < NUM_OVERLOADED_OPERATORS; ++Op) {
347     CXXOperatorNames[Op].ExtraKindOrNumArgs
348       = Op + DeclarationNameExtra::CXXConversionFunction;
349     CXXOperatorNames[Op].FETokenInfo = nullptr;
350   }
351 }
352 
~DeclarationNameTable()353 DeclarationNameTable::~DeclarationNameTable() {
354   llvm::FoldingSet<CXXSpecialName> *SpecialNames =
355     static_cast<llvm::FoldingSet<CXXSpecialName>*>(CXXSpecialNamesImpl);
356   llvm::FoldingSet<CXXLiteralOperatorIdName> *LiteralNames
357     = static_cast<llvm::FoldingSet<CXXLiteralOperatorIdName>*>
358         (CXXLiteralOperatorNames);
359 
360   delete SpecialNames;
361   delete LiteralNames;
362 }
363 
getCXXConstructorName(CanQualType Ty)364 DeclarationName DeclarationNameTable::getCXXConstructorName(CanQualType Ty) {
365   return getCXXSpecialName(DeclarationName::CXXConstructorName,
366                            Ty.getUnqualifiedType());
367 }
368 
getCXXDestructorName(CanQualType Ty)369 DeclarationName DeclarationNameTable::getCXXDestructorName(CanQualType Ty) {
370   return getCXXSpecialName(DeclarationName::CXXDestructorName,
371                            Ty.getUnqualifiedType());
372 }
373 
374 DeclarationName
getCXXConversionFunctionName(CanQualType Ty)375 DeclarationNameTable::getCXXConversionFunctionName(CanQualType Ty) {
376   return getCXXSpecialName(DeclarationName::CXXConversionFunctionName, Ty);
377 }
378 
379 DeclarationName
getCXXSpecialName(DeclarationName::NameKind Kind,CanQualType Ty)380 DeclarationNameTable::getCXXSpecialName(DeclarationName::NameKind Kind,
381                                         CanQualType Ty) {
382   assert(Kind >= DeclarationName::CXXConstructorName &&
383          Kind <= DeclarationName::CXXConversionFunctionName &&
384          "Kind must be a C++ special name kind");
385   llvm::FoldingSet<CXXSpecialName> *SpecialNames
386     = static_cast<llvm::FoldingSet<CXXSpecialName>*>(CXXSpecialNamesImpl);
387 
388   DeclarationNameExtra::ExtraKind EKind;
389   switch (Kind) {
390   case DeclarationName::CXXConstructorName:
391     EKind = DeclarationNameExtra::CXXConstructor;
392     assert(!Ty.hasQualifiers() &&"Constructor type must be unqualified");
393     break;
394   case DeclarationName::CXXDestructorName:
395     EKind = DeclarationNameExtra::CXXDestructor;
396     assert(!Ty.hasQualifiers() && "Destructor type must be unqualified");
397     break;
398   case DeclarationName::CXXConversionFunctionName:
399     EKind = DeclarationNameExtra::CXXConversionFunction;
400     break;
401   default:
402     return DeclarationName();
403   }
404 
405   // Unique selector, to guarantee there is one per name.
406   llvm::FoldingSetNodeID ID;
407   ID.AddInteger(EKind);
408   ID.AddPointer(Ty.getAsOpaquePtr());
409 
410   void *InsertPos = nullptr;
411   if (CXXSpecialName *Name = SpecialNames->FindNodeOrInsertPos(ID, InsertPos))
412     return DeclarationName(Name);
413 
414   CXXSpecialName *SpecialName = new (Ctx) CXXSpecialName;
415   SpecialName->ExtraKindOrNumArgs = EKind;
416   SpecialName->Type = Ty;
417   SpecialName->FETokenInfo = nullptr;
418 
419   SpecialNames->InsertNode(SpecialName, InsertPos);
420   return DeclarationName(SpecialName);
421 }
422 
423 DeclarationName
getCXXOperatorName(OverloadedOperatorKind Op)424 DeclarationNameTable::getCXXOperatorName(OverloadedOperatorKind Op) {
425   return DeclarationName(&CXXOperatorNames[(unsigned)Op]);
426 }
427 
428 DeclarationName
getCXXLiteralOperatorName(IdentifierInfo * II)429 DeclarationNameTable::getCXXLiteralOperatorName(IdentifierInfo *II) {
430   llvm::FoldingSet<CXXLiteralOperatorIdName> *LiteralNames
431     = static_cast<llvm::FoldingSet<CXXLiteralOperatorIdName>*>
432                                                       (CXXLiteralOperatorNames);
433 
434   llvm::FoldingSetNodeID ID;
435   ID.AddPointer(II);
436 
437   void *InsertPos = nullptr;
438   if (CXXLiteralOperatorIdName *Name =
439                                LiteralNames->FindNodeOrInsertPos(ID, InsertPos))
440     return DeclarationName (Name);
441 
442   CXXLiteralOperatorIdName *LiteralName = new (Ctx) CXXLiteralOperatorIdName;
443   LiteralName->ExtraKindOrNumArgs = DeclarationNameExtra::CXXLiteralOperator;
444   LiteralName->ID = II;
445   LiteralName->FETokenInfo = nullptr;
446 
447   LiteralNames->InsertNode(LiteralName, InsertPos);
448   return DeclarationName(LiteralName);
449 }
450 
DeclarationNameLoc(DeclarationName Name)451 DeclarationNameLoc::DeclarationNameLoc(DeclarationName Name) {
452   switch (Name.getNameKind()) {
453   case DeclarationName::Identifier:
454     break;
455   case DeclarationName::CXXConstructorName:
456   case DeclarationName::CXXDestructorName:
457   case DeclarationName::CXXConversionFunctionName:
458     NamedType.TInfo = nullptr;
459     break;
460   case DeclarationName::CXXOperatorName:
461     CXXOperatorName.BeginOpNameLoc = SourceLocation().getRawEncoding();
462     CXXOperatorName.EndOpNameLoc = SourceLocation().getRawEncoding();
463     break;
464   case DeclarationName::CXXLiteralOperatorName:
465     CXXLiteralOperatorName.OpNameLoc = SourceLocation().getRawEncoding();
466     break;
467   case DeclarationName::ObjCZeroArgSelector:
468   case DeclarationName::ObjCOneArgSelector:
469   case DeclarationName::ObjCMultiArgSelector:
470     // FIXME: ?
471     break;
472   case DeclarationName::CXXUsingDirective:
473     break;
474   }
475 }
476 
containsUnexpandedParameterPack() const477 bool DeclarationNameInfo::containsUnexpandedParameterPack() const {
478   switch (Name.getNameKind()) {
479   case DeclarationName::Identifier:
480   case DeclarationName::ObjCZeroArgSelector:
481   case DeclarationName::ObjCOneArgSelector:
482   case DeclarationName::ObjCMultiArgSelector:
483   case DeclarationName::CXXOperatorName:
484   case DeclarationName::CXXLiteralOperatorName:
485   case DeclarationName::CXXUsingDirective:
486     return false;
487 
488   case DeclarationName::CXXConstructorName:
489   case DeclarationName::CXXDestructorName:
490   case DeclarationName::CXXConversionFunctionName:
491     if (TypeSourceInfo *TInfo = LocInfo.NamedType.TInfo)
492       return TInfo->getType()->containsUnexpandedParameterPack();
493 
494     return Name.getCXXNameType()->containsUnexpandedParameterPack();
495   }
496   llvm_unreachable("All name kinds handled.");
497 }
498 
isInstantiationDependent() const499 bool DeclarationNameInfo::isInstantiationDependent() const {
500   switch (Name.getNameKind()) {
501   case DeclarationName::Identifier:
502   case DeclarationName::ObjCZeroArgSelector:
503   case DeclarationName::ObjCOneArgSelector:
504   case DeclarationName::ObjCMultiArgSelector:
505   case DeclarationName::CXXOperatorName:
506   case DeclarationName::CXXLiteralOperatorName:
507   case DeclarationName::CXXUsingDirective:
508     return false;
509 
510   case DeclarationName::CXXConstructorName:
511   case DeclarationName::CXXDestructorName:
512   case DeclarationName::CXXConversionFunctionName:
513     if (TypeSourceInfo *TInfo = LocInfo.NamedType.TInfo)
514       return TInfo->getType()->isInstantiationDependentType();
515 
516     return Name.getCXXNameType()->isInstantiationDependentType();
517   }
518   llvm_unreachable("All name kinds handled.");
519 }
520 
getAsString() const521 std::string DeclarationNameInfo::getAsString() const {
522   std::string Result;
523   llvm::raw_string_ostream OS(Result);
524   printName(OS);
525   return OS.str();
526 }
527 
printName(raw_ostream & OS) const528 void DeclarationNameInfo::printName(raw_ostream &OS) const {
529   switch (Name.getNameKind()) {
530   case DeclarationName::Identifier:
531   case DeclarationName::ObjCZeroArgSelector:
532   case DeclarationName::ObjCOneArgSelector:
533   case DeclarationName::ObjCMultiArgSelector:
534   case DeclarationName::CXXOperatorName:
535   case DeclarationName::CXXLiteralOperatorName:
536   case DeclarationName::CXXUsingDirective:
537     OS << Name;
538     return;
539 
540   case DeclarationName::CXXConstructorName:
541   case DeclarationName::CXXDestructorName:
542   case DeclarationName::CXXConversionFunctionName:
543     if (TypeSourceInfo *TInfo = LocInfo.NamedType.TInfo) {
544       if (Name.getNameKind() == DeclarationName::CXXDestructorName)
545         OS << '~';
546       else if (Name.getNameKind() == DeclarationName::CXXConversionFunctionName)
547         OS << "operator ";
548       LangOptions LO;
549       LO.CPlusPlus = true;
550       LO.Bool = true;
551       OS << TInfo->getType().getAsString(PrintingPolicy(LO));
552     } else
553       OS << Name;
554     return;
555   }
556   llvm_unreachable("Unexpected declaration name kind");
557 }
558 
getEndLoc() const559 SourceLocation DeclarationNameInfo::getEndLoc() const {
560   switch (Name.getNameKind()) {
561   case DeclarationName::Identifier:
562     return NameLoc;
563 
564   case DeclarationName::CXXOperatorName: {
565     unsigned raw = LocInfo.CXXOperatorName.EndOpNameLoc;
566     return SourceLocation::getFromRawEncoding(raw);
567   }
568 
569   case DeclarationName::CXXLiteralOperatorName: {
570     unsigned raw = LocInfo.CXXLiteralOperatorName.OpNameLoc;
571     return SourceLocation::getFromRawEncoding(raw);
572   }
573 
574   case DeclarationName::CXXConstructorName:
575   case DeclarationName::CXXDestructorName:
576   case DeclarationName::CXXConversionFunctionName:
577     if (TypeSourceInfo *TInfo = LocInfo.NamedType.TInfo)
578       return TInfo->getTypeLoc().getEndLoc();
579     else
580       return NameLoc;
581 
582     // DNInfo work in progress: FIXME.
583   case DeclarationName::ObjCZeroArgSelector:
584   case DeclarationName::ObjCOneArgSelector:
585   case DeclarationName::ObjCMultiArgSelector:
586   case DeclarationName::CXXUsingDirective:
587     return NameLoc;
588   }
589   llvm_unreachable("Unexpected declaration name kind");
590 }
591