blob: 3ca19c6590445a6262f95867e39c380a51385cb1 [file] [log] [blame]
//===--- TypeChecker.cpp - Type Checking ----------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements the swift::performTypeChecking entry point for
// semantic analysis.
//
//===----------------------------------------------------------------------===//
#include "swift/Subsystems.h"
#include "TypeChecker.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/ASTVisitor.h"
#include "swift/AST/Attr.h"
#include "swift/AST/ExprHandle.h"
#include "swift/AST/Identifier.h"
#include "swift/AST/ModuleLoader.h"
#include "swift/AST/NameLookup.h"
#include "swift/AST/PrettyStackTrace.h"
#include "swift/AST/TypeRefinementContext.h"
#include "swift/Basic/STLExtras.h"
#include "swift/Basic/Timer.h"
#include "swift/ClangImporter/ClangImporter.h"
#include "swift/Parse/Lexer.h"
#include "swift/Sema/IDETypeChecking.h"
#include "swift/Strings.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/TinyPtrVector.h"
#include "llvm/ADT/Twine.h"
#include <algorithm>
using namespace swift;
TypeChecker::TypeChecker(ASTContext &Ctx, DiagnosticEngine &Diags)
: Context(Ctx), Diags(Diags)
{
auto clangImporter =
static_cast<ClangImporter *>(Context.getClangModuleLoader());
clangImporter->setTypeResolver(*this);
Context.setLazyResolver(this);
}
TypeChecker::~TypeChecker() {
auto clangImporter =
static_cast<ClangImporter *>(Context.getClangModuleLoader());
clangImporter->clearTypeResolver();
Context.setLazyResolver(nullptr);
}
void TypeChecker::handleExternalDecl(Decl *decl) {
if (auto SD = dyn_cast<StructDecl>(decl)) {
addImplicitConstructors(SD);
addImplicitStructConformances(SD);
}
if (auto CD = dyn_cast<ClassDecl>(decl)) {
addImplicitDestructor(CD);
}
if (auto ED = dyn_cast<EnumDecl>(decl)) {
addImplicitEnumConformances(ED);
}
}
ProtocolDecl *TypeChecker::getProtocol(SourceLoc loc, KnownProtocolKind kind) {
auto protocol = Context.getProtocol(kind);
if (!protocol && loc.isValid()) {
diagnose(loc, diag::missing_protocol,
Context.getIdentifier(getProtocolName(kind)));
}
if (protocol && !protocol->hasType()) {
validateDecl(protocol);
if (protocol->isInvalid())
return nullptr;
}
return protocol;
}
ProtocolDecl *TypeChecker::getLiteralProtocol(Expr *expr) {
if (isa<ArrayExpr>(expr))
return getProtocol(expr->getLoc(),
KnownProtocolKind::ExpressibleByArrayLiteral);
if (isa<DictionaryExpr>(expr))
return getProtocol(expr->getLoc(),
KnownProtocolKind::ExpressibleByDictionaryLiteral);
if (!isa<LiteralExpr>(expr))
return nullptr;
if (isa<NilLiteralExpr>(expr))
return getProtocol(expr->getLoc(),
KnownProtocolKind::ExpressibleByNilLiteral);
if (isa<IntegerLiteralExpr>(expr))
return getProtocol(expr->getLoc(),
KnownProtocolKind::ExpressibleByIntegerLiteral);
if (isa<FloatLiteralExpr>(expr))
return getProtocol(expr->getLoc(),
KnownProtocolKind::ExpressibleByFloatLiteral);
if (isa<BooleanLiteralExpr>(expr))
return getProtocol(expr->getLoc(),
KnownProtocolKind::ExpressibleByBooleanLiteral);
if (const auto *SLE = dyn_cast<StringLiteralExpr>(expr)) {
if (SLE->isSingleUnicodeScalar())
return getProtocol(
expr->getLoc(),
KnownProtocolKind::ExpressibleByUnicodeScalarLiteral);
if (SLE->isSingleExtendedGraphemeCluster())
return getProtocol(
expr->getLoc(),
KnownProtocolKind::ExpressibleByExtendedGraphemeClusterLiteral);
return getProtocol(expr->getLoc(),
KnownProtocolKind::ExpressibleByStringLiteral);
}
if (isa<InterpolatedStringLiteralExpr>(expr))
return getProtocol(expr->getLoc(),
KnownProtocolKind::ExpressibleByStringInterpolation);
if (auto E = dyn_cast<MagicIdentifierLiteralExpr>(expr)) {
switch (E->getKind()) {
case MagicIdentifierLiteralExpr::File:
case MagicIdentifierLiteralExpr::Function:
return getProtocol(expr->getLoc(),
KnownProtocolKind::ExpressibleByStringLiteral);
case MagicIdentifierLiteralExpr::Line:
case MagicIdentifierLiteralExpr::Column:
return getProtocol(expr->getLoc(),
KnownProtocolKind::ExpressibleByIntegerLiteral);
case MagicIdentifierLiteralExpr::DSOHandle:
return nullptr;
}
}
if (auto E = dyn_cast<ObjectLiteralExpr>(expr)) {
switch (E->getLiteralKind()) {
#define POUND_OBJECT_LITERAL(Name, Desc, Protocol)\
case ObjectLiteralExpr::Name:\
return getProtocol(expr->getLoc(), KnownProtocolKind::Protocol);
#include "swift/Parse/Tokens.def"
}
}
return nullptr;
}
DeclName TypeChecker::getObjectLiteralConstructorName(ObjectLiteralExpr *expr) {
switch (expr->getLiteralKind()) {
case ObjectLiteralExpr::colorLiteral: {
return DeclName(Context, Context.Id_init,
{ Context.getIdentifier("colorLiteralRed"),
Context.getIdentifier("green"),
Context.getIdentifier("blue"),
Context.getIdentifier("alpha") });
}
case ObjectLiteralExpr::imageLiteral: {
return DeclName(Context, Context.Id_init,
{ Context.getIdentifier("imageLiteralResourceName") });
}
case ObjectLiteralExpr::fileLiteral: {
return DeclName(Context, Context.Id_init,
{ Context.getIdentifier("fileReferenceLiteralResourceName") });
}
}
llvm_unreachable("unknown literal constructor");
}
/// Return an idealized form of the parameter type of the given
/// object-literal initializer. This removes references to the protocol
/// name from the first argument label, which would be otherwise be
/// redundant when writing out the object-literal syntax:
///
/// #fileLiteral(fileReferenceLiteralResourceName: "hello.jpg")
///
/// Doing this allows us to preserve a nicer (and source-compatible)
/// literal syntax while still giving the initializer a semantically
/// unambiguous name.
Type TypeChecker::getObjectLiteralParameterType(ObjectLiteralExpr *expr,
ConstructorDecl *ctor) {
Type argType = ctor->getArgumentType();
auto argTuple = argType->getAs<TupleType>();
if (!argTuple) return argType;
auto replace = [&](StringRef replacement) -> Type {
SmallVector<TupleTypeElt, 4> elements;
elements.append(argTuple->getElements().begin(),
argTuple->getElements().end());
elements[0] = TupleTypeElt(elements[0].getType(),
Context.getIdentifier(replacement));
return TupleType::get(elements, Context);
};
switch (expr->getLiteralKind()) {
case ObjectLiteralExpr::colorLiteral:
return replace("red");
case ObjectLiteralExpr::fileLiteral:
case ObjectLiteralExpr::imageLiteral:
return replace("resourceName");
}
llvm_unreachable("unknown literal constructor");
}
Module *TypeChecker::getStdlibModule(const DeclContext *dc) {
if (StdlibModule)
return StdlibModule;
if (!StdlibModule)
StdlibModule = Context.getStdlibModule();
if (!StdlibModule)
StdlibModule = dc->getParentModule();
assert(StdlibModule && "no main module found");
Context.recordKnownProtocols(StdlibModule);
return StdlibModule;
}
Type TypeChecker::lookupBoolType(const DeclContext *dc) {
if (!boolType) {
boolType = ([&] {
SmallVector<ValueDecl *, 2> results;
getStdlibModule(dc)->lookupValue({}, Context.getIdentifier("Bool"),
NLKind::QualifiedLookup, results);
if (results.size() != 1) {
diagnose(SourceLoc(), diag::bool_type_broken);
return Type();
}
auto tyDecl = dyn_cast<TypeDecl>(results.front());
if (!tyDecl) {
diagnose(SourceLoc(), diag::bool_type_broken);
return Type();
}
return tyDecl->getDeclaredType();
})();
}
return *boolType;
}
namespace swift {
/// Clone the given generic parameters in the given list. We don't need any
/// of the requirements, because they will be inferred.
GenericParamList *cloneGenericParams(ASTContext &ctx,
DeclContext *dc,
GenericParamList *fromParams,
GenericParamList *outerParams) {
// Clone generic parameters.
SmallVector<GenericTypeParamDecl *, 2> toGenericParams;
for (auto fromGP : *fromParams) {
// Create the new generic parameter.
auto toGP = new (ctx) GenericTypeParamDecl(dc, fromGP->getName(),
SourceLoc(),
fromGP->getDepth(),
fromGP->getIndex());
toGP->setImplicit(true);
// Record new generic parameter.
toGenericParams.push_back(toGP);
}
auto toParams = GenericParamList::create(ctx, SourceLoc(), toGenericParams,
SourceLoc());
toParams->setOuterParameters(outerParams);
return toParams;
}
}
// FIXME: total hack
GenericParamList *createProtocolGenericParams(ASTContext &ctx,
ProtocolDecl *proto,
DeclContext *dc);
static void bindExtensionDecl(ExtensionDecl *ED, TypeChecker &TC) {
if (ED->getExtendedType())
return;
// If we didn't parse a type, fill in an error type and bail out.
if (!ED->getExtendedTypeLoc().getTypeRepr()) {
ED->setInvalid();
ED->getExtendedTypeLoc().setInvalidType(TC.Context);
return;
}
auto dc = ED->getDeclContext();
// Validate the representation.
// FIXME: Perform some kind of "shallow" validation here?
TypeResolutionOptions options;
options |= TR_AllowUnboundGenerics;
options |= TR_ExtensionBinding;
if (TC.validateType(ED->getExtendedTypeLoc(), dc, options)) {
ED->setInvalid();
return;
}
// Dig out the extended type.
auto extendedType = ED->getExtendedType();
// Handle easy cases.
// Cannot extend a metatype.
if (extendedType->is<AnyMetatypeType>()) {
TC.diagnose(ED->getLoc(), diag::extension_metatype, extendedType)
.highlight(ED->getExtendedTypeLoc().getSourceRange());
ED->setInvalid();
ED->getExtendedTypeLoc().setInvalidType(TC.Context);
return;
}
// Cannot extend a bound generic type.
if (extendedType->isSpecialized() && extendedType->getAnyNominal()) {
TC.diagnose(ED->getLoc(), diag::extension_specialization,
extendedType->getAnyNominal()->getName())
.highlight(ED->getExtendedTypeLoc().getSourceRange());
ED->setInvalid();
ED->getExtendedTypeLoc().setInvalidType(TC.Context);
return;
}
// Dig out the nominal type being extended.
NominalTypeDecl *extendedNominal = extendedType->getAnyNominal();
if (!extendedNominal) {
TC.diagnose(ED->getLoc(), diag::non_nominal_extension, extendedType)
.highlight(ED->getExtendedTypeLoc().getSourceRange());
ED->setInvalid();
ED->getExtendedTypeLoc().setInvalidType(TC.Context);
return;
}
assert(extendedNominal && "Should have the nominal type being extended");
// If the extended type is generic or is a protocol. Clone or create
// the generic parameters.
if (extendedNominal->getGenericParams()) {
if (auto proto = dyn_cast<ProtocolDecl>(extendedNominal)) {
// For a protocol extension, build the generic parameter list.
ED->setGenericParams(proto->createGenericParams(ED));
} else {
// Clone the existing generic parameter list.
ED->setGenericParams(
cloneGenericParams(TC.Context, ED,
extendedNominal->getGenericParams(),
nullptr));
}
}
// If we have a trailing where clause, deal with it now.
// For now, trailing where clauses are only permitted on protocol extensions.
if (auto trailingWhereClause = ED->getTrailingWhereClause()) {
if (!extendedNominal->getGenericParams()) {
// Only generic and protocol types are permitted to have
// trailing where clauses.
TC.diagnose(ED, diag::extension_nongeneric_trailing_where, extendedType)
.highlight(trailingWhereClause->getSourceRange());
ED->setTrailingWhereClause(nullptr);
} else {
// Merge the trailing where clause into the generic parameter list.
// FIXME: Long-term, we'd like clients to deal with the trailing where
// clause explicitly, but for now it's far more direct to represent
// the trailing where clause as part of the requirements.
ED->getGenericParams()->addTrailingWhereClause(
TC.Context,
trailingWhereClause->getWhereLoc(),
trailingWhereClause->getRequirements());
}
}
extendedNominal->addExtension(ED);
}
static void typeCheckFunctionsAndExternalDecls(TypeChecker &TC) {
unsigned currentFunctionIdx = 0;
unsigned currentExternalDef = TC.Context.LastCheckedExternalDefinition;
do {
// Type check the body of each of the function in turn. Note that outside
// functions must be visited before nested functions for type-checking to
// work correctly.
for (unsigned n = TC.definedFunctions.size(); currentFunctionIdx != n;
++currentFunctionIdx) {
auto *AFD = TC.definedFunctions[currentFunctionIdx];
// HACK: don't type-check the same function body twice. This is
// supposed to be handled by just not enqueuing things twice,
// but that gets tricky with synthesized function bodies.
if (AFD->isBodyTypeChecked()) continue;
PrettyStackTraceDecl StackEntry("type-checking", AFD);
TC.typeCheckAbstractFunctionBody(AFD);
AFD->setBodyTypeCheckedIfPresent();
}
for (unsigned n = TC.Context.ExternalDefinitions.size();
currentExternalDef != n;
++currentExternalDef) {
auto decl = TC.Context.ExternalDefinitions[currentExternalDef];
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(decl)) {
// HACK: don't type-check the same function body twice. This is
// supposed to be handled by just not enqueuing things twice,
// but that gets tricky with synthesized function bodies.
if (AFD->isBodyTypeChecked()) continue;
PrettyStackTraceDecl StackEntry("type-checking", AFD);
TC.typeCheckAbstractFunctionBody(AFD);
continue;
}
if (isa<NominalTypeDecl>(decl)) {
TC.handleExternalDecl(decl);
continue;
}
if (isa<VarDecl>(decl))
continue;
llvm_unreachable("Unhandled external definition kind");
}
// Validate the contents of any referenced nominal types for SIL's purposes.
// Note: if we ever start putting extension members in vtables, we'll need
// to validate those members too.
// FIXME: If we're not planning to run SILGen, this is wasted effort.
while (!TC.ValidatedTypes.empty()) {
auto nominal = TC.ValidatedTypes.pop_back_val();
if (nominal->isInvalid() || TC.Context.hadError())
continue;
Optional<bool> lazyVarsAlreadyHaveImplementation;
for (auto *D : nominal->getMembers()) {
auto VD = dyn_cast<ValueDecl>(D);
if (!VD)
continue;
TC.validateDecl(VD);
// The only thing left to do is synthesize storage for lazy variables.
// We only have to do that if it's a type from another file, though.
// In NDEBUG builds, bail out as soon as we can.
#ifdef NDEBUG
if (lazyVarsAlreadyHaveImplementation.hasValue() &&
lazyVarsAlreadyHaveImplementation.getValue())
continue;
#endif
auto *prop = dyn_cast<VarDecl>(D);
if (!prop)
continue;
if (prop->getAttrs().hasAttribute<LazyAttr>() && !prop->isStatic()
&& prop->getGetter()) {
bool hasImplementation = prop->getGetter()->hasBody();
if (lazyVarsAlreadyHaveImplementation.hasValue()) {
assert(lazyVarsAlreadyHaveImplementation.getValue() ==
hasImplementation &&
"only some lazy vars already have implementations");
} else {
lazyVarsAlreadyHaveImplementation = hasImplementation;
}
if (!hasImplementation)
TC.completeLazyVarImplementation(prop);
}
}
// FIXME: We need to add implicit initializers and dtors when a decl is
// touched, because it affects vtable layout. If you're not defining the
// class, you shouldn't have to know what the vtable layout is.
if (auto *CD = dyn_cast<ClassDecl>(nominal)) {
TC.addImplicitConstructors(CD);
TC.addImplicitDestructor(CD);
}
}
// Complete any conformances that we used.
for (unsigned i = 0; i != TC.UsedConformances.size(); ++i) {
auto conformance = TC.UsedConformances[i];
if (conformance->isIncomplete())
TC.checkConformance(conformance);
}
TC.UsedConformances.clear();
} while (currentFunctionIdx < TC.definedFunctions.size() ||
currentExternalDef < TC.Context.ExternalDefinitions.size() ||
!TC.UsedConformances.empty());
// FIXME: Horrible hack. Store this somewhere more appropriate.
TC.Context.LastCheckedExternalDefinition = currentExternalDef;
// Compute captures for functions and closures we visited.
for (AnyFunctionRef closure : TC.ClosuresWithUncomputedCaptures) {
TC.computeCaptures(closure);
}
for (AbstractFunctionDecl *FD : reversed(TC.definedFunctions)) {
TC.computeCaptures(FD);
}
// Check error-handling correctness for all the functions defined in
// this file. This can depend on all of their interior function
// bodies having been type-checked.
for (AbstractFunctionDecl *FD : TC.definedFunctions) {
TC.checkFunctionErrorHandling(FD);
}
for (auto decl : TC.Context.ExternalDefinitions) {
if (auto fn = dyn_cast<AbstractFunctionDecl>(decl)) {
TC.checkFunctionErrorHandling(fn);
}
}
}
void swift::typeCheckExternalDefinitions(SourceFile &SF) {
assert(SF.ASTStage == SourceFile::TypeChecked);
auto &Ctx = SF.getASTContext();
TypeChecker TC(Ctx);
typeCheckFunctionsAndExternalDecls(TC);
}
void swift::performTypeChecking(SourceFile &SF, TopLevelContext &TLC,
OptionSet<TypeCheckingFlags> Options,
unsigned StartElem,
unsigned WarnLongFunctionBodies) {
if (SF.ASTStage == SourceFile::TypeChecked)
return;
// Make sure that name binding has been completed before doing any type
// checking.
{
SharedTimer timer("Name binding");
performNameBinding(SF, StartElem);
}
auto &Ctx = SF.getASTContext();
{
// NOTE: The type checker is scoped to be torn down before AST
// verification.
TypeChecker TC(Ctx);
SharedTimer timer("Type checking / Semantic analysis");
TC.setWarnLongFunctionBodies(WarnLongFunctionBodies);
if (Options.contains(TypeCheckingFlags::DebugTimeFunctionBodies))
TC.enableDebugTimeFunctionBodies();
if (Options.contains(TypeCheckingFlags::ForImmediateMode))
TC.setInImmediateMode(true);
// Lookup the swift module. This ensures that we record all known
// protocols in the AST.
(void) TC.getStdlibModule(&SF);
if (!Ctx.LangOpts.DisableAvailabilityChecking) {
// Build the type refinement hierarchy for the primary
// file before type checking.
TC.buildTypeRefinementContextHierarchy(SF, StartElem);
}
// Resolve extensions. This has to occur first during type checking,
// because the extensions need to be wired into the AST for name lookup
// to work.
// FIXME: We can have interesting ordering dependencies among the various
// extensions, so we'll need to be smarter here.
// FIXME: The current source file needs to be handled specially, because of
// private extensions.
SF.forAllVisibleModules([&](Module::ImportedModule import) {
// FIXME: Respect the access path?
for (auto file : import.second->getFiles()) {
auto SF = dyn_cast<SourceFile>(file);
if (!SF)
continue;
for (auto D : SF->Decls) {
if (auto ED = dyn_cast<ExtensionDecl>(D))
bindExtensionDecl(ED, TC);
}
}
});
// FIXME: Check for cycles in class inheritance here?
// Type check the top-level elements of the source file.
for (auto D : llvm::makeArrayRef(SF.Decls).slice(StartElem)) {
if (isa<TopLevelCodeDecl>(D))
continue;
TC.typeCheckDecl(D, /*isFirstPass*/true);
}
// At this point, we can perform general name lookup into any type.
// We don't know the types of all the global declarations in the first
// pass, which means we can't completely analyze everything. Perform the
// second pass now.
bool hasTopLevelCode = false;
for (auto D : llvm::makeArrayRef(SF.Decls).slice(StartElem)) {
if (TopLevelCodeDecl *TLCD = dyn_cast<TopLevelCodeDecl>(D)) {
hasTopLevelCode = true;
// Immediately perform global name-binding etc.
TC.typeCheckTopLevelCodeDecl(TLCD);
} else {
TC.typeCheckDecl(D, /*isFirstPass*/false);
}
}
if (hasTopLevelCode) {
TC.contextualizeTopLevelCode(TLC,
llvm::makeArrayRef(SF.Decls).slice(StartElem));
}
// If we're in REPL mode, inject temporary result variables and other stuff
// that the REPL needs to synthesize.
if (SF.Kind == SourceFileKind::REPL && !TC.Context.hadError())
TC.processREPLTopLevel(SF, TLC, StartElem);
typeCheckFunctionsAndExternalDecls(TC);
}
// Checking that benefits from having the whole module available.
if (!(Options & TypeCheckingFlags::DelayWholeModuleChecking)) {
performWholeModuleTypeChecking(SF);
}
// Verify that we've checked types correctly.
SF.ASTStage = SourceFile::TypeChecked;
{
SharedTimer timer("AST verification");
// Verify the SourceFile.
verify(SF);
// Verify imported modules.
#ifndef NDEBUG
if (SF.Kind != SourceFileKind::REPL &&
!Ctx.LangOpts.DebuggerSupport) {
Ctx.verifyAllLoadedModules();
}
#endif
}
}
void swift::finishTypeChecking(SourceFile &SF) {
auto &Ctx = SF.getASTContext();
TypeChecker TC(Ctx);
for (auto D : SF.Decls)
if (auto PD = dyn_cast<ProtocolDecl>(D))
TC.inferDefaultWitnesses(PD);
}
void swift::performWholeModuleTypeChecking(SourceFile &SF) {
auto &Ctx = SF.getASTContext();
Ctx.diagnoseAttrsRequiringFoundation(SF);
Ctx.diagnoseObjCMethodConflicts(SF);
Ctx.diagnoseObjCUnsatisfiedOptReqConflicts(SF);
Ctx.diagnoseUnintendedObjCMethodOverrides(SF);
}
bool swift::performTypeLocChecking(ASTContext &Ctx, TypeLoc &T,
bool isSILType, DeclContext *DC,
bool ProduceDiagnostics) {
TypeResolutionOptions options;
// Fine to have unbound generic types.
options |= TR_AllowUnboundGenerics;
if (isSILType)
options |= TR_SILType;
if (ProduceDiagnostics) {
return TypeChecker(Ctx).validateType(T, DC, options);
} else {
// Set up a diagnostics engine that swallows diagnostics.
DiagnosticEngine Diags(Ctx.SourceMgr);
return TypeChecker(Ctx, Diags).validateType(T, DC, options);
}
}
/// Expose TypeChecker's handling of GenericParamList to SIL parsing.
GenericSignature *swift::handleSILGenericParams(ASTContext &Ctx,
GenericParamList *genericParams,
DeclContext *DC) {
return TypeChecker(Ctx).handleSILGenericParams(genericParams, DC);
}
bool swift::typeCheckCompletionDecl(Decl *D) {
auto &Ctx = D->getASTContext();
// Set up a diagnostics engine that swallows diagnostics.
DiagnosticEngine Diags(Ctx.SourceMgr);
TypeChecker TC(Ctx, Diags);
TC.typeCheckDecl(D, true);
return true;
}
bool swift::isConvertibleTo(Type Ty1, Type Ty2, DeclContext &DC) {
auto &Ctx = DC.getASTContext();
// We try to reuse the type checker associated with the ast context first.
if (Ctx.getLazyResolver()) {
TypeChecker *TC = static_cast<TypeChecker*>(Ctx.getLazyResolver());
return TC->isConvertibleTo(Ty1, Ty2, &DC);
} else {
DiagnosticEngine Diags(Ctx.SourceMgr);
return (new TypeChecker(Ctx, Diags))->isConvertibleTo(Ty1, Ty2, &DC);
}
}
Type swift::lookUpTypeInContext(DeclContext *DC, StringRef Name) {
auto &Ctx = DC->getASTContext();
auto ReturnResult = [](UnqualifiedLookup &Lookup) {
if (auto Result = Lookup.getSingleTypeResult())
return Result->getDeclaredType();
return Type();
};
if (Ctx.getLazyResolver()) {
UnqualifiedLookup Lookup(DeclName(Ctx.getIdentifier(Name)), DC,
Ctx.getLazyResolver(), false, SourceLoc(), true);
return ReturnResult(Lookup);
} else {
DiagnosticEngine Diags(Ctx.SourceMgr);
LazyResolver *Resolver = new TypeChecker(Ctx, Diags);
UnqualifiedLookup Lookup(DeclName(Ctx.getIdentifier(Name)), DC,
Resolver, false, SourceLoc(), true);
return ReturnResult(Lookup);
}
}
static Optional<Type> getTypeOfCompletionContextExpr(
TypeChecker &TC,
DeclContext *DC,
CompletionTypeCheckKind kind,
Expr *&parsedExpr,
ConcreteDeclRef &referencedDecl) {
switch (kind) {
case CompletionTypeCheckKind::Normal:
// Handle below.
break;
case CompletionTypeCheckKind::ObjCKeyPath:
referencedDecl = nullptr;
if (auto keyPath = dyn_cast<ObjCKeyPathExpr>(parsedExpr))
return TC.checkObjCKeyPathExpr(DC, keyPath, /*requireResulType=*/true);
return None;
}
CanType originalType = parsedExpr->getType().getCanonicalTypeOrNull();
if (auto T = TC.getTypeOfExpressionWithoutApplying(parsedExpr, DC,
referencedDecl, FreeTypeVariableBinding::GenericParameters))
return T;
// Try to recover if we've made any progress.
if (parsedExpr && !isa<ErrorExpr>(parsedExpr) && parsedExpr->getType() &&
!parsedExpr->getType()->is<ErrorType>() &&
parsedExpr->getType().getCanonicalTypeOrNull() != originalType) {
return parsedExpr->getType();
}
return None;
}
/// \brief Return the type of an expression parsed during code completion, or
/// a null \c Type on error.
Optional<Type> swift::getTypeOfCompletionContextExpr(
ASTContext &Ctx,
DeclContext *DC,
CompletionTypeCheckKind kind,
Expr *&parsedExpr,
ConcreteDeclRef &referencedDecl) {
if (Ctx.getLazyResolver()) {
TypeChecker *TC = static_cast<TypeChecker *>(Ctx.getLazyResolver());
return ::getTypeOfCompletionContextExpr(*TC, DC, kind, parsedExpr,
referencedDecl);
} else {
// Set up a diagnostics engine that swallows diagnostics.
DiagnosticEngine diags(Ctx.SourceMgr);
TypeChecker TC(Ctx, diags);
// Try to solve for the actual type of the expression.
return ::getTypeOfCompletionContextExpr(TC, DC, kind, parsedExpr,
referencedDecl);
}
}
bool swift::typeCheckCompletionSequence(DeclContext *DC, Expr *&parsedExpr) {
auto &ctx = DC->getASTContext();
if (ctx.getLazyResolver()) {
TypeChecker *TC = static_cast<TypeChecker *>(ctx.getLazyResolver());
return TC->typeCheckCompletionSequence(parsedExpr, DC);
} else {
// Set up a diagnostics engine that swallows diagnostics.
DiagnosticEngine diags(ctx.SourceMgr);
TypeChecker TC(ctx, diags);
return TC.typeCheckCompletionSequence(parsedExpr, DC);
}
}
bool swift::typeCheckExpression(DeclContext *DC, Expr *&parsedExpr) {
auto &ctx = DC->getASTContext();
if (ctx.getLazyResolver()) {
TypeChecker *TC = static_cast<TypeChecker *>(ctx.getLazyResolver());
return TC->typeCheckExpression(parsedExpr, DC);
} else {
// Set up a diagnostics engine that swallows diagnostics.
DiagnosticEngine diags(ctx.SourceMgr);
TypeChecker TC(ctx, diags);
return TC.typeCheckExpression(parsedExpr, DC);
}
}
bool swift::typeCheckAbstractFunctionBodyUntil(AbstractFunctionDecl *AFD,
SourceLoc EndTypeCheckLoc) {
auto &Ctx = AFD->getASTContext();
// Set up a diagnostics engine that swallows diagnostics.
DiagnosticEngine Diags(Ctx.SourceMgr);
TypeChecker TC(Ctx, Diags);
return !TC.typeCheckAbstractFunctionBodyUntil(AFD, EndTypeCheckLoc);
}
bool swift::typeCheckTopLevelCodeDecl(TopLevelCodeDecl *TLCD) {
auto &Ctx = static_cast<Decl *>(TLCD)->getASTContext();
// Set up a diagnostics engine that swallows diagnostics.
DiagnosticEngine Diags(Ctx.SourceMgr);
TypeChecker TC(Ctx, Diags);
TC.typeCheckTopLevelCodeDecl(TLCD);
return true;
}
static void deleteTypeCheckerAndDiags(LazyResolver *resolver) {
DiagnosticEngine &diags = static_cast<TypeChecker*>(resolver)->Diags;
delete resolver;
delete &diags;
}
OwnedResolver swift::createLazyResolver(ASTContext &Ctx) {
auto diags = new DiagnosticEngine(Ctx.SourceMgr);
return OwnedResolver(new TypeChecker(Ctx, *diags),
&deleteTypeCheckerAndDiags);
}
void TypeChecker::diagnoseAmbiguousMemberType(Type baseTy,
SourceRange baseRange,
Identifier name,
SourceLoc nameLoc,
LookupTypeResult &lookup) {
if (auto moduleTy = baseTy->getAs<ModuleType>()) {
diagnose(nameLoc, diag::ambiguous_module_type, name,
moduleTy->getModule()->getName())
.highlight(baseRange);
} else {
diagnose(nameLoc, diag::ambiguous_member_type, name, baseTy)
.highlight(baseRange);
}
for (const auto &member : lookup) {
diagnose(member.first, diag::found_candidate_type,
member.second);
}
}
/// Returns the first availability attribute on the declaration that is active
/// on the target platform.
static const AvailableAttr *getActiveAvailableAttribute(const Decl *D,
ASTContext &AC) {
for (auto Attr : D->getAttrs())
if (auto AvAttr = dyn_cast<AvailableAttr>(Attr)) {
if (!AvAttr->isInvalid() && AvAttr->isActivePlatform(AC)) {
return AvAttr;
}
}
return nullptr;
}
/// Returns true if there is any availability attribute on the declaration
/// that is active on the target platform.
static bool hasActiveAvailableAttribute(Decl *D,
ASTContext &AC) {
return getActiveAvailableAttribute(D, AC);
}
namespace {
/// A class to walk the AST to build the type refinement context hierarchy.
class TypeRefinementContextBuilder : private ASTWalker {
struct ContextInfo {
TypeRefinementContext *TRC;
/// The node whose end marks the end of the refinement context.
/// If the builder sees this node in a post-visitor, it will pop
/// the context from the stack. This node can be null (ParentTy()),
/// indicating that custom logic elsewhere will handle removing
/// the context when needed.
ParentTy ScopeNode;
};
std::vector<ContextInfo> ContextStack;
TypeChecker &TC;
/// A mapping from abstract storage declarations with accessors to
/// to the type refinement contexts for those declarations. We refer to
/// this map to determine the appropriate parent TRC to use when
/// walking the accessor function.
llvm::DenseMap<AbstractStorageDecl *, TypeRefinementContext *>
StorageContexts;
TypeRefinementContext *getCurrentTRC() {
return ContextStack.back().TRC;
}
void pushContext(TypeRefinementContext *TRC, ParentTy PopAfterNode) {
ContextInfo Info;
Info.TRC = TRC;
Info.ScopeNode = PopAfterNode;
ContextStack.push_back(Info);
}
public:
TypeRefinementContextBuilder(TypeRefinementContext *TRC, TypeChecker &TC)
: TC(TC) {
assert(TRC);
pushContext(TRC, ParentTy());
}
void build(Decl *D) {
unsigned StackHeight = ContextStack.size();
D->walk(*this);
assert(ContextStack.size() == StackHeight);
(void)StackHeight;
}
void build(Stmt *S) {
unsigned StackHeight = ContextStack.size();
S->walk(*this);
assert(ContextStack.size() == StackHeight);
(void)StackHeight;
}
void build(Expr *E) {
unsigned StackHeight = ContextStack.size();
E->walk(*this);
assert(ContextStack.size() == StackHeight);
(void)StackHeight;
}
private:
virtual bool walkToDeclPre(Decl *D) override {
TypeRefinementContext *DeclTRC = getNewContextForWalkOfDecl(D);
if (DeclTRC) {
pushContext(DeclTRC, D);
}
return true;
}
virtual bool walkToDeclPost(Decl *D) override {
if (ContextStack.back().ScopeNode.getAsDecl() == D) {
ContextStack.pop_back();
}
return true;
}
/// Returns a new context to be introduced for the declaration, or nullptr
/// if no new context should be introduced.
TypeRefinementContext *getNewContextForWalkOfDecl(Decl *D) {
if (auto FD = dyn_cast<FuncDecl>(D)) {
if (FD->isAccessor()) {
// Use TRC of the storage rather the current TRC when walking this
// function.
auto it = StorageContexts.find(FD->getAccessorStorageDecl());
if (it != StorageContexts.end()) {
return it->second;
}
}
}
if (declarationIntroducesNewContext(D)) {
return buildDeclarationRefinementContext(D);
}
return nullptr;
}
/// Builds the type refinement hierarchy for the body of the function.
TypeRefinementContext *buildDeclarationRefinementContext(Decl *D) {
// We require a valid range in order to be able to query for the TRC
// corresponding to a given SourceLoc.
// If this assert fires, it means we have probably synthesized an implicit
// declaration without location information. The appropriate fix is
// probably to gin up a source range for the declaration when synthesizing
// it.
assert(D->getSourceRange().isValid());
// The potential versions in the declaration are constrained by both
// the declared availability of the declaration and the potential versions
// of its lexical context.
AvailabilityContext DeclInfo =
swift::AvailabilityInference::availableRange(D, TC.Context);
DeclInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());
TypeRefinementContext *NewTRC =
TypeRefinementContext::createForDecl(TC.Context, D, getCurrentTRC(),
DeclInfo,
refinementSourceRangeForDecl(D));
// Record the TRC for this storage declaration so that
// when we process the accessor, we can use this TRC as the
// parent.
if (auto *StorageDecl = dyn_cast<AbstractStorageDecl>(D)) {
if (StorageDecl->hasAccessorFunctions()) {
StorageContexts[StorageDecl] = NewTRC;
}
}
return NewTRC;
}
/// Returns true if the declaration should introduce a new refinement context.
bool declarationIntroducesNewContext(Decl *D) {
if (!isa<ValueDecl>(D) && !isa<ExtensionDecl>(D)) {
return false;
}
// No need to introduce a context if the declaration does not have an
// availability attribute.
if (!hasActiveAvailableAttribute(D, TC.Context)) {
return false;
}
// Only introduce for an AbstractStorageDecl if it is not local.
// We introduce for the non-local case because these may
// have getters and setters (and these may be synthesized, so they might
// not even exist yet).
if (auto *storageDecl = dyn_cast<AbstractStorageDecl>(D)) {
if (storageDecl->getDeclContext()->isLocalContext()) {
// No need to
return false;
}
}
return true;
}
/// Returns the source range which should be refined by declaration. This
/// provides a convenient place to specify the refined range when it is
/// different than the declaration's source range.
SourceRange refinementSourceRangeForDecl(Decl *D) {
if (auto *storageDecl = dyn_cast<AbstractStorageDecl>(D)) {
// Use the declaration's availability for the context when checking
// the bodies of its accessors.
// HACK: For synthesized trivial accessors we may have not a valid
// location for the end of the braces, so in that case we will fall back
// to using the range for the storage declaration. The right fix here is
// to update AbstractStorageDecl::addTrivialAccessors() to take brace
// locations and have callers of that method provide appropriate source
// locations.
SourceLoc BracesEnd = storageDecl->getBracesRange().End;
if (storageDecl->hasAccessorFunctions() && BracesEnd.isValid()) {
return SourceRange(storageDecl->getStartLoc(),
BracesEnd);
}
// For a variable declaration (without accessors) we use the range of the
// containing pattern binding declaration to make sure that we include
// any type annotation in the type refinement context range.
if (auto varDecl = dyn_cast<VarDecl>(storageDecl)) {
auto *PBD = varDecl->getParentPatternBinding();
if (PBD)
return PBD->getSourceRange();
}
}
return D->getSourceRange();
}
virtual std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
if (auto *IS = dyn_cast<IfStmt>(S)) {
buildIfStmtRefinementContext(IS);
return std::make_pair(false, S);
}
if (auto *RS = dyn_cast<GuardStmt>(S)) {
buildGuardStmtRefinementContext(RS);
return std::make_pair(false, S);
}
if (auto *WS = dyn_cast<WhileStmt>(S)) {
buildWhileStmtRefinementContext(WS);
return std::make_pair(false, S);
}
return std::make_pair(true, S);
}
virtual Stmt *walkToStmtPost(Stmt *S) override {
// If we have multiple guard statements in the same block
// then we may have multiple refinement contexts to pop
// after walking that block.
while (!ContextStack.empty() &&
ContextStack.back().ScopeNode.getAsStmt() == S) {
ContextStack.pop_back();
}
return S;
}
/// Builds the type refinement hierarchy for the IfStmt if the guard
/// introduces a new refinement context for the Then branch.
/// There is no need for the caller to explicitly traverse the children
/// of this node.
void buildIfStmtRefinementContext(IfStmt *IS) {
Optional<AvailabilityContext> ThenRange;
Optional<AvailabilityContext> ElseRange;
std::tie(ThenRange, ElseRange) =
buildStmtConditionRefinementContext(IS->getCond());
if (ThenRange.hasValue()) {
// Create a new context for the Then branch and traverse it in that new
// context.
auto *ThenTRC =
TypeRefinementContext::createForIfStmtThen(TC.Context, IS,
getCurrentTRC(),
ThenRange.getValue());
TypeRefinementContextBuilder(ThenTRC, TC).build(IS->getThenStmt());
} else {
build(IS->getThenStmt());
}
Stmt *ElseStmt = IS->getElseStmt();
if (!ElseStmt)
return;
// Refine the else branch if we're given a version range for that branch.
// For now, if present, this will only be the empty range, indicating
// that the branch is dead. We use it to suppress potential unavailability
// and deprecation diagnostics on code that definitely will not run with
// the current platform and minimum deployment target.
// If we add a more precise version range lattice (i.e., one that can
// support "<") we should create non-empty contexts for the Else branch.
if (ElseRange.hasValue()) {
// Create a new context for the Then branch and traverse it in that new
// context.
auto *ElseTRC =
TypeRefinementContext::createForIfStmtElse(TC.Context, IS,
getCurrentTRC(),
ElseRange.getValue());
TypeRefinementContextBuilder(ElseTRC, TC).build(ElseStmt);
} else {
build(IS->getElseStmt());
}
}
/// Builds the type refinement hierarchy for the WhileStmt if the guard
/// introduces a new refinement context for the body branch.
/// There is no need for the caller to explicitly traverse the children
/// of this node.
void buildWhileStmtRefinementContext(WhileStmt *WS) {
Optional<AvailabilityContext> BodyRange =
buildStmtConditionRefinementContext(WS->getCond()).first;
if (BodyRange.hasValue()) {
// Create a new context for the body and traverse it in the new
// context.
auto *BodyTRC = TypeRefinementContext::createForWhileStmtBody(
TC.Context, WS, getCurrentTRC(), BodyRange.getValue());
TypeRefinementContextBuilder(BodyTRC, TC).build(WS->getBody());
} else {
build(WS->getBody());
}
}
/// Builds the type refinement hierarchy for the GuardStmt and pushes
/// the fallthrough context onto the context stack so that subsequent
/// AST elements in the same scope are analyzed in the context of the
/// fallthrough TRC.
void buildGuardStmtRefinementContext(GuardStmt *GS) {
// 'guard' statements fall through if all of the
// guard conditions are true, so we refine the range after the require
// until the end of the enclosing block.
// if ... {
// guard available(...) else { return } <-- Refined range starts here
// ...
// } <-- Refined range ends here
//
// This is slightly tricky because, unlike our other control constructs,
// the refined region is not lexically contained inside the construct
// introducing the refinement context.
Optional<AvailabilityContext> FallthroughRange;
Optional<AvailabilityContext> ElseRange;
std::tie(FallthroughRange, ElseRange) =
buildStmtConditionRefinementContext(GS->getCond());
if (Stmt *ElseBody = GS->getBody()) {
if (ElseRange.hasValue()) {
auto *TrueTRC = TypeRefinementContext::createForGuardStmtElse(
TC.Context, GS, getCurrentTRC(), ElseRange.getValue());
TypeRefinementContextBuilder(TrueTRC, TC).build(ElseBody);
} else {
build(ElseBody);
}
}
BraceStmt *ParentBrace = dyn_cast<BraceStmt>(Parent.getAsStmt());
assert(ParentBrace && "Expected parent of GuardStmt to be BraceStmt");
if (!FallthroughRange.hasValue())
return;
// Create a new context for the fallthrough.
auto *FallthroughTRC =
TypeRefinementContext::createForGuardStmtFallthrough(TC.Context, GS,
ParentBrace, getCurrentTRC(), FallthroughRange.getValue());
pushContext(FallthroughTRC, ParentBrace);
}
/// Build the type refinement context for a StmtCondition and return a pair
/// of optional version ranges, the first for the true branch and the second
/// for the false branch. A value of None for a given branch indicates that
/// the branch does not introduce a new refinement.
std::pair<Optional<AvailabilityContext>, Optional<AvailabilityContext>>
buildStmtConditionRefinementContext(StmtCondition Cond) {
// Any refinement contexts introduced in the statement condition
// will end at the end of the last condition element.
StmtConditionElement LastElement = Cond.back();
// Keep track of how many nested refinement contexts we have pushed on
// the context stack so we can pop them when we're done building the
// context for the StmtCondition.
unsigned NestedCount = 0;
// Tracks the potential version range when the condition is false.
auto FalseFlow = AvailabilityContext::neverAvailable();
TypeRefinementContext *StartingTRC = getCurrentTRC();
for (StmtConditionElement Element : Cond) {
TypeRefinementContext *CurrentTRC = getCurrentTRC();
AvailabilityContext CurrentInfo = CurrentTRC->getAvailabilityInfo();
// If the element is not a condition, walk it in the current TRC.
if (Element.getKind() != StmtConditionElement::CK_Availability) {
// Assume any condition element that is not a #available() can
// potentially be false, so conservatively combine the version
// range of the current context with the accumulated false flow
// of all other conjuncts.
FalseFlow.unionWith(CurrentInfo);
Element.walk(*this);
continue;
}
// #available query: introduce a new refinement context for the statement
// condition elements following it.
auto *Query = Element.getAvailability();
// If this query expression has no queries, we will not introduce a new
// refinement context. We do not diagnose here: a diagnostic will already
// have been emitted by the parser.
if (Query->getQueries().size() == 0)
continue;
AvailabilitySpec *Spec = bestActiveSpecForQuery(Query);
if (!Spec) {
// We couldn't find an appropriate spec for the current platform,
// so rather than refining, emit a diagnostic and just use the current
// TRC.
TC.Diags.diagnose(Query->getLoc(),
diag::availability_query_required_for_platform,
platformString(targetPlatform(TC.getLangOpts())));
continue;
}
AvailabilityContext NewConstraint = contextForSpec(Spec);
Query->setAvailableRange(NewConstraint.getOSVersion());
if (Spec->getKind() == AvailabilitySpecKind::OtherPlatform) {
// The wildcard spec '*' represents the minimum deployment target, so
// there is no need to create a refinement context for this query.
// Further, we won't diagnose for useless #available() conditions
// where * matched on this platform -- presumably those conditions are
// needed for some other platform.
continue;
}
// If the version range for the current TRC is completely contained in
// the range for the spec, then a version query can never be false, so the
// spec is useless. If so, report this.
if (CurrentInfo.isContainedIn(NewConstraint)) {
DiagnosticEngine &Diags = TC.Diags;
// Some availability checks will always pass because the minimum
// deployment target guarantees they will never be false. We don't
// diagnose these checks as useless because the source file may
// be shared with other projects/targets having older deployment
// targets. We don't currently have a mechanism for the user to
// suppress these warnings (for example, by indicating when the
// required compatibility version is different than the deployment
// target).
if (CurrentTRC->getReason() != TypeRefinementContext::Reason::Root) {
Diags.diagnose(Query->getLoc(),
diag::availability_query_useless_enclosing_scope,
platformString(targetPlatform(TC.getLangOpts())));
Diags.diagnose(CurrentTRC->getIntroductionLoc(),
diag::availability_query_useless_enclosing_scope_here);
}
// No need to actually create the refinement context if we know it is
// useless.
continue;
}
// If the #available() is not useless then there is potential false flow,
// so join the false flow with the potential versions of the current
// context.
// We could be more precise here if we enriched the lattice to include
// ranges of the form [x, y).
FalseFlow.unionWith(CurrentInfo);
auto *TRC = TypeRefinementContext::createForConditionFollowingQuery(
TC.Context, Query, LastElement, CurrentTRC, NewConstraint);
pushContext(TRC, ParentTy());
NestedCount++;
}
Optional<AvailabilityContext> FalseRefinement = None;
// The version range for the false branch should never have any versions
// that weren't possible when the condition started evaluating.
assert(FalseFlow.isContainedIn(StartingTRC->getAvailabilityInfo()));
// If the starting version range is not completely contained in the
// false flow version range then it must be the case that false flow range
// is strictly smaller than the starting range (because the false flow
// range *is* contained in the starting range), so we should introduce a
// new refinement for the false flow.
if (!StartingTRC->getAvailabilityInfo().isContainedIn(FalseFlow)) {
FalseRefinement = FalseFlow;
}
if (NestedCount == 0)
return std::make_pair(None, FalseRefinement);
TypeRefinementContext *NestedTRC = getCurrentTRC();
while (NestedCount-- > 0)
ContextStack.pop_back();
assert(getCurrentTRC() == StartingTRC);
return std::make_pair(NestedTRC->getAvailabilityInfo(), FalseRefinement);
}
/// Return the best active spec for the target platform or nullptr if no
/// such spec exists.
AvailabilitySpec *bestActiveSpecForQuery(PoundAvailableInfo *available) {
OtherPlatformAvailabilitySpec *FoundOtherSpec = nullptr;
for (auto *Spec : available->getQueries()) {
if (auto *OtherSpec = dyn_cast<OtherPlatformAvailabilitySpec>(Spec)) {
FoundOtherSpec = OtherSpec;
continue;
}
auto *VersionSpec = dyn_cast<VersionConstraintAvailabilitySpec>(Spec);
if (!VersionSpec)
continue;
// FIXME: This is not quite right: we want to handle AppExtensions
// properly. For example, on the OSXApplicationExtension platform
// we want to chose the OS X spec unless there is an explicit
// OSXApplicationExtension spec.
if (isPlatformActive(VersionSpec->getPlatform(), TC.getLangOpts())) {
return VersionSpec;
}
}
// If we have reached this point, we found no spec for our target, so
// we return the other spec ('*'), if we found it, or nullptr, if not.
return FoundOtherSpec;
}
/// Return the availability context for the given spec.
AvailabilityContext contextForSpec(AvailabilitySpec *Spec) {
if (isa<OtherPlatformAvailabilitySpec>(Spec)) {
return AvailabilityContext::alwaysAvailable();
}
auto *VersionSpec = cast<VersionConstraintAvailabilitySpec>(Spec);
return AvailabilityContext(VersionRange::allGTE(VersionSpec->getVersion()));
}
virtual Expr *walkToExprPost(Expr *E) override {
if (ContextStack.back().ScopeNode.getAsExpr() == E) {
ContextStack.pop_back();
}
return E;
}
};
}
void TypeChecker::buildTypeRefinementContextHierarchy(SourceFile &SF,
unsigned StartElem) {
TypeRefinementContext *RootTRC = SF.getTypeRefinementContext();
// If we are not starting at the beginning of the source file, we had better
// already have a root type refinement context.
assert(StartElem == 0 || RootTRC);
ASTContext &AC = SF.getASTContext();
if (!RootTRC) {
// The root type refinement context reflects the fact that all parts of
// the source file are guaranteed to be executing on at least the minimum
// platform version.
AvailabilityContext MinPlatformReq{
VersionRange::allGTE(AC.LangOpts.getMinPlatformVersion())};
RootTRC = TypeRefinementContext::createRoot(&SF, MinPlatformReq);
SF.setTypeRefinementContext(RootTRC);
}
// Build refinement contexts, if necessary, for all declarations starting
// with StartElem.
TypeRefinementContextBuilder Builder(RootTRC, *this);
for (auto D : llvm::makeArrayRef(SF.Decls).slice(StartElem)) {
Builder.build(D);
}
}
TypeRefinementContext *
TypeChecker::getOrBuildTypeRefinementContext(SourceFile *SF) {
TypeRefinementContext *TRC = SF->getTypeRefinementContext();
if (!TRC) {
buildTypeRefinementContextHierarchy(*SF, 0);
TRC = SF->getTypeRefinementContext();
}
return TRC;
}
AvailabilityContext
TypeChecker::overApproximateAvailabilityAtLocation(SourceLoc loc,
const DeclContext *DC) {
SourceFile *SF = DC->getParentSourceFile();
// If our source location is invalid (this may be synthesized code), climb
// the decl context hierarchy until we find a location that is valid,
// collecting availability ranges on the way up.
// We will combine the version ranges from these annotations
// with the TRC for the valid location to overapproximate the running
// OS versions at the original source location.
// Because we are climbing DeclContexts we will miss refinement contexts in
// synthesized code that are introduced by AST elements that are themselves
// not DeclContexts, such as #available(..) and property declarations.
// That is, a reference with an invalid location that is contained
// inside a #available() and with no intermediate DeclContext will not be
// refined. For now, this is fine -- but if we ever synthesize #available(),
// this will be a real problem.
// We can assume we are running on at least the minimum deployment target.
AvailabilityContext OverApproximateContext{
VersionRange::allGTE(getLangOpts().getMinPlatformVersion())};
while (DC && loc.isInvalid()) {
const Decl *D = DC->getInnermostDeclarationDeclContext();
if (!D)
break;
loc = D->getLoc();
Optional<AvailabilityContext> Info =
AvailabilityInference::annotatedAvailableRange(D, Context);
if (Info.hasValue()) {
OverApproximateContext.constrainWith(Info.getValue());
}
DC = D->getDeclContext();
}
if (SF && loc.isValid()) {
TypeRefinementContext *rootTRC = getOrBuildTypeRefinementContext(SF);
TypeRefinementContext *TRC =
rootTRC->findMostRefinedSubContext(loc, Context.SourceMgr);
OverApproximateContext.constrainWith(TRC->getAvailabilityInfo());
}
return OverApproximateContext;
}
bool TypeChecker::isDeclAvailable(const Decl *D, SourceLoc referenceLoc,
const DeclContext *referenceDC,
AvailabilityContext &OutAvailableInfo) {
AvailabilityContext safeRangeUnderApprox{
AvailabilityInference::availableRange(D, Context)};
AvailabilityContext runningOSOverApprox =
overApproximateAvailabilityAtLocation(referenceLoc, referenceDC);
// The reference is safe if an over-approximation of the running OS
// versions is fully contained within an under-approximation
// of the versions on which the declaration is available. If this
// containment cannot be guaranteed, we say the reference is
// not available.
if (!(runningOSOverApprox.isContainedIn(safeRangeUnderApprox))) {
OutAvailableInfo = safeRangeUnderApprox;
return false;
}
return true;
}
Optional<UnavailabilityReason>
TypeChecker::checkDeclarationAvailability(const Decl *D, SourceLoc referenceLoc,
const DeclContext *referenceDC) {
if (Context.LangOpts.DisableAvailabilityChecking) {
return None;
}
if (!referenceDC->getParentSourceFile()) {
// We only check availability if this reference is in a source file; we do
// not check in other kinds of FileUnits.
return None;
}
auto safeRangeUnderApprox = AvailabilityContext::neverAvailable();
if (isDeclAvailable(D, referenceLoc, referenceDC, safeRangeUnderApprox)) {
return None;
}
// safeRangeUnderApprox now holds the safe range.
VersionRange version = safeRangeUnderApprox.getOSVersion();
return UnavailabilityReason::requiresVersionRange(version);
}
void TypeChecker::diagnosePotentialUnavailability(
const ValueDecl *D, SourceRange ReferenceRange,
const DeclContext *ReferenceDC,
const UnavailabilityReason &Reason) {
diagnosePotentialUnavailability(D, D->getFullName(), ReferenceRange,
ReferenceDC, Reason);
}
/// A class that walks the AST to find the innermost (i.e., deepest) node that
/// contains a target SourceRange and matches a particular criterion.
/// This class finds the innermost nodes of interest by walking
/// down the root until it has found the target range (in a Pre-visitor)
/// and then recording the innermost node on the way back up in the
/// Post-visitors. It does its best to not search unnecessary subtrees,
/// although this is complicated by the fact that not all nodes have
/// source range information.
class InnermostAncestorFinder : private ASTWalker {
public:
/// The type of a match predicate, which takes as input a node and its
/// parent and returns a bool indicating whether the node matches.
typedef std::function<bool(ASTNode, ASTWalker::ParentTy)> MatchPredicate;
private:
const SourceRange TargetRange;
const SourceManager &SM;
const MatchPredicate Predicate;
bool FoundTarget = false;
Optional<ASTNode> InnermostMatchingNode;
public:
InnermostAncestorFinder(SourceRange TargetRange, const SourceManager &SM,
ASTNode SearchNode, const MatchPredicate &Predicate)
: TargetRange(TargetRange), SM(SM), Predicate(Predicate) {
assert(TargetRange.isValid());
SearchNode.walk(*this);
}
/// Returns the innermost node containing the target range that matches
/// the predicate.
Optional<ASTNode> getInnermostMatchingNode() { return InnermostMatchingNode; }
virtual std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
return std::make_pair(walkToRangePre(E->getSourceRange()), E);
}
virtual std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
return std::make_pair(walkToRangePre(S->getSourceRange()), S);
}
virtual bool walkToDeclPre(Decl *D) override {
return walkToRangePre(D->getSourceRange());
}
virtual std::pair<bool, Pattern *> walkToPatternPre(Pattern *P) override {
return std::make_pair(walkToRangePre(P->getSourceRange()), P);
}
virtual bool walkToTypeReprPre(TypeRepr *T) override {
return walkToRangePre(T->getSourceRange());
}
/// Returns true if the walker should traverse an AST node with
/// source range Range.
bool walkToRangePre(SourceRange Range) {
// When walking down the tree, we traverse until we have found a node
// inside the target range. Once we have found such a node, there is no
// need to traverse any deeper.
if (FoundTarget)
return false;
// If we haven't found our target yet and the node we are pre-visiting
// doesn't have a valid range, we still have to traverse it because its
// subtrees may have valid ranges.
if (Range.isInvalid())
return true;
// We have found our target if the range of the node we are visiting
// is contained in the range we are looking for.
FoundTarget = SM.rangeContains(TargetRange, Range);
if (FoundTarget)
return false;
// Search the subtree if the target range is inside its range.
return SM.rangeContains(Range, TargetRange);
}
virtual Expr *walkToExprPost(Expr *E) override {
if (walkToNodePost(E)) {
return E;
}
return nullptr;
}
virtual Stmt *walkToStmtPost(Stmt *S) override {
if (walkToNodePost(S)) {
return S;
}
return nullptr;
}
virtual bool walkToDeclPost(Decl *D) override {
return walkToNodePost(D);
}
/// Once we have found the target node, look for the innermost ancestor
/// matching our criteria on the way back up the spine of the tree.
bool walkToNodePost(ASTNode Node) {
if (!InnermostMatchingNode.hasValue() && Predicate(Node, Parent)) {
assert(Node.getSourceRange().isInvalid() ||
SM.rangeContains(Node.getSourceRange(), TargetRange));
InnermostMatchingNode = Node;
return false;
}
return true;
}
};
/// Starting from SearchRoot, finds the innermost node containing ChildRange
/// for which Predicate returns true. Returns None if no such root is found.
static Optional<ASTNode> findInnermostAncestor(
SourceRange ChildRange, const SourceManager &SM, ASTNode SearchRoot,
const InnermostAncestorFinder::MatchPredicate &Predicate) {
InnermostAncestorFinder Finder(ChildRange, SM, SearchRoot, Predicate);
return Finder.getInnermostMatchingNode();
}
/// Given a reference range and a declaration context containing the range,
/// attempt to find a declaration containing the reference. This may not
/// be the innermost declaration containing the range.
/// Returns null if no such declaration can be found.
static const Decl *findContainingDeclaration(SourceRange ReferenceRange,
const DeclContext *ReferenceDC,
const SourceManager &SM) {
if (const Decl *D = ReferenceDC->getInnermostDeclarationDeclContext())
return D;
// We couldn't find a suitable node by climbing the DeclContext
// hierarchy, so fall back to looking for a top-level declaration
// that contains the reference range. We will hit this case for
// top-level elements that do not themselves introduce DeclContexts,
// such as extensions and global variables. If we don't have a reference
// range, there is nothing we can do, so return null.
if (ReferenceRange.isInvalid())
return nullptr;
SourceFile *SF = ReferenceDC->getParentSourceFile();
if (!SF)
return nullptr;
for (Decl *D : SF->Decls) {
if (SM.rangeContains(D->getSourceRange(), ReferenceRange)) {
return D;
}
}
return nullptr;
}
/// Given a declaration that allows availability attributes in the abstract
/// syntax tree, return the declaration upon which the declaration would
/// appear in concrete syntax. This function is necessary because for semantic
/// analysis, the parser attaches attributes to declarations other
/// than those on which they, concretely, appear. For these declarations (enum
/// cases and variable declarations) a Fix-It for an added availability
/// attribute should be suggested for the appropriate concrete location.
static const Decl *
concreteSyntaxDeclForAvailableAttribute(const Decl *AbstractSyntaxDecl) {
// This function needs to be kept in sync with its counterpart,
// abstractSyntaxDeclForAvailableAttribute().
// The source range for VarDecls does not include 'var ' (and, in any
// event, multiple variables can be introduced with a single 'var'),
// so suggest adding an attribute to the PatterningBindingDecl instead.
if (auto *VD = dyn_cast<VarDecl>(AbstractSyntaxDecl)) {
return VD->getParentPatternBinding();
}
// Similarly suggest applying the Fix-It to the parent enum case rather than
// the enum element.
if (auto *EE = dyn_cast<EnumElementDecl>(AbstractSyntaxDecl)) {
return EE->getParentCase();
}
return AbstractSyntaxDecl;
}
/// Given a declaration upon which an availability attribute would appear in
/// concrete syntax, return a declaration to which the parser
/// actually attaches the attribute in the abstract syntax tree. We use this
/// function to determine whether the concrete syntax already has an
/// availability attribute.
static const Decl *
abstractSyntaxDeclForAvailableAttribute(const Decl *ConcreteSyntaxDecl) {
// This function needs to be kept in sync with its counterpart,
// concreteSyntaxDeclForAvailableAttribute().
if (auto *PBD = dyn_cast<PatternBindingDecl>(ConcreteSyntaxDecl)) {
// Existing @available attributes in the AST are attached to VarDecls
// rather than PatternBindingDecls, so we return the first VarDecl for
// the pattern binding declaration.
// This is safe, even though there may be multiple VarDecls, because
// all parsed attribute that appear in the concrete syntax upon on the
// PatternBindingDecl are added to all of the VarDecls for the pattern
// binding.
ArrayRef<PatternBindingEntry> Entries = PBD->getPatternList();
if (Entries.size() > 0) {
VarDecl *VD = Entries.front().getPattern()->getSingleVar();
if (VD)
return VD;
}
} else if (auto *ECD = dyn_cast<EnumCaseDecl>(ConcreteSyntaxDecl)) {
// Similar to the PatternBindingDecl case above, we return the
// first EnumElementDecl.
ArrayRef<EnumElementDecl *> Elems = ECD->getElements();
if (Elems.size() > 0) {
return Elems.front();
}
}
return ConcreteSyntaxDecl;
}
/// Given a declaration, return a better related declaration for which
/// to suggest an @available fixit, or the original declaration
/// if no such related declaration exists.
static const Decl *relatedDeclForAvailabilityFixit(const Decl *D) {
if (auto *FD = dyn_cast<FuncDecl>(D)) {
// Suggest @available Fix-Its on property rather than individual
// accessors.
if (FD->isAccessor()) {
D = FD->getAccessorStorageDecl();
}
}
return abstractSyntaxDeclForAvailableAttribute(D);
}
/// Walk the DeclContext hierarchy starting from D to find a declaration
/// at the member level (i.e., declared in a type context) on which to provide
/// an @available() Fix-It.
static const Decl *ancestorMemberLevelDeclForAvailabilityFixit(const Decl *D) {
while (D) {
D = relatedDeclForAvailabilityFixit(D);
if (D->getDeclContext()->isTypeContext() &&
DeclAttribute::canAttributeAppearOnDecl(DeclAttrKind::DAK_Available,
D)) {
break;
}
D = cast_or_null<AbstractFunctionDecl>(
D->getDeclContext()->getInnermostMethodContext());
}
return D;
}
/// Returns true if the declaration is at the type level (either a nominal
/// type, an extension, or a global function) and can support an @available
/// attribute.
static bool isTypeLevelDeclForAvailabilityFixit(const Decl *D) {
if (!DeclAttribute::canAttributeAppearOnDecl(DeclAttrKind::DAK_Available,
D)) {
return false;
}
if (isa<ExtensionDecl>(D) || isa<NominalTypeDecl>(D)) {
return true;
}
bool IsModuleScopeContext = D->getDeclContext()->isModuleScopeContext();
// We consider global functions to be "type level"
if (isa<FuncDecl>(D)) {
return IsModuleScopeContext;
}
if (auto *VD = dyn_cast<VarDecl>(D)) {
if (!IsModuleScopeContext)
return false;
if (PatternBindingDecl *PBD = VD->getParentPatternBinding()) {
return PBD->getDeclContext()->isModuleScopeContext();
}
}
return false;
}
/// Walk the DeclContext hierarchy starting from D to find a declaration
/// at a member level (i.e., declared in a type context) on which to provide an
/// @available() Fix-It.
static const Decl *ancestorTypeLevelDeclForAvailabilityFixit(const Decl *D) {
assert(D);
D = relatedDeclForAvailabilityFixit(D);
while (D && !isTypeLevelDeclForAvailabilityFixit(D)) {
D = D->getDeclContext()->getInnermostDeclarationDeclContext();
}
return D;
}
/// Given the range of a reference to an unavailable symbol and the
/// declaration context containing the reference, make a best effort find up to
/// three locations for potential fixits.
///
/// \param FoundVersionCheckNode Returns a node that can be wrapped in a
/// if #available(...) { ... } version check to fix the unavailable reference,
/// or None if such a node cannot be found.
///
/// \param FoundMemberLevelDecl Returns member-level declaration (i.e., the
/// child of a type DeclContext) for which an @available attribute would
/// fix the unavailable reference.
///
/// \param FoundTypeLevelDecl returns a type-level declaration (a
/// a nominal type, an extension, or a global function) for which an
/// @available attribute would fix the unavailable reference.
static void findAvailabilityFixItNodes(SourceRange ReferenceRange,
const DeclContext *ReferenceDC,
const SourceManager &SM,
Optional<ASTNode> &FoundVersionCheckNode,
const Decl *&FoundMemberLevelDecl,
const Decl *&FoundTypeLevelDecl) {
FoundVersionCheckNode = None;
FoundMemberLevelDecl = nullptr;
FoundTypeLevelDecl = nullptr;
// Limit tree to search based on the DeclContext of the reference.
const Decl *DeclarationToSearch =
findContainingDeclaration(ReferenceRange, ReferenceDC, SM);
if (!DeclarationToSearch)
return;
// Const-cast to inject into ASTNode. This search will not modify
// the declaration.
ASTNode SearchRoot = const_cast<Decl *>(DeclarationToSearch);
// The node to wrap in if #available(...) { ... } is the innermost node in
// SearchRoot that (1) can be guarded with an if statement and (2)
// contains the ReferenceRange.
// We make no guarantee that the Fix-It, when applied, will result in
// semantically valid code -- but, at a minimum, it should parse. So,
// for example, we may suggest wrapping a variable declaration in a guard,
// which would not be valid if the variable is later used. The goal
// is discoverability of #os() (via the diagnostic and Fix-It) rather than
// magically fixing the code in all cases.
InnermostAncestorFinder::MatchPredicate IsGuardable =
[](ASTNode Node, ASTWalker::ParentTy Parent) {
if (Expr *ParentExpr = Parent.getAsExpr()) {
auto *ParentClosure = dyn_cast<ClosureExpr>(ParentExpr);
if (!ParentClosure || !ParentClosure->hasSingleExpressionBody()) {
return false;
}
} else if (auto *ParentStmt = Parent.getAsStmt()) {
if (!isa<BraceStmt>(ParentStmt)) {
return false;
}
} else {
return false;
}
return true;
};
FoundVersionCheckNode =
findInnermostAncestor(ReferenceRange, SM, SearchRoot, IsGuardable);
// Find some Decl that contains the reference range. We use this declaration
// as a starting place to climb the DeclContext hierarchy to find
// places to suggest adding @available() annotations.
InnermostAncestorFinder::MatchPredicate IsDeclaration = [](
ASTNode Node, ASTWalker::ParentTy Parent) { return Node.is<Decl *>(); };
Optional<ASTNode> FoundDeclarationNode =
findInnermostAncestor(ReferenceRange, SM, SearchRoot, IsDeclaration);
const Decl *ContainingDecl = nullptr;
if (FoundDeclarationNode.hasValue()) {
ContainingDecl = FoundDeclarationNode.getValue().get<Decl *>();
}
if (!ContainingDecl) {
ContainingDecl = ReferenceDC->getInnermostMethodContext();
}
// Try to find declarations on which @available attributes can be added.
// The heuristics for finding these declarations are biased towards deeper
// nodes in the AST to limit the scope of suggested availability regions
// and provide a better IDE experience (it can get jumpy if Fix-It locations
// are far away from the error needing the Fix-It).
if (ContainingDecl) {
FoundMemberLevelDecl =
ancestorMemberLevelDeclForAvailabilityFixit(ContainingDecl);
FoundTypeLevelDecl =
ancestorTypeLevelDeclForAvailabilityFixit(ContainingDecl);
}
}
/// Emit a diagnostic note and Fix-It to add an @available attribute
/// on the given declaration for the given version range.
static void fixAvailabilityForDecl(SourceRange ReferenceRange, const Decl *D,
const VersionRange &RequiredRange,
TypeChecker &TC) {
assert(D);
// Don't suggest adding an @available() to a declaration where we would
// emit a diagnostic saying it is not allowed.
if (TC.diagnosticIfDeclCannotBePotentiallyUnavailable(D).hasValue())
return;
if (getActiveAvailableAttribute(D, TC.Context)) {
// For QoI, in future should emit a fixit to update the existing attribute.
return;
}
// For some declarations (variables, enum elements), the location in concrete
// syntax to suggest the Fix-It may differ from the declaration to which
// we attach availability attributes in the abstract syntax tree during
// parsing.
const Decl *ConcDecl = concreteSyntaxDeclForAvailableAttribute(D);
DescriptiveDeclKind KindForDiagnostic = ConcDecl->getDescriptiveKind();
SourceLoc InsertLoc;
// To avoid exposing the pattern binding declaration to the user, get the
// descriptive kind from one of the VarDecls. We get the Fix-It location
// from the PatternBindingDecl unless the VarDecl has attributes,
// in which case we get the start location of the VarDecl attributes.
DeclAttributes AttrsForLoc;
if (KindForDiagnostic == DescriptiveDeclKind::PatternBinding) {
KindForDiagnostic = D->getDescriptiveKind();
AttrsForLoc = D->getAttrs();
} else {
InsertLoc = ConcDecl->getAttrs().getStartLoc(/*forModifiers=*/false);
}
InsertLoc = D->getAttrs().getStartLoc(/*forModifiers=*/false);
if (InsertLoc.isInvalid()) {
InsertLoc = ConcDecl->getStartLoc();
}
if (InsertLoc.isInvalid())
return;
StringRef OriginalIndent =
Lexer::getIndentationForLine(TC.Context.SourceMgr, InsertLoc);
std::string AttrText;
{
llvm::raw_string_ostream Out(AttrText);
PlatformKind Target = targetPlatform(TC.getLangOpts());
Out << "@available(" << platformString(Target) << " "
<< RequiredRange.getLowerEndpoint().getAsString() << ", *)\n"
<< OriginalIndent;
}
TC.diagnose(ReferenceRange.Start, diag::availability_add_attribute,
KindForDiagnostic)
.fixItInsert(InsertLoc, AttrText);
}
/// Emit a diagnostic note and Fix-It to add an if #available(...) { } guard
/// that checks for the given version range around the given node.
static void fixAvailabilityByAddingVersionCheck(
ASTNode NodeToWrap, const VersionRange &RequiredRange,
SourceRange ReferenceRange, TypeChecker &TC) {
SourceRange RangeToWrap = NodeToWrap.getSourceRange();
if (RangeToWrap.isInvalid())
return;
SourceLoc ReplaceLocStart = RangeToWrap.Start;
StringRef OriginalIndent =
Lexer::getIndentationForLine(TC.Context.SourceMgr, ReplaceLocStart);
std::string IfText;
{
llvm::raw_string_ostream Out(IfText);
SourceLoc ReplaceLocEnd =
Lexer::getLocForEndOfToken(TC.Context.SourceMgr, RangeToWrap.End);
std::string GuardedText =
TC.Context.SourceMgr.extractText(CharSourceRange(TC.Context.SourceMgr,
ReplaceLocStart,
ReplaceLocEnd)).str();
// We'll indent with 4 spaces
std::string ExtraIndent = " ";
std::string NewLine = "\n";
// Indent the body of the Fix-It if. Because the body may be a compound
// statement, we may have to indent multiple lines.
size_t StartAt = 0;
while ((StartAt = GuardedText.find(NewLine, StartAt)) !=
std::string::npos) {
GuardedText.replace(StartAt, NewLine.length(), NewLine + ExtraIndent);
StartAt += NewLine.length();
}
PlatformKind Target = targetPlatform(TC.getLangOpts());
Out << "if #available(" << platformString(Target)
<< " " << RequiredRange.getLowerEndpoint().getAsString()
<< ", *) {\n";
Out << OriginalIndent << ExtraIndent << GuardedText << "\n";
// We emit an empty fallback case with a comment to encourage the developer
// to think explicitly about whether fallback on earlier versions is needed.
Out << OriginalIndent << "} else {\n";
Out << OriginalIndent << ExtraIndent << "// Fallback on earlier versions\n";
Out << OriginalIndent << "}";
}
TC.diagnose(ReferenceRange.Start, diag::availability_guard_with_version_check)
.fixItReplace(RangeToWrap, IfText);
}
/// Emit suggested Fix-Its for a reference with to an unavailable symbol
/// requiting the given OS version range.
static void fixAvailability(SourceRange ReferenceRange,
const DeclContext *ReferenceDC,
const VersionRange &RequiredRange,
TypeChecker &TC) {
if (ReferenceRange.isInvalid())
return;
Optional<ASTNode> NodeToWrapInVersionCheck;
const Decl *FoundMemberDecl = nullptr;
const Decl *FoundTypeLevelDecl = nullptr;
findAvailabilityFixItNodes(ReferenceRange, ReferenceDC, TC.Context.SourceMgr,
NodeToWrapInVersionCheck, FoundMemberDecl,
FoundTypeLevelDecl);
// Suggest wrapping in if #available(...) { ... } if possible.
if (NodeToWrapInVersionCheck.hasValue()) {
fixAvailabilityByAddingVersionCheck(NodeToWrapInVersionCheck.getValue(),
RequiredRange, ReferenceRange, TC);
}
// Suggest adding availability attributes.
if (FoundMemberDecl) {
fixAvailabilityForDecl(ReferenceRange, FoundMemberDecl, RequiredRange, TC);
}
if (FoundTypeLevelDecl) {
fixAvailabilityForDecl(ReferenceRange, FoundTypeLevelDecl, RequiredRange,
TC);
}
}
void TypeChecker::diagnosePotentialUnavailability(
const Decl *D, DeclName Name, SourceRange ReferenceRange,
const DeclContext *ReferenceDC, const UnavailabilityReason &Reason) {
// We only emit diagnostics for API unavailability, not for explicitly
// weak-linked symbols.
if (Reason.getReasonKind() !=
UnavailabilityReason::Kind::RequiresOSVersionRange) {
return;
}
diagnose(ReferenceRange.Start, diag::availability_decl_only_version_newer,
Name, prettyPlatformString(targetPlatform(Context.LangOpts)),
Reason.getRequiredOSVersionRange().getLowerEndpoint());
fixAvailability(ReferenceRange, ReferenceDC,
Reason.getRequiredOSVersionRange(), *this);
}
void TypeChecker::diagnosePotentialAccessorUnavailability(
FuncDecl *Accessor, SourceRange ReferenceRange,
const DeclContext *ReferenceDC, const UnavailabilityReason &Reason,
bool ForInout) {
assert(Accessor->isGetterOrSetter());
AbstractStorageDecl *ASD = Accessor->getAccessorStorageDecl();
DeclName Name = ASD->getFullName();
auto &diag = ForInout ? diag::availability_inout_accessor_only_version_newer
: diag::availability_accessor_only_version_newer;
diagnose(ReferenceRange.Start, diag,
static_cast<unsigned>(Accessor->getAccessorKind()), Name,
prettyPlatformString(targetPlatform(Context.LangOpts)),
Reason.getRequiredOSVersionRange().getLowerEndpoint());
fixAvailability(ReferenceRange, ReferenceDC,
Reason.getRequiredOSVersionRange(), *this);
}
const AvailableAttr *TypeChecker::getDeprecated(const Decl *D) {
if (auto *Attr = D->getAttrs().getDeprecated(D->getASTContext()))
return Attr;
// Treat extensions methods as deprecated if their extension
// is deprecated.
DeclContext *DC = D->getDeclContext();
if (auto *ED = dyn_cast<ExtensionDecl>(DC)) {
return getDeprecated(ED);
}
return nullptr;
}
/// Returns true if some declaration lexically enclosing the reference
/// matches the passed in predicate and false otherwise.
static bool someEnclosingDeclMatches(SourceRange ReferenceRange,
const DeclContext *ReferenceDC,
TypeChecker &TC,
std::function<bool(const Decl *)> Pred) {
ASTContext &Ctx = TC.Context;
// Climb the DeclContext hierarchy to see if any of the containing
// declarations matches the predicate.
const DeclContext *DC = ReferenceDC;
do {
auto *D = DC->getInnermostDeclarationDeclContext();
if (!D)
break;
if (Pred(D)) {
return true;
}
// If we are in an accessor, check to see if the associated
// property is matches the predicate.
auto *FD = dyn_cast<FuncDecl>(D);
if (FD && FD->isAccessor() && Pred(FD->getAccessorStorageDecl())) {
return true;
}
DC = DC->getParent();
} while (DC);
// Search the AST starting from our innermost declaration context to see if
// if the reference is inside a property declaration but not inside an
// accessor (this can happen for the TypeRepr for the declared type of a
// property, for example).
// We can't rely on the DeclContext hierarchy climb above because properties
// do not introduce a new DeclContext. This search is potentially slow, so we
// do it last and only if the reference declaration context is a
// type or global context.
if (!ReferenceDC->isTypeContext() && !ReferenceDC->isModuleScopeContext())
return false;
// Don't search for a containing declaration if we don't have a source range.
if (ReferenceRange.isInvalid())
return false;
const Decl *DeclToSearch =
findContainingDeclaration(ReferenceRange, ReferenceDC, Ctx.SourceMgr);
// We may not be able to find a declaration to search if the ReferenceRange
// is invalid (i.e., we are in synthesized code).
if (!DeclToSearch)
return false;
InnermostAncestorFinder::MatchPredicate IsDeclaration =
[](ASTNode Node, ASTWalker::ParentTy Parent) {
return Node.is<Decl *>();
};
Optional<ASTNode> FoundDeclarationNode =
findInnermostAncestor(ReferenceRange, Ctx.SourceMgr,
const_cast<Decl *>(DeclToSearch), IsDeclaration);
if (FoundDeclarationNode.hasValue()) {
const Decl *D = FoundDeclarationNode.getValue().get<Decl *>();
D = abstractSyntaxDeclForAvailableAttribute(D);
if (Pred(D)) {
return true;
}
}
return false;
}
bool TypeChecker::isInsideImplicitFunction(SourceRange ReferenceRange,
const DeclContext *DC) {
auto IsInsideImplicitFunc = [](const Decl *D) {
auto *AFD = dyn_cast<AbstractFunctionDecl>(D);
return AFD && AFD->isImplicit();
};
return someEnclosingDeclMatches(ReferenceRange, DC, *this,
IsInsideImplicitFunc);
}
bool TypeChecker::isInsideUnavailableDeclaration(
SourceRange ReferenceRange, const DeclContext *ReferenceDC) {
auto IsUnavailable = [](const Decl *D) {
return D->getAttrs().getUnavailable(D->getASTContext());
};
return someEnclosingDeclMatches(ReferenceRange, ReferenceDC, *this,
IsUnavailable);
}
bool TypeChecker::isInsideDeprecatedDeclaration(SourceRange ReferenceRange,
const DeclContext *ReferenceDC){
std::function<bool(const Decl *)> IsDeprecated = [](const Decl *D) {
return D->getAttrs().getDeprecated(D->getASTContext());
};
return someEnclosingDeclMatches(ReferenceRange, ReferenceDC, *this,
IsDeprecated);
}
// checkForForbiddenPrefix is for testing purposes.
void TypeChecker::checkForForbiddenPrefix(const Decl *D) {
if (!hasEnabledForbiddenTypecheckPrefix())
return;
if (auto VD = dyn_cast<ValueDecl>(D)) {
checkForForbiddenPrefix(VD->getNameStr());
}
}
void TypeChecker::checkForForbiddenPrefix(const UnresolvedDeclRefExpr *E) {
if (!hasEnabledForbiddenTypecheckPrefix())
return;
checkForForbiddenPrefix(E->getName().getBaseName());
}
void TypeChecker::checkForForbiddenPrefix(Identifier Ident) {
if (!hasEnabledForbiddenTypecheckPrefix())
return;
checkForForbiddenPrefix(Ident.empty() ? StringRef() : Ident.str());
}
void TypeChecker::checkForForbiddenPrefix(StringRef Name) {
if (!hasEnabledForbiddenTypecheckPrefix())
return;
if (Name.empty())
return;
if (Name.startswith(Context.LangOpts.DebugForbidTypecheckPrefix)) {
std::string Msg = "forbidden typecheck occurred: ";
Msg += Name;
llvm::report_fatal_error(Msg);
}
}