1//===--- HeuristicResolver.cpp ---------------------------*- C++-*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang/Sema/HeuristicResolver.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/CXXInheritance.h"
12#include "clang/AST/DeclTemplate.h"
13#include "clang/AST/ExprCXX.h"
14#include "clang/AST/TemplateBase.h"
15#include "clang/AST/Type.h"
16
17namespace clang {
18
19namespace {
20
21// Helper class for implementing HeuristicResolver.
22// Unlike HeuristicResolver which is a long-lived class,
23// a new instance of this class is created for every external
24// call into a HeuristicResolver operation. That allows this
25// class to store state that's local to such a top-level call,
26// particularly "recursion protection sets" that keep track of
27// nodes that have already been seen to avoid infinite recursion.
28class HeuristicResolverImpl {
29public:
30 HeuristicResolverImpl(ASTContext &Ctx) : Ctx(Ctx) {}
31
32 // These functions match the public interface of HeuristicResolver
33 // (but aren't const since they may modify the recursion protection sets).
34 std::vector<const NamedDecl *>
35 resolveMemberExpr(const CXXDependentScopeMemberExpr *ME);
36 std::vector<const NamedDecl *>
37 resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE);
38 std::vector<const NamedDecl *> resolveTypeOfCallExpr(const CallExpr *CE);
39 std::vector<const NamedDecl *> resolveCalleeOfCallExpr(const CallExpr *CE);
40 std::vector<const NamedDecl *>
41 resolveUsingValueDecl(const UnresolvedUsingValueDecl *UUVD);
42 std::vector<const NamedDecl *>
43 resolveDependentNameType(const DependentNameType *DNT);
44 std::vector<const NamedDecl *> resolveTemplateSpecializationType(
45 const DependentTemplateSpecializationType *DTST);
46 QualType resolveNestedNameSpecifierToType(const NestedNameSpecifier *NNS);
47 QualType getPointeeType(QualType T);
48 std::vector<const NamedDecl *>
49 lookupDependentName(CXXRecordDecl *RD, DeclarationName Name,
50 llvm::function_ref<bool(const NamedDecl *ND)> Filter);
51 TagDecl *resolveTypeToTagDecl(QualType T);
52 QualType simplifyType(QualType Type, const Expr *E, bool UnwrapPointer);
53
54private:
55 ASTContext &Ctx;
56
57 // Recursion protection sets
58 llvm::SmallSet<const DependentNameType *, 4> SeenDependentNameTypes;
59
60 // Given a tag-decl type and a member name, heuristically resolve the
61 // name to one or more declarations.
62 // The current heuristic is simply to look up the name in the primary
63 // template. This is a heuristic because the template could potentially
64 // have specializations that declare different members.
65 // Multiple declarations could be returned if the name is overloaded
66 // (e.g. an overloaded method in the primary template).
67 // This heuristic will give the desired answer in many cases, e.g.
68 // for a call to vector<T>::size().
69 std::vector<const NamedDecl *>
70 resolveDependentMember(QualType T, DeclarationName Name,
71 llvm::function_ref<bool(const NamedDecl *ND)> Filter);
72
73 // Try to heuristically resolve the type of a possibly-dependent expression
74 // `E`.
75 QualType resolveExprToType(const Expr *E);
76 std::vector<const NamedDecl *> resolveExprToDecls(const Expr *E);
77
78 bool findOrdinaryMemberInDependentClasses(const CXXBaseSpecifier *Specifier,
79 CXXBasePath &Path,
80 DeclarationName Name);
81};
82
83// Convenience lambdas for use as the 'Filter' parameter of
84// HeuristicResolver::resolveDependentMember().
85const auto NoFilter = [](const NamedDecl *D) { return true; };
86const auto NonStaticFilter = [](const NamedDecl *D) {
87 return D->isCXXInstanceMember();
88};
89const auto StaticFilter = [](const NamedDecl *D) {
90 return !D->isCXXInstanceMember();
91};
92const auto ValueFilter = [](const NamedDecl *D) { return isa<ValueDecl>(Val: D); };
93const auto TypeFilter = [](const NamedDecl *D) { return isa<TypeDecl>(Val: D); };
94const auto TemplateFilter = [](const NamedDecl *D) {
95 return isa<TemplateDecl>(Val: D);
96};
97
98QualType resolveDeclsToType(const std::vector<const NamedDecl *> &Decls,
99 ASTContext &Ctx) {
100 if (Decls.size() != 1) // Names an overload set -- just bail.
101 return QualType();
102 if (const auto *TD = dyn_cast<TypeDecl>(Val: Decls[0])) {
103 return Ctx.getTypeDeclType(Decl: TD);
104 }
105 if (const auto *VD = dyn_cast<ValueDecl>(Val: Decls[0])) {
106 return VD->getType();
107 }
108 return QualType();
109}
110
111TemplateName getReferencedTemplateName(const Type *T) {
112 if (const auto *TST = T->getAs<TemplateSpecializationType>()) {
113 return TST->getTemplateName();
114 }
115 if (const auto *DTST = T->getAs<DeducedTemplateSpecializationType>()) {
116 return DTST->getTemplateName();
117 }
118 return TemplateName();
119}
120
121// Helper function for HeuristicResolver::resolveDependentMember()
122// which takes a possibly-dependent type `T` and heuristically
123// resolves it to a CXXRecordDecl in which we can try name lookup.
124TagDecl *HeuristicResolverImpl::resolveTypeToTagDecl(QualType QT) {
125 const Type *T = QT.getTypePtrOrNull();
126 if (!T)
127 return nullptr;
128
129 // Unwrap type sugar such as type aliases.
130 T = T->getCanonicalTypeInternal().getTypePtr();
131
132 if (const auto *DNT = T->getAs<DependentNameType>()) {
133 T = resolveDeclsToType(Decls: resolveDependentNameType(DNT), Ctx)
134 .getTypePtrOrNull();
135 if (!T)
136 return nullptr;
137 T = T->getCanonicalTypeInternal().getTypePtr();
138 }
139
140 if (auto *TT = T->getAs<TagType>()) {
141 TagDecl *TD = TT->getDecl();
142 // Template might not be instantiated yet, fall back to primary template
143 // in such cases.
144 if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(Val: TD)) {
145 if (CTSD->getTemplateSpecializationKind() == TSK_Undeclared) {
146 return CTSD->getSpecializedTemplate()->getTemplatedDecl();
147 }
148 }
149 return TD;
150 }
151
152 if (const auto *ICNT = T->getAs<InjectedClassNameType>())
153 T = ICNT->getInjectedSpecializationType().getTypePtrOrNull();
154 if (!T)
155 return nullptr;
156
157 TemplateName TN = getReferencedTemplateName(T);
158 if (TN.isNull())
159 return nullptr;
160
161 const ClassTemplateDecl *TD =
162 dyn_cast_or_null<ClassTemplateDecl>(Val: TN.getAsTemplateDecl());
163 if (!TD)
164 return nullptr;
165
166 return TD->getTemplatedDecl();
167}
168
169QualType HeuristicResolverImpl::getPointeeType(QualType T) {
170 if (T.isNull())
171 return QualType();
172
173 if (T->isPointerType())
174 return T->castAs<PointerType>()->getPointeeType();
175
176 // Try to handle smart pointer types.
177
178 // Look up operator-> in the primary template. If we find one, it's probably a
179 // smart pointer type.
180 auto ArrowOps = resolveDependentMember(
181 T, Name: Ctx.DeclarationNames.getCXXOperatorName(Op: OO_Arrow), Filter: NonStaticFilter);
182 if (ArrowOps.empty())
183 return QualType();
184
185 // Getting the return type of the found operator-> method decl isn't useful,
186 // because we discarded template arguments to perform lookup in the primary
187 // template scope, so the return type would just have the form U* where U is a
188 // template parameter type.
189 // Instead, just handle the common case where the smart pointer type has the
190 // form of SmartPtr<X, ...>, and assume X is the pointee type.
191 auto *TST = T->getAs<TemplateSpecializationType>();
192 if (!TST)
193 return QualType();
194 if (TST->template_arguments().size() == 0)
195 return QualType();
196 const TemplateArgument &FirstArg = TST->template_arguments()[0];
197 if (FirstArg.getKind() != TemplateArgument::Type)
198 return QualType();
199 return FirstArg.getAsType();
200}
201
202QualType HeuristicResolverImpl::simplifyType(QualType Type, const Expr *E,
203 bool UnwrapPointer) {
204 bool DidUnwrapPointer = false;
205 // A type, together with an optional expression whose type it represents
206 // which may have additional information about the expression's type
207 // not stored in the QualType itself.
208 struct TypeExprPair {
209 QualType Type;
210 const Expr *E = nullptr;
211 };
212 TypeExprPair Current{Type, E};
213 auto SimplifyOneStep = [UnwrapPointer, &DidUnwrapPointer,
214 this](TypeExprPair T) -> TypeExprPair {
215 if (UnwrapPointer) {
216 if (QualType Pointee = getPointeeType(T: T.Type); !Pointee.isNull()) {
217 DidUnwrapPointer = true;
218 return {Pointee};
219 }
220 }
221 if (const auto *RT = T.Type->getAs<ReferenceType>()) {
222 // Does not count as "unwrap pointer".
223 return {RT->getPointeeType()};
224 }
225 if (const auto *BT = T.Type->getAs<BuiltinType>()) {
226 // If BaseType is the type of a dependent expression, it's just
227 // represented as BuiltinType::Dependent which gives us no information. We
228 // can get further by analyzing the dependent expression.
229 if (T.E && BT->getKind() == BuiltinType::Dependent) {
230 return {resolveExprToType(T.E), T.E};
231 }
232 }
233 if (const auto *AT = T.Type->getContainedAutoType()) {
234 // If T contains a dependent `auto` type, deduction will not have
235 // been performed on it yet. In simple cases (e.g. `auto` variable with
236 // initializer), get the approximate type that would result from
237 // deduction.
238 // FIXME: A more accurate implementation would propagate things like the
239 // `const` in `const auto`.
240 if (T.E && AT->isUndeducedAutoType()) {
241 if (const auto *DRE = dyn_cast<DeclRefExpr>(Val: T.E)) {
242 if (const auto *VD = dyn_cast<VarDecl>(Val: DRE->getDecl())) {
243 if (auto *Init = VD->getInit())
244 return {resolveExprToType(Init), Init};
245 }
246 }
247 }
248 }
249 if (const auto *TTPT = dyn_cast_if_present<TemplateTypeParmType>(T.Type)) {
250 // We can't do much useful with a template parameter (e.g. we cannot look
251 // up member names inside it). However, if the template parameter has a
252 // default argument, as a heuristic we can replace T with the default
253 // argument type.
254 if (const auto *TTPD = TTPT->getDecl()) {
255 if (TTPD->hasDefaultArgument()) {
256 const auto &DefaultArg = TTPD->getDefaultArgument().getArgument();
257 if (DefaultArg.getKind() == TemplateArgument::Type) {
258 return {DefaultArg.getAsType()};
259 }
260 }
261 }
262 }
263 return T;
264 };
265 // As an additional protection against infinite loops, bound the number of
266 // simplification steps.
267 size_t StepCount = 0;
268 const size_t MaxSteps = 64;
269 while (!Current.Type.isNull() && StepCount++ < MaxSteps) {
270 TypeExprPair New = SimplifyOneStep(Current);
271 if (New.Type == Current.Type)
272 break;
273 Current = New;
274 }
275 if (UnwrapPointer && !DidUnwrapPointer)
276 return QualType();
277 return Current.Type;
278}
279
280std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(
281 const CXXDependentScopeMemberExpr *ME) {
282 // If the expression has a qualifier, try resolving the member inside the
283 // qualifier's type.
284 // Note that we cannot use a NonStaticFilter in either case, for a couple
285 // of reasons:
286 // 1. It's valid to access a static member using instance member syntax,
287 // e.g. `instance.static_member`.
288 // 2. We can sometimes get a CXXDependentScopeMemberExpr for static
289 // member syntax too, e.g. if `X::static_member` occurs inside
290 // an instance method, it's represented as a CXXDependentScopeMemberExpr
291 // with `this` as the base expression as `X` as the qualifier
292 // (which could be valid if `X` names a base class after instantiation).
293 if (NestedNameSpecifier *NNS = ME->getQualifier()) {
294 if (QualType QualifierType = resolveNestedNameSpecifierToType(NNS);
295 !QualifierType.isNull()) {
296 auto Decls =
297 resolveDependentMember(T: QualifierType, Name: ME->getMember(), Filter: NoFilter);
298 if (!Decls.empty())
299 return Decls;
300 }
301
302 // Do not proceed to try resolving the member in the expression's base type
303 // without regard to the qualifier, as that could produce incorrect results.
304 // For example, `void foo() { this->Base::foo(); }` shouldn't resolve to
305 // foo() itself!
306 return {};
307 }
308
309 // Try resolving the member inside the expression's base type.
310 Expr *Base = ME->isImplicitAccess() ? nullptr : ME->getBase();
311 QualType BaseType = ME->getBaseType();
312 BaseType = simplifyType(Type: BaseType, E: Base, UnwrapPointer: ME->isArrow());
313 return resolveDependentMember(T: BaseType, Name: ME->getMember(), Filter: NoFilter);
314}
315
316std::vector<const NamedDecl *>
317HeuristicResolverImpl::resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE) {
318 QualType Qualifier = resolveNestedNameSpecifierToType(NNS: RE->getQualifier());
319 Qualifier = simplifyType(Type: Qualifier, E: nullptr, /*UnwrapPointer=*/false);
320 return resolveDependentMember(T: Qualifier, Name: RE->getDeclName(), Filter: StaticFilter);
321}
322
323std::vector<const NamedDecl *>
324HeuristicResolverImpl::resolveTypeOfCallExpr(const CallExpr *CE) {
325 QualType CalleeType = resolveExprToType(E: CE->getCallee());
326 if (CalleeType.isNull())
327 return {};
328 if (const auto *FnTypePtr = CalleeType->getAs<PointerType>())
329 CalleeType = FnTypePtr->getPointeeType();
330 if (const FunctionType *FnType = CalleeType->getAs<FunctionType>()) {
331 if (const auto *D = resolveTypeToTagDecl(QT: FnType->getReturnType())) {
332 return {D};
333 }
334 }
335 return {};
336}
337
338std::vector<const NamedDecl *>
339HeuristicResolverImpl::resolveCalleeOfCallExpr(const CallExpr *CE) {
340 if (const auto *ND = dyn_cast_or_null<NamedDecl>(Val: CE->getCalleeDecl())) {
341 return {ND};
342 }
343
344 return resolveExprToDecls(E: CE->getCallee());
345}
346
347std::vector<const NamedDecl *> HeuristicResolverImpl::resolveUsingValueDecl(
348 const UnresolvedUsingValueDecl *UUVD) {
349 return resolveDependentMember(T: QualType(UUVD->getQualifier()->getAsType(), 0),
350 Name: UUVD->getNameInfo().getName(), Filter: ValueFilter);
351}
352
353std::vector<const NamedDecl *>
354HeuristicResolverImpl::resolveDependentNameType(const DependentNameType *DNT) {
355 if (auto [_, inserted] = SeenDependentNameTypes.insert(Ptr: DNT); !inserted)
356 return {};
357 return resolveDependentMember(
358 T: resolveNestedNameSpecifierToType(NNS: DNT->getQualifier()),
359 Name: DNT->getIdentifier(), Filter: TypeFilter);
360}
361
362std::vector<const NamedDecl *>
363HeuristicResolverImpl::resolveTemplateSpecializationType(
364 const DependentTemplateSpecializationType *DTST) {
365 const DependentTemplateStorage &DTN = DTST->getDependentTemplateName();
366 return resolveDependentMember(
367 T: resolveNestedNameSpecifierToType(NNS: DTN.getQualifier()),
368 Name: DTN.getName().getIdentifier(), Filter: TemplateFilter);
369}
370
371std::vector<const NamedDecl *>
372HeuristicResolverImpl::resolveExprToDecls(const Expr *E) {
373 if (const auto *ME = dyn_cast<CXXDependentScopeMemberExpr>(Val: E)) {
374 return resolveMemberExpr(ME);
375 }
376 if (const auto *RE = dyn_cast<DependentScopeDeclRefExpr>(Val: E)) {
377 return resolveDeclRefExpr(RE);
378 }
379 if (const auto *OE = dyn_cast<OverloadExpr>(Val: E)) {
380 return {OE->decls_begin(), OE->decls_end()};
381 }
382 if (const auto *CE = dyn_cast<CallExpr>(Val: E)) {
383 return resolveTypeOfCallExpr(CE);
384 }
385 if (const auto *ME = dyn_cast<MemberExpr>(Val: E))
386 return {ME->getMemberDecl()};
387
388 return {};
389}
390
391QualType HeuristicResolverImpl::resolveExprToType(const Expr *E) {
392 std::vector<const NamedDecl *> Decls = resolveExprToDecls(E);
393 if (!Decls.empty())
394 return resolveDeclsToType(Decls, Ctx);
395
396 return E->getType();
397}
398
399QualType HeuristicResolverImpl::resolveNestedNameSpecifierToType(
400 const NestedNameSpecifier *NNS) {
401 if (!NNS)
402 return QualType();
403
404 // The purpose of this function is to handle the dependent (Kind ==
405 // Identifier) case, but we need to recurse on the prefix because
406 // that may be dependent as well, so for convenience handle
407 // the TypeSpec cases too.
408 switch (NNS->getKind()) {
409 case NestedNameSpecifier::TypeSpec:
410 return QualType(NNS->getAsType(), 0);
411 case NestedNameSpecifier::Identifier: {
412 return resolveDeclsToType(
413 Decls: resolveDependentMember(
414 T: resolveNestedNameSpecifierToType(NNS: NNS->getPrefix()),
415 Name: NNS->getAsIdentifier(), Filter: TypeFilter),
416 Ctx);
417 }
418 default:
419 break;
420 }
421 return QualType();
422}
423
424bool isOrdinaryMember(const NamedDecl *ND) {
425 return ND->isInIdentifierNamespace(Decl::IDNS_Ordinary | Decl::IDNS_Tag |
426 Decl::IDNS_Member);
427}
428
429bool findOrdinaryMember(const CXXRecordDecl *RD, CXXBasePath &Path,
430 DeclarationName Name) {
431 Path.Decls = RD->lookup(Name).begin();
432 for (DeclContext::lookup_iterator I = Path.Decls, E = I.end(); I != E; ++I)
433 if (isOrdinaryMember(ND: *I))
434 return true;
435
436 return false;
437}
438
439bool HeuristicResolverImpl::findOrdinaryMemberInDependentClasses(
440 const CXXBaseSpecifier *Specifier, CXXBasePath &Path,
441 DeclarationName Name) {
442 TagDecl *TD = resolveTypeToTagDecl(QT: Specifier->getType());
443 if (const auto *RD = dyn_cast_if_present<CXXRecordDecl>(Val: TD)) {
444 return findOrdinaryMember(RD, Path, Name);
445 }
446 return false;
447}
448
449std::vector<const NamedDecl *> HeuristicResolverImpl::lookupDependentName(
450 CXXRecordDecl *RD, DeclarationName Name,
451 llvm::function_ref<bool(const NamedDecl *ND)> Filter) {
452 std::vector<const NamedDecl *> Results;
453
454 // Lookup in the class.
455 bool AnyOrdinaryMembers = false;
456 for (const NamedDecl *ND : RD->lookup(Name)) {
457 if (isOrdinaryMember(ND))
458 AnyOrdinaryMembers = true;
459 if (Filter(ND))
460 Results.push_back(ND);
461 }
462 if (AnyOrdinaryMembers)
463 return Results;
464
465 // Perform lookup into our base classes.
466 CXXBasePaths Paths;
467 Paths.setOrigin(RD);
468 if (!RD->lookupInBases(
469 BaseMatches: [&](const CXXBaseSpecifier *Specifier, CXXBasePath &Path) {
470 return findOrdinaryMemberInDependentClasses(Specifier, Path, Name);
471 },
472 Paths, /*LookupInDependent=*/true))
473 return Results;
474 for (DeclContext::lookup_iterator I = Paths.front().Decls, E = I.end();
475 I != E; ++I) {
476 if (isOrdinaryMember(ND: *I) && Filter(*I))
477 Results.push_back(x: *I);
478 }
479 return Results;
480}
481
482std::vector<const NamedDecl *> HeuristicResolverImpl::resolveDependentMember(
483 QualType QT, DeclarationName Name,
484 llvm::function_ref<bool(const NamedDecl *ND)> Filter) {
485 TagDecl *TD = resolveTypeToTagDecl(QT);
486 if (!TD)
487 return {};
488 if (auto *ED = dyn_cast<EnumDecl>(Val: TD)) {
489 auto Result = ED->lookup(Name);
490 return {Result.begin(), Result.end()};
491 }
492 if (auto *RD = dyn_cast<CXXRecordDecl>(Val: TD)) {
493 if (!RD->hasDefinition())
494 return {};
495 RD = RD->getDefinition();
496 return lookupDependentName(RD, Name, Filter: [&](const NamedDecl *ND) {
497 if (!Filter(ND))
498 return false;
499 if (const auto *MD = dyn_cast<CXXMethodDecl>(Val: ND)) {
500 return !MD->isInstance() ||
501 MD->getMethodQualifiers().compatiblyIncludes(other: QT.getQualifiers(),
502 Ctx);
503 }
504 return true;
505 });
506 }
507 return {};
508}
509} // namespace
510
511std::vector<const NamedDecl *> HeuristicResolver::resolveMemberExpr(
512 const CXXDependentScopeMemberExpr *ME) const {
513 return HeuristicResolverImpl(Ctx).resolveMemberExpr(ME);
514}
515std::vector<const NamedDecl *> HeuristicResolver::resolveDeclRefExpr(
516 const DependentScopeDeclRefExpr *RE) const {
517 return HeuristicResolverImpl(Ctx).resolveDeclRefExpr(RE);
518}
519std::vector<const NamedDecl *>
520HeuristicResolver::resolveTypeOfCallExpr(const CallExpr *CE) const {
521 return HeuristicResolverImpl(Ctx).resolveTypeOfCallExpr(CE);
522}
523std::vector<const NamedDecl *>
524HeuristicResolver::resolveCalleeOfCallExpr(const CallExpr *CE) const {
525 return HeuristicResolverImpl(Ctx).resolveCalleeOfCallExpr(CE);
526}
527std::vector<const NamedDecl *> HeuristicResolver::resolveUsingValueDecl(
528 const UnresolvedUsingValueDecl *UUVD) const {
529 return HeuristicResolverImpl(Ctx).resolveUsingValueDecl(UUVD);
530}
531std::vector<const NamedDecl *> HeuristicResolver::resolveDependentNameType(
532 const DependentNameType *DNT) const {
533 return HeuristicResolverImpl(Ctx).resolveDependentNameType(DNT);
534}
535std::vector<const NamedDecl *>
536HeuristicResolver::resolveTemplateSpecializationType(
537 const DependentTemplateSpecializationType *DTST) const {
538 return HeuristicResolverImpl(Ctx).resolveTemplateSpecializationType(DTST);
539}
540QualType HeuristicResolver::resolveNestedNameSpecifierToType(
541 const NestedNameSpecifier *NNS) const {
542 return HeuristicResolverImpl(Ctx).resolveNestedNameSpecifierToType(NNS);
543}
544std::vector<const NamedDecl *> HeuristicResolver::lookupDependentName(
545 CXXRecordDecl *RD, DeclarationName Name,
546 llvm::function_ref<bool(const NamedDecl *ND)> Filter) {
547 return HeuristicResolverImpl(Ctx).lookupDependentName(RD, Name, Filter);
548}
549const QualType HeuristicResolver::getPointeeType(QualType T) const {
550 return HeuristicResolverImpl(Ctx).getPointeeType(T);
551}
552TagDecl *HeuristicResolver::resolveTypeToTagDecl(QualType T) const {
553 return HeuristicResolverImpl(Ctx).resolveTypeToTagDecl(QT: T);
554}
555QualType HeuristicResolver::simplifyType(QualType Type, const Expr *E,
556 bool UnwrapPointer) {
557 return HeuristicResolverImpl(Ctx).simplifyType(Type, E, UnwrapPointer);
558}
559
560} // namespace clang
561

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang/lib/Sema/HeuristicResolver.cpp