blob: 909e6f8cf58a521144981bfc830eabb2620835dd [file] [log] [blame]
//===--- TypeCheckDeclObjC.cpp - Type Checking for ObjC Declarations ------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements semantic analysis for Objective-C-specific
// aspects of declarations.
//
//===----------------------------------------------------------------------===//
#include "TypeCheckObjC.h"
#include "TypeChecker.h"
#include "TypeCheckProtocol.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/Decl.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/ForeignErrorConvention.h"
#include "swift/AST/ImportCache.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/Basic/StringExtras.h"
using namespace swift;
#pragma mark Determine whether an entity is representable in Objective-C.
bool swift::shouldDiagnoseObjCReason(ObjCReason reason, ASTContext &ctx) {
switch(reason) {
case ObjCReason::ExplicitlyCDecl:
case ObjCReason::ExplicitlyDynamic:
case ObjCReason::ExplicitlyObjC:
case ObjCReason::ExplicitlyIBOutlet:
case ObjCReason::ExplicitlyIBAction:
case ObjCReason::ExplicitlyIBSegueAction:
case ObjCReason::ExplicitlyNSManaged:
case ObjCReason::MemberOfObjCProtocol:
case ObjCReason::OverridesObjC:
case ObjCReason::WitnessToObjC:
case ObjCReason::ImplicitlyObjC:
case ObjCReason::MemberOfObjCExtension:
return true;
case ObjCReason::ExplicitlyIBInspectable:
case ObjCReason::ExplicitlyGKInspectable:
return !ctx.LangOpts.EnableSwift3ObjCInference;
case ObjCReason::MemberOfObjCSubclass:
case ObjCReason::MemberOfObjCMembersClass:
case ObjCReason::ElementOfObjCEnum:
case ObjCReason::Accessor:
return false;
}
llvm_unreachable("unhandled reason");
}
unsigned swift::getObjCDiagnosticAttrKind(ObjCReason reason) {
switch (reason) {
case ObjCReason::ExplicitlyCDecl:
case ObjCReason::ExplicitlyDynamic:
case ObjCReason::ExplicitlyObjC:
case ObjCReason::ExplicitlyIBOutlet:
case ObjCReason::ExplicitlyIBAction:
case ObjCReason::ExplicitlyIBSegueAction:
case ObjCReason::ExplicitlyNSManaged:
case ObjCReason::MemberOfObjCProtocol:
case ObjCReason::OverridesObjC:
case ObjCReason::WitnessToObjC:
case ObjCReason::ImplicitlyObjC:
case ObjCReason::ExplicitlyIBInspectable:
case ObjCReason::ExplicitlyGKInspectable:
case ObjCReason::MemberOfObjCExtension:
return static_cast<unsigned>(reason);
case ObjCReason::MemberOfObjCSubclass:
case ObjCReason::MemberOfObjCMembersClass:
case ObjCReason::ElementOfObjCEnum:
case ObjCReason::Accessor:
llvm_unreachable("should not diagnose this @objc reason");
}
llvm_unreachable("unhandled reason");
}
/// Emit an additional diagnostic describing why we are applying @objc to the
/// decl, if this is not obvious from the decl itself.
static void describeObjCReason(const ValueDecl *VD, ObjCReason Reason) {
if (Reason == ObjCReason::MemberOfObjCProtocol) {
VD->diagnose(diag::objc_inferring_on_objc_protocol_member);
} else if (Reason == ObjCReason::OverridesObjC) {
unsigned kind = isa<VarDecl>(VD) ? 0
: isa<SubscriptDecl>(VD) ? 1
: isa<ConstructorDecl>(VD) ? 2
: 3;
auto overridden = VD->getOverriddenDecl();
overridden->diagnose(diag::objc_overriding_objc_decl,
kind, VD->getOverriddenDecl()->getFullName());
} else if (Reason == ObjCReason::WitnessToObjC) {
auto requirement = Reason.getObjCRequirement();
requirement->diagnose(diag::objc_witness_objc_requirement,
VD->getDescriptiveKind(), requirement->getFullName(),
cast<ProtocolDecl>(requirement->getDeclContext())
->getFullName());
}
}
static void diagnoseTypeNotRepresentableInObjC(const DeclContext *DC,
Type T,
SourceRange TypeRange) {
auto &diags = DC->getASTContext().Diags;
// Special diagnostic for tuples.
if (T->is<TupleType>()) {
if (T->isVoid())
diags.diagnose(TypeRange.Start, diag::not_objc_empty_tuple)
.highlight(TypeRange);
else
diags.diagnose(TypeRange.Start, diag::not_objc_tuple)
.highlight(TypeRange);
return;
}
// Special diagnostic for classes.
if (auto *CD = T->getClassOrBoundGenericClass()) {
if (!CD->isObjC())
diags.diagnose(TypeRange.Start, diag::not_objc_swift_class)
.highlight(TypeRange);
return;
}
// Special diagnostic for structs.
if (T->is<StructType>()) {
diags.diagnose(TypeRange.Start, diag::not_objc_swift_struct)
.highlight(TypeRange);
return;
}
// Special diagnostic for enums.
if (T->is<EnumType>()) {
diags.diagnose(TypeRange.Start, diag::not_objc_swift_enum)
.highlight(TypeRange);
return;
}
// Special diagnostic for protocols and protocol compositions.
if (T->isExistentialType()) {
if (T->isAny()) {
// Any is not @objc.
diags.diagnose(TypeRange.Start,
diag::not_objc_empty_protocol_composition);
return;
}
auto layout = T->getExistentialLayout();
// See if the superclass is not @objc.
if (auto superclass = layout.explicitSuperclass) {
if (!superclass->getClassOrBoundGenericClass()->isObjC()) {
diags.diagnose(TypeRange.Start, diag::not_objc_class_constraint,
superclass);
return;
}
}
// Find a protocol that is not @objc.
bool sawErrorProtocol = false;
for (auto P : layout.getProtocols()) {
auto *PD = P->getDecl();
if (PD->isSpecificProtocol(KnownProtocolKind::Error)) {
sawErrorProtocol = true;
break;
}
if (!PD->isObjC()) {
diags.diagnose(TypeRange.Start, diag::not_objc_protocol,
PD->getDeclaredType());
return;
}
}
if (sawErrorProtocol) {
diags.diagnose(TypeRange.Start,
diag::not_objc_error_protocol_composition);
return;
}
return;
}
if (T->is<ArchetypeType>() || T->isTypeParameter()) {
diags.diagnose(TypeRange.Start, diag::not_objc_generic_type_param)
.highlight(TypeRange);
return;
}
if (auto fnTy = T->getAs<FunctionType>()) {
if (fnTy->getExtInfo().throws() ) {
diags.diagnose(TypeRange.Start, diag::not_objc_function_type_throwing)
.highlight(TypeRange);
return;
}
diags.diagnose(TypeRange.Start, diag::not_objc_function_type_param)
.highlight(TypeRange);
return;
}
}
static void diagnoseFunctionParamNotRepresentable(
const AbstractFunctionDecl *AFD, unsigned NumParams,
unsigned ParamIndex, const ParamDecl *P, ObjCReason Reason) {
if (!shouldDiagnoseObjCReason(Reason, AFD->getASTContext()))
return;
if (NumParams == 1) {
AFD->diagnose(diag::objc_invalid_on_func_single_param_type,
getObjCDiagnosticAttrKind(Reason));
} else {
AFD->diagnose(diag::objc_invalid_on_func_param_type,
ParamIndex + 1, getObjCDiagnosticAttrKind(Reason));
}
if (P->hasType()) {
Type ParamTy = P->getType();
SourceRange SR;
if (auto typeRepr = P->getTypeLoc().getTypeRepr())
SR = typeRepr->getSourceRange();
diagnoseTypeNotRepresentableInObjC(AFD, ParamTy, SR);
}
describeObjCReason(AFD, Reason);
}
static bool isParamListRepresentableInObjC(const AbstractFunctionDecl *AFD,
const ParameterList *PL,
ObjCReason Reason) {
// If you change this function, you must add or modify a test in PrintAsObjC.
ASTContext &ctx = AFD->getASTContext();
auto &diags = ctx.Diags;
bool Diagnose = shouldDiagnoseObjCReason(Reason, ctx);
bool IsObjC = true;
unsigned NumParams = PL->size();
for (unsigned ParamIndex = 0; ParamIndex != NumParams; ParamIndex++) {
auto param = PL->get(ParamIndex);
// Swift Varargs are not representable in Objective-C.
if (param->isVariadic()) {
if (Diagnose && shouldDiagnoseObjCReason(Reason, ctx)) {
diags.diagnose(param->getStartLoc(), diag::objc_invalid_on_func_variadic,
getObjCDiagnosticAttrKind(Reason))
.highlight(param->getSourceRange());
describeObjCReason(AFD, Reason);
}
return false;
}
// Swift inout parameters are not representable in Objective-C.
if (param->isInOut()) {
if (Diagnose && shouldDiagnoseObjCReason(Reason, ctx)) {
diags.diagnose(param->getStartLoc(), diag::objc_invalid_on_func_inout,
getObjCDiagnosticAttrKind(Reason))
.highlight(param->getSourceRange());
describeObjCReason(AFD, Reason);
}
return false;
}
if (param->getType()->hasError())
return false;
if (param->getType()->isRepresentableIn(
ForeignLanguage::ObjectiveC,
const_cast<AbstractFunctionDecl *>(AFD)))
continue;
// Permit '()' when this method overrides a method with a
// foreign error convention that replaces NSErrorPointer with ()
// and this is the replaced parameter.
AbstractFunctionDecl *overridden;
if (param->getType()->isVoid() && AFD->hasThrows() &&
(overridden = AFD->getOverriddenDecl())) {
auto foreignError = overridden->getForeignErrorConvention();
if (foreignError &&
foreignError->isErrorParameterReplacedWithVoid() &&
foreignError->getErrorParameterIndex() == ParamIndex) {
continue;
}
}
IsObjC = false;
if (!Diagnose) {
// Save some work and return as soon as possible if we are not
// producing diagnostics.
return IsObjC;
}
diagnoseFunctionParamNotRepresentable(AFD, NumParams, ParamIndex,
param, Reason);
}
return IsObjC;
}
/// Check whether the given declaration contains its own generic parameters,
/// and therefore is not representable in Objective-C.
static bool checkObjCWithGenericParams(const AbstractFunctionDecl *AFD,
ObjCReason Reason) {
bool Diagnose = shouldDiagnoseObjCReason(Reason, AFD->getASTContext());
if (AFD->getGenericParams()) {
// Diagnose this problem, if asked to.
if (Diagnose) {
AFD->diagnose(diag::objc_invalid_with_generic_params,
getObjCDiagnosticAttrKind(Reason));
describeObjCReason(AFD, Reason);
}
return true;
}
return false;
}
/// CF types cannot have @objc methods, because they don't have real class
/// objects.
static bool checkObjCInForeignClassContext(const ValueDecl *VD,
ObjCReason Reason) {
bool Diagnose = shouldDiagnoseObjCReason(Reason, VD->getASTContext());
auto type = VD->getDeclContext()->getDeclaredInterfaceType();
if (!type)
return false;
auto clas = type->getClassOrBoundGenericClass();
if (!clas)
return false;
switch (clas->getForeignClassKind()) {
case ClassDecl::ForeignKind::Normal:
return false;
case ClassDecl::ForeignKind::CFType:
if (Diagnose) {
VD->diagnose(diag::objc_invalid_on_foreign_class,
getObjCDiagnosticAttrKind(Reason));
describeObjCReason(VD, Reason);
}
break;
case ClassDecl::ForeignKind::RuntimeOnly:
if (Diagnose) {
VD->diagnose(diag::objc_in_objc_runtime_visible,
VD->getDescriptiveKind(), getObjCDiagnosticAttrKind(Reason),
clas->getName());
describeObjCReason(VD, Reason);
}
break;
}
return true;
}
static VersionRange getMinOSVersionForClassStubs(const llvm::Triple &target) {
if (target.isMacOSX())
return VersionRange::allGTE(llvm::VersionTuple(10, 15, 0));
if (target.isiOS()) // also returns true on tvOS
return VersionRange::allGTE(llvm::VersionTuple(13, 0, 0));
if (target.isWatchOS())
return VersionRange::allGTE(llvm::VersionTuple(6, 0, 0));
return VersionRange::all();
}
static bool checkObjCClassStubAvailability(ASTContext &ctx, const Decl *decl) {
auto minRange = getMinOSVersionForClassStubs(ctx.LangOpts.Target);
auto targetRange = AvailabilityContext::forDeploymentTarget(ctx);
if (targetRange.getOSVersion().isContainedIn(minRange))
return true;
auto declRange = AvailabilityInference::availableRange(decl, ctx);
return declRange.getOSVersion().isContainedIn(minRange);
}
static const ClassDecl *getResilientAncestor(ModuleDecl *mod,
const ClassDecl *classDecl) {
auto *superclassDecl = classDecl;
for (;;) {
if (superclassDecl->hasResilientMetadata(mod,
ResilienceExpansion::Maximal))
return superclassDecl;
superclassDecl = superclassDecl->getSuperclassDecl();
}
}
/// Check whether the given declaration occurs within a constrained
/// extension, or an extension of a generic class, or an
/// extension of an Objective-C runtime visible class, and
/// therefore is not representable in Objective-C.
static bool checkObjCInExtensionContext(const ValueDecl *value,
bool diagnose) {
auto DC = value->getDeclContext();
if (auto ED = dyn_cast<ExtensionDecl>(DC)) {
if (ED->getTrailingWhereClause()) {
if (diagnose) {
value->diagnose(diag::objc_in_extension_context);
}
return true;
}
if (auto classDecl = ED->getSelfClassDecl()) {
auto *mod = value->getModuleContext();
auto &ctx = mod->getASTContext();
if (!checkObjCClassStubAvailability(ctx, value)) {
if (classDecl->checkAncestry().contains(
AncestryFlags::ResilientOther) ||
classDecl->hasResilientMetadata(mod,
ResilienceExpansion::Maximal)) {
if (diagnose) {
auto &target = ctx.LangOpts.Target;
auto platform = prettyPlatformString(targetPlatform(ctx.LangOpts));
auto range = getMinOSVersionForClassStubs(target);
auto *ancestor = getResilientAncestor(mod, classDecl);
value->diagnose(diag::objc_in_resilient_extension,
value->getDescriptiveKind(),
ancestor->getName(),
platform,
range.getLowerEndpoint());
}
return true;
}
}
if (classDecl->isGenericContext()) {
if (!classDecl->usesObjCGenericsModel()) {
if (diagnose) {
value->diagnose(diag::objc_in_generic_extension,
classDecl->isGeneric());
}
return true;
}
}
}
}
return false;
}
/// Determines whether the given type is a valid Objective-C class type that
/// can be returned as a result of a throwing function.
static bool isValidObjectiveCErrorResultType(DeclContext *dc, Type type) {
switch (type->getForeignRepresentableIn(ForeignLanguage::ObjectiveC, dc)
.first) {
case ForeignRepresentableKind::Trivial:
case ForeignRepresentableKind::None:
// Special case: If the type is Unmanaged<T>, then return true, because
// Unmanaged<T> can be represented in Objective-C (if T can be).
if (auto BGT = type->getAs<BoundGenericType>()) {
if (BGT->getDecl() == dc->getASTContext().getUnmanagedDecl()) {
return true;
}
}
return false;
case ForeignRepresentableKind::Object:
case ForeignRepresentableKind::Bridged:
case ForeignRepresentableKind::BridgedError:
case ForeignRepresentableKind::StaticBridged:
return true;
}
llvm_unreachable("Unhandled ForeignRepresentableKind in switch.");
}
bool swift::isRepresentableInObjC(
const AbstractFunctionDecl *AFD,
ObjCReason Reason,
Optional<ForeignErrorConvention> &errorConvention) {
// Clear out the error convention. It will be added later if needed.
errorConvention = None;
// If you change this function, you must add or modify a test in PrintAsObjC.
ASTContext &ctx = AFD->getASTContext();
// FIXME(InterfaceTypeRequest): Remove this.
(void)AFD->getInterfaceType();
bool Diagnose = shouldDiagnoseObjCReason(Reason, ctx);
if (checkObjCInForeignClassContext(AFD, Reason))
return false;
if (checkObjCWithGenericParams(AFD, Reason))
return false;
if (checkObjCInExtensionContext(AFD, Diagnose))
return false;
if (AFD->isOperator()) {
AFD->diagnose((isa<ProtocolDecl>(AFD->getDeclContext())
? diag::objc_operator_proto
: diag::objc_operator));
return false;
}
if (auto accessor = dyn_cast<AccessorDecl>(AFD)) {
// Accessors can only be @objc if the storage declaration is.
// Global computed properties may however @_cdecl their accessors.
auto storage = accessor->getStorage();
if (!storage->isObjC() && Reason != ObjCReason::ExplicitlyCDecl &&
Reason != ObjCReason::WitnessToObjC &&
Reason != ObjCReason::MemberOfObjCProtocol) {
if (Diagnose) {
auto error = accessor->isGetter()
? (isa<VarDecl>(storage)
? diag::objc_getter_for_nonobjc_property
: diag::objc_getter_for_nonobjc_subscript)
: (isa<VarDecl>(storage)
? diag::objc_setter_for_nonobjc_property
: diag::objc_setter_for_nonobjc_subscript);
accessor->diagnose(error);
describeObjCReason(accessor, Reason);
}
return false;
}
switch (accessor->getAccessorKind()) {
case AccessorKind::DidSet:
case AccessorKind::WillSet:
// willSet/didSet implementations are never exposed to objc, they are
// always directly dispatched from the synthesized setter.
if (Diagnose) {
accessor->diagnose(diag::objc_observing_accessor);
describeObjCReason(accessor, Reason);
}
return false;
case AccessorKind::Get:
case AccessorKind::Set:
return true;
case AccessorKind::Address:
case AccessorKind::MutableAddress:
if (Diagnose) {
accessor->diagnose(diag::objc_addressor);
describeObjCReason(accessor, Reason);
}
return false;
case AccessorKind::Read:
case AccessorKind::Modify:
if (Diagnose) {
accessor->diagnose(diag::objc_coroutine_accessor);
describeObjCReason(accessor, Reason);
}
return false;
}
llvm_unreachable("bad kind");
}
// As a special case, an initializer with a single, named parameter of type
// '()' is always representable in Objective-C. This allows us to cope with
// zero-parameter methods with selectors that are longer than "init". For
// example, this allows:
//
// \code
// class Foo {
// @objc init(malice: ()) { } // selector is "initWithMalice"
// }
// \endcode
bool isSpecialInit = false;
if (auto init = dyn_cast<ConstructorDecl>(AFD))
isSpecialInit = init->isObjCZeroParameterWithLongSelector();
if (!isSpecialInit &&
!isParamListRepresentableInObjC(AFD,
AFD->getParameters(),
Reason)) {
return false;
}
if (auto FD = dyn_cast<FuncDecl>(AFD)) {
Type ResultType = FD->mapTypeIntoContext(FD->getResultInterfaceType());
if (!ResultType->hasError() &&
!ResultType->isVoid() &&
!ResultType->isUninhabited() &&
!ResultType->isRepresentableIn(ForeignLanguage::ObjectiveC,
const_cast<FuncDecl *>(FD))) {
if (Diagnose) {
AFD->diagnose(diag::objc_invalid_on_func_result_type,
getObjCDiagnosticAttrKind(Reason));
SourceRange Range =
FD->getBodyResultTypeLoc().getTypeRepr()->getSourceRange();
diagnoseTypeNotRepresentableInObjC(FD, ResultType, Range);
describeObjCReason(FD, Reason);
}
return false;
}
}
// Throwing functions must map to a particular error convention.
if (AFD->hasThrows()) {
DeclContext *dc = const_cast<AbstractFunctionDecl *>(AFD);
SourceLoc throwsLoc;
Type resultType;
const ConstructorDecl *ctor = nullptr;
if (auto func = dyn_cast<FuncDecl>(AFD)) {
resultType = func->getResultInterfaceType();
throwsLoc = func->getThrowsLoc();
} else {
ctor = cast<ConstructorDecl>(AFD);
throwsLoc = ctor->getThrowsLoc();
}
ForeignErrorConvention::Kind kind;
CanType errorResultType;
Type optOptionalType;
if (ctor) {
// Initializers always use the nil result convention.
kind = ForeignErrorConvention::NilResult;
// Only non-failing initializers can throw.
if (ctor->isFailable()) {
if (Diagnose) {
AFD->diagnose(diag::objc_invalid_on_failing_init,
getObjCDiagnosticAttrKind(Reason))
.highlight(throwsLoc);
describeObjCReason(AFD, Reason);
}
return false;
}
} else if (resultType->isVoid()) {
// Functions that return nothing (void) can be throwing; they indicate
// failure with a 'false' result.
kind = ForeignErrorConvention::ZeroResult;
NominalTypeDecl *boolDecl = ctx.getObjCBoolDecl();
// On Linux, we might still run @objc tests even though there's
// no ObjectiveC Foundation, so use Swift.Bool instead of crapping
// out.
if (boolDecl == nullptr)
boolDecl = ctx.getBoolDecl();
if (boolDecl == nullptr) {
AFD->diagnose(diag::broken_bool);
return false;
}
errorResultType = boolDecl->getDeclaredType()->getCanonicalType();
} else if (!resultType->getOptionalObjectType() &&
isValidObjectiveCErrorResultType(dc, resultType)) {
// Functions that return a (non-optional) type bridged to Objective-C
// can be throwing; they indicate failure with a nil result.
kind = ForeignErrorConvention::NilResult;
} else if ((optOptionalType = resultType->getOptionalObjectType()) &&
isValidObjectiveCErrorResultType(dc, optOptionalType)) {
// Cannot return an optional bridged type, because 'nil' is reserved
// to indicate failure. Call this out in a separate diagnostic.
if (Diagnose) {
AFD->diagnose(diag::objc_invalid_on_throwing_optional_result,
getObjCDiagnosticAttrKind(Reason),
resultType)
.highlight(throwsLoc);
describeObjCReason(AFD, Reason);
}
return false;
} else {
// Other result types are not permitted.
if (Diagnose) {
AFD->diagnose(diag::objc_invalid_on_throwing_result,
getObjCDiagnosticAttrKind(Reason),
resultType)
.highlight(throwsLoc);
describeObjCReason(AFD, Reason);
}
return false;
}
// The error type is always 'AutoreleasingUnsafeMutablePointer<NSError?>?'.
auto nsError = ctx.getNSErrorDecl();
Type errorParameterType;
if (nsError) {
errorParameterType = nsError->getDeclaredInterfaceType();
errorParameterType = OptionalType::get(errorParameterType);
errorParameterType
= BoundGenericType::get(
ctx.getAutoreleasingUnsafeMutablePointerDecl(),
nullptr,
errorParameterType);
errorParameterType = OptionalType::get(errorParameterType);
}
// Determine the parameter index at which the error will go.
unsigned errorParameterIndex;
bool foundErrorParameterIndex = false;
// If there is an explicit @objc attribute with a name, look for
// the "error" selector piece.
if (auto objc = AFD->getAttrs().getAttribute<ObjCAttr>()) {
if (auto objcName = objc->getName()) {
auto selectorPieces = objcName->getSelectorPieces();
for (unsigned i = selectorPieces.size(); i > 0; --i) {
// If the selector piece is "error", this is the location of
// the error parameter.
auto piece = selectorPieces[i-1];
if (piece == ctx.Id_error) {
errorParameterIndex = i-1;
foundErrorParameterIndex = true;
break;
}
// If the first selector piece ends with "Error", it's here.
if (i == 1 && camel_case::getLastWord(piece.str()) == "Error") {
errorParameterIndex = i-1;
foundErrorParameterIndex = true;
break;
}
}
}
}
// If the selector did not provide an index for the error, find
// the last parameter that is not a trailing closure.
if (!foundErrorParameterIndex) {
auto *paramList = AFD->getParameters();
errorParameterIndex = paramList->size();
// Note: the errorParameterIndex is actually a SIL function
// parameter index, which means tuples are exploded. Normally
// tuple types cannot be bridged to Objective-C, except for
// one special case -- a constructor with a single named parameter
// 'foo' of tuple type becomes a zero-argument selector named
// 'initFoo'.
if (auto *CD = dyn_cast<ConstructorDecl>(AFD))
if (CD->isObjCZeroParameterWithLongSelector())
errorParameterIndex--;
while (errorParameterIndex > 0) {
// Skip over trailing closures.
auto type = paramList->get(errorParameterIndex - 1)->getType();
// It can't be a trailing closure unless it has a specific form.
// Only consider the rvalue type.
type = type->getRValueType();
// Look through one level of optionality.
if (auto objectType = type->getOptionalObjectType())
type = objectType;
// Is it a function type?
if (!type->is<AnyFunctionType>()) break;
--errorParameterIndex;
}
}
// Form the error convention.
CanType canErrorParameterType;
if (errorParameterType)
canErrorParameterType = errorParameterType->getCanonicalType();
switch (kind) {
case ForeignErrorConvention::ZeroResult:
errorConvention = ForeignErrorConvention::getZeroResult(
errorParameterIndex,
ForeignErrorConvention::IsNotOwned,
ForeignErrorConvention::IsNotReplaced,
canErrorParameterType,
errorResultType);
break;
case ForeignErrorConvention::NonZeroResult:
errorConvention = ForeignErrorConvention::getNonZeroResult(
errorParameterIndex,
ForeignErrorConvention::IsNotOwned,
ForeignErrorConvention::IsNotReplaced,
canErrorParameterType,
errorResultType);
break;
case ForeignErrorConvention::ZeroPreservedResult:
errorConvention = ForeignErrorConvention::getZeroPreservedResult(
errorParameterIndex,
ForeignErrorConvention::IsNotOwned,
ForeignErrorConvention::IsNotReplaced,
canErrorParameterType);
break;
case ForeignErrorConvention::NilResult:
errorConvention = ForeignErrorConvention::getNilResult(
errorParameterIndex,
ForeignErrorConvention::IsNotOwned,
ForeignErrorConvention::IsNotReplaced,
canErrorParameterType);
break;
case ForeignErrorConvention::NonNilError:
errorConvention = ForeignErrorConvention::getNilResult(
errorParameterIndex,
ForeignErrorConvention::IsNotOwned,
ForeignErrorConvention::IsNotReplaced,
canErrorParameterType);
break;
}
}
return true;
}
bool swift::isRepresentableInObjC(const VarDecl *VD, ObjCReason Reason) {
// If you change this function, you must add or modify a test in PrintAsObjC.
// FIXME: Computes isInvalid() below.
(void) VD->getInterfaceType();
if (VD->isInvalid())
return false;
Type T = VD->getDeclContext()->mapTypeIntoContext(VD->getInterfaceType());
if (auto *RST = T->getAs<ReferenceStorageType>()) {
// In-memory layout of @weak and @unowned does not correspond to anything
// in Objective-C, but this does not really matter here, since Objective-C
// uses getters and setters to operate on the property.
// Because of this, look through @weak and @unowned.
T = RST->getReferentType();
}
ASTContext &ctx = VD->getASTContext();
bool Result = T->isRepresentableIn(ForeignLanguage::ObjectiveC,
VD->getDeclContext());
bool Diagnose = shouldDiagnoseObjCReason(Reason, ctx);
if (Result && checkObjCInExtensionContext(VD, Diagnose))
return false;
if (checkObjCInForeignClassContext(VD, Reason))
return false;
if (!Diagnose || Result)
return Result;
SourceRange TypeRange = VD->getTypeSourceRangeForDiagnostics();
// TypeRange can be invalid; e.g. '@objc let foo = SwiftType()'
if (TypeRange.isInvalid())
TypeRange = VD->getNameLoc();
VD->diagnose(diag::objc_invalid_on_var, getObjCDiagnosticAttrKind(Reason))
.highlight(TypeRange);
diagnoseTypeNotRepresentableInObjC(VD->getDeclContext(),
VD->getInterfaceType(),
TypeRange);
describeObjCReason(VD, Reason);
return Result;
}
bool swift::isRepresentableInObjC(const SubscriptDecl *SD, ObjCReason Reason) {
// If you change this function, you must add or modify a test in PrintAsObjC.
ASTContext &ctx = SD->getASTContext();
bool Diagnose = shouldDiagnoseObjCReason(Reason, ctx);
if (checkObjCInForeignClassContext(SD, Reason))
return false;
// ObjC doesn't support class subscripts.
if (!SD->isInstanceMember()) {
if (Diagnose) {
SD->diagnose(diag::objc_invalid_on_static_subscript,
SD->getDescriptiveKind(), Reason);
describeObjCReason(SD, Reason);
}
return true;
}
// Figure out the type of the indices.
auto SubscriptType = SD->getInterfaceType()->getAs<AnyFunctionType>();
if (!SubscriptType)
return false;
if (SubscriptType->getParams().size() != 1)
return false;
auto IndexParam = SubscriptType->getParams()[0];
if (IndexParam.isInOut())
return false;
Type IndexType = SubscriptType->getParams()[0].getParameterType();
if (IndexType->hasError())
return false;
bool IndexResult =
IndexType->isRepresentableIn(ForeignLanguage::ObjectiveC,
SD->getDeclContext());
Type ElementType = SD->getElementInterfaceType();
bool ElementResult = ElementType->isRepresentableIn(
ForeignLanguage::ObjectiveC, SD->getDeclContext());
bool Result = IndexResult && ElementResult;
if (Result && checkObjCInExtensionContext(SD, Diagnose))
return false;
if (!Diagnose || Result)
return Result;
SourceRange TypeRange;
if (!IndexResult)
TypeRange = SD->getIndices()->getSourceRange();
else
TypeRange = SD->getElementTypeLoc().getSourceRange();
SD->diagnose(diag::objc_invalid_on_subscript,
getObjCDiagnosticAttrKind(Reason))
.highlight(TypeRange);
diagnoseTypeNotRepresentableInObjC(SD->getDeclContext(),
!IndexResult ? IndexType
: ElementType,
TypeRange);
describeObjCReason(SD, Reason);
return Result;
}
bool swift::canBeRepresentedInObjC(const ValueDecl *decl) {
ASTContext &ctx = decl->getASTContext();
if (!ctx.LangOpts.EnableObjCInterop)
return false;
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
Optional<ForeignErrorConvention> errorConvention;
return isRepresentableInObjC(func, ObjCReason::MemberOfObjCMembersClass,
errorConvention);
}
if (auto var = dyn_cast<VarDecl>(decl))
return isRepresentableInObjC(var, ObjCReason::MemberOfObjCMembersClass);
if (auto subscript = dyn_cast<SubscriptDecl>(decl))
return isRepresentableInObjC(subscript,
ObjCReason::MemberOfObjCMembersClass);
return false;
}
static Type getObjectiveCNominalType(Type &cache,
Identifier ModuleName,
Identifier TypeName,
DeclContext *dc) {
if (cache)
return cache;
// FIXME: Does not respect visibility of the module.
ASTContext &ctx = dc->getASTContext();
ModuleDecl *module = ctx.getLoadedModule(ModuleName);
if (!module)
return nullptr;
SmallVector<ValueDecl *, 4> decls;
NLOptions options = NL_QualifiedDefault | NL_OnlyTypes;
dc->lookupQualified(module, TypeName, options, decls);
for (auto decl : decls) {
if (auto nominal = dyn_cast<NominalTypeDecl>(decl)) {
cache = nominal->getDeclaredType();
return cache;
}
}
return nullptr;
}
#pragma mark Objective-C-specific types
Type TypeChecker::getNSObjectType(DeclContext *dc) {
return getObjectiveCNominalType(NSObjectType, Context.Id_ObjectiveC,
Context.getSwiftId(
KnownFoundationEntity::NSObject),
dc);
}
Type TypeChecker::getObjCSelectorType(DeclContext *dc) {
return getObjectiveCNominalType(ObjCSelectorType,
Context.Id_ObjectiveC,
Context.Id_Selector,
dc);
}
#pragma mark Bridging support
/// Check runtime functions responsible for implicit bridging of Objective-C
/// types.
static void checkObjCBridgingFunctions(ModuleDecl *mod,
StringRef bridgedTypeName,
StringRef forwardConversion,
StringRef reverseConversion) {
assert(mod);
SmallVector<ValueDecl *, 4> results;
auto &ctx = mod->getASTContext();
mod->lookupValue(ctx.getIdentifier(bridgedTypeName),
NLKind::QualifiedLookup, results);
mod->lookupValue(ctx.getIdentifier(forwardConversion),
NLKind::QualifiedLookup, results);
mod->lookupValue(ctx.getIdentifier(reverseConversion),
NLKind::QualifiedLookup, results);
for (auto D : results) {
// FIXME(InterfaceTypeRequest): Remove this.
(void)D->getInterfaceType();
}
}
void swift::checkBridgedFunctions(ASTContext &ctx) {
#define BRIDGE_TYPE(BRIDGED_MOD, BRIDGED_TYPE, _, NATIVE_TYPE, OPT) \
Identifier ID_##BRIDGED_MOD = ctx.getIdentifier(#BRIDGED_MOD);\
if (ModuleDecl *module = ctx.getLoadedModule(ID_##BRIDGED_MOD)) {\
checkObjCBridgingFunctions(module, #BRIDGED_TYPE, \
"_convert" #BRIDGED_TYPE "To" #NATIVE_TYPE, \
"_convert" #NATIVE_TYPE "To" #BRIDGED_TYPE); \
}
#include "swift/SIL/BridgedTypes.def"
if (ModuleDecl *module = ctx.getLoadedModule(ctx.Id_Foundation)) {
checkObjCBridgingFunctions(module,
ctx.getSwiftName(
KnownFoundationEntity::NSError),
"_convertNSErrorToError",
"_convertErrorToNSError");
}
}
#pragma mark "@objc declaration handling"
/// Whether this declaration is a member of a class extension marked @objc.
static bool isMemberOfObjCClassExtension(const ValueDecl *VD) {
auto ext = dyn_cast<ExtensionDecl>(VD->getDeclContext());
if (!ext) return false;
return ext->getSelfClassDecl() && ext->getAttrs().hasAttribute<ObjCAttr>();
}
/// Whether this declaration is a member of a class with the `@objcMembers`
/// attribute.
static bool isMemberOfObjCMembersClass(const ValueDecl *VD) {
auto classDecl = VD->getDeclContext()->getSelfClassDecl();
if (!classDecl) return false;
return classDecl->checkAncestry(AncestryFlags::ObjCMembers);
}
// A class is @objc if it does not have generic ancestry, and it either has
// an explicit @objc attribute, or its superclass is @objc.
static Optional<ObjCReason> shouldMarkClassAsObjC(const ClassDecl *CD) {
ASTContext &ctx = CD->getASTContext();
auto ancestry = CD->checkAncestry();
if (auto attr = CD->getAttrs().getAttribute<ObjCAttr>()) {
if (ancestry.contains(AncestryFlags::Generic)) {
if (attr->hasName() && !CD->isGenericContext()) {
// @objc with a name on a non-generic subclass of a generic class is
// just controlling the runtime name. Don't diagnose this case.
const_cast<ClassDecl *>(CD)->getAttrs().add(
new (ctx) ObjCRuntimeNameAttr(*attr));
return None;
}
ctx.Diags.diagnose(attr->getLocation(), diag::objc_for_generic_class)
.fixItRemove(attr->getRangeWithAt());
}
// If the class has resilient ancestry, @objc just controls the runtime
// name unless all targets where the class is available support
// class stubs.
if (ancestry.contains(AncestryFlags::ResilientOther) &&
!checkObjCClassStubAvailability(ctx, CD)) {
if (attr->hasName()) {
const_cast<ClassDecl *>(CD)->getAttrs().add(
new (ctx) ObjCRuntimeNameAttr(*attr));
return None;
}
auto &target = ctx.LangOpts.Target;
auto platform = prettyPlatformString(targetPlatform(ctx.LangOpts));
auto range = getMinOSVersionForClassStubs(target);
auto *ancestor = getResilientAncestor(CD->getParentModule(), CD);
ctx.Diags.diagnose(attr->getLocation(),
diag::objc_for_resilient_class,
ancestor->getName(),
platform,
range.getLowerEndpoint())
.fixItRemove(attr->getRangeWithAt());
}
// Only allow ObjC-rooted classes to be @objc.
// (Leave a hole for test cases.)
if (ancestry.contains(AncestryFlags::ObjC) &&
!ancestry.contains(AncestryFlags::ClangImported)) {
if (ctx.LangOpts.EnableObjCAttrRequiresFoundation)
ctx.Diags.diagnose(attr->getLocation(),
diag::invalid_objc_swift_rooted_class)
.fixItRemove(attr->getRangeWithAt());
if (!ctx.LangOpts.EnableObjCInterop)
ctx.Diags.diagnose(attr->getLocation(), diag::objc_interop_disabled)
.fixItRemove(attr->getRangeWithAt());
}
return ObjCReason(ObjCReason::ExplicitlyObjC);
}
if (ancestry.contains(AncestryFlags::ObjC)) {
if (ancestry.contains(AncestryFlags::Generic)) {
return None;
}
if (ancestry.contains(AncestryFlags::ResilientOther) &&
!checkObjCClassStubAvailability(ctx, CD)) {
return None;
}
return ObjCReason(ObjCReason::ImplicitlyObjC);
}
return None;
}
/// Figure out if a declaration should be exported to Objective-C.
Optional<ObjCReason> shouldMarkAsObjC(const ValueDecl *VD, bool allowImplicit) {
// If Objective-C interoperability is disabled, nothing gets marked as @objc.
if (!VD->getASTContext().LangOpts.EnableObjCInterop)
return None;
if (auto classDecl = dyn_cast<ClassDecl>(VD)) {
return shouldMarkClassAsObjC(classDecl);
}
// Infer @objc for @_dynamicReplacement(for:) when replaced decl is @objc.
if (isa<AbstractFunctionDecl>(VD) || isa<AbstractStorageDecl>(VD))
if (auto *replacementAttr =
VD->getAttrs().getAttribute<DynamicReplacementAttr>()) {
if (auto *replaced = replacementAttr->getReplacedFunction()) {
if (replaced->isObjC())
return ObjCReason(ObjCReason::ImplicitlyObjC);
} else if (auto *replaced =
TypeChecker::findReplacedDynamicFunction(VD)) {
if (replaced->isObjC())
return ObjCReason(ObjCReason::ImplicitlyObjC);
}
}
// Destructors are always @objc, with -dealloc as their entry point.
if (isa<DestructorDecl>(VD))
return ObjCReason(ObjCReason::ImplicitlyObjC);
ProtocolDecl *protocolContext =
dyn_cast<ProtocolDecl>(VD->getDeclContext());
bool isMemberOfObjCProtocol =
protocolContext && protocolContext->isObjC();
// Local function to determine whether we can implicitly infer @objc.
auto canInferImplicitObjC = [&](bool allowAnyAccess) {
if (VD->isInvalid())
return false;
if (VD->isOperator())
return false;
// Implicitly generated declarations are not @objc, except for constructors.
if (!allowImplicit && VD->isImplicit())
return false;
if (!allowAnyAccess && VD->getFormalAccess() <= AccessLevel::FilePrivate)
return false;
if (auto accessor = dyn_cast<AccessorDecl>(VD)) {
switch (accessor->getAccessorKind()) {
case AccessorKind::DidSet:
case AccessorKind::Modify:
case AccessorKind::Read:
case AccessorKind::WillSet:
return false;
case AccessorKind::MutableAddress:
case AccessorKind::Address:
case AccessorKind::Get:
case AccessorKind::Set:
break;
}
}
return true;
};
// explicitly declared @objc.
if (VD->getAttrs().hasAttribute<ObjCAttr>())
return ObjCReason(ObjCReason::ExplicitlyObjC);
// Getter or setter for an @objc property or subscript.
if (auto accessor = dyn_cast<AccessorDecl>(VD)) {
if (accessor->getAccessorKind() == AccessorKind::Get ||
accessor->getAccessorKind() == AccessorKind::Set) {
if (accessor->getStorage()->isObjC())
return ObjCReason(ObjCReason::Accessor);
return None;
}
}
// @IBOutlet, @IBAction, @IBSegueAction, @NSManaged, and @GKInspectable imply
// @objc.
//
// @IBInspectable and @GKInspectable imply @objc quietly in Swift 3
// (where they warn on failure) and loudly in Swift 4 (error on failure).
if (VD->getAttrs().hasAttribute<IBOutletAttr>())
return ObjCReason(ObjCReason::ExplicitlyIBOutlet);
if (VD->getAttrs().hasAttribute<IBActionAttr>())
return ObjCReason(ObjCReason::ExplicitlyIBAction);
if (VD->getAttrs().hasAttribute<IBSegueActionAttr>())
return ObjCReason(ObjCReason::ExplicitlyIBSegueAction);
if (VD->getAttrs().hasAttribute<IBInspectableAttr>())
return ObjCReason(ObjCReason::ExplicitlyIBInspectable);
if (VD->getAttrs().hasAttribute<GKInspectableAttr>())
return ObjCReason(ObjCReason::ExplicitlyGKInspectable);
if (VD->getAttrs().hasAttribute<NSManagedAttr>())
return ObjCReason(ObjCReason::ExplicitlyNSManaged);
// A member of an @objc protocol is implicitly @objc.
if (isMemberOfObjCProtocol) {
if (!VD->isProtocolRequirement())
return None;
return ObjCReason(ObjCReason::MemberOfObjCProtocol);
}
// A @nonobjc is not @objc, even if it is an override of an @objc, so check
// for @nonobjc first.
if (VD->getAttrs().hasAttribute<NonObjCAttr>() ||
(isa<ExtensionDecl>(VD->getDeclContext()) &&
cast<ExtensionDecl>(VD->getDeclContext())->getAttrs()
.hasAttribute<NonObjCAttr>()))
return None;
if (isMemberOfObjCClassExtension(VD) &&
canInferImplicitObjC(/*allowAnyAccess*/true))
return ObjCReason(ObjCReason::MemberOfObjCExtension);
if (isMemberOfObjCMembersClass(VD) &&
canInferImplicitObjC(/*allowAnyAccess*/false))
return ObjCReason(ObjCReason::MemberOfObjCMembersClass);
// An override of an @objc declaration is implicitly @objc.
if (VD->getOverriddenDecl() && VD->getOverriddenDecl()->isObjC())
return ObjCReason(ObjCReason::OverridesObjC);
// A witness to an @objc protocol requirement is implicitly @objc.
if (VD->getDeclContext()->getSelfClassDecl()) {
auto requirements =
findWitnessedObjCRequirements(VD, /*anySingleRequirement=*/true);
if (!requirements.empty())
return ObjCReason::witnessToObjC(requirements.front());
}
ASTContext &ctx = VD->getASTContext();
// Under Swift 3's @objc inference rules, 'dynamic' infers '@objc'.
if (auto attr = VD->getAttrs().getAttribute<DynamicAttr>()) {
bool isGetterOrSetter =
isa<AccessorDecl>(VD) && cast<AccessorDecl>(VD)->isGetterOrSetter();
if (ctx.LangOpts.EnableSwift3ObjCInference) {
// If we've been asked to warn about deprecated @objc inference, do so
// now.
if (ctx.LangOpts.WarnSwift3ObjCInference !=
Swift3ObjCInferenceWarnings::None &&
!isGetterOrSetter) {
VD->diagnose(diag::objc_inference_swift3_dynamic)
.highlight(attr->getLocation())
.fixItInsert(VD->getAttributeInsertionLoc(/*forModifier=*/false),
"@objc ");
}
return ObjCReason(ObjCReason::ExplicitlyDynamic);
}
}
// If we aren't provided Swift 3's @objc inference rules, we're done.
if (!ctx.LangOpts.EnableSwift3ObjCInference)
return None;
// Infer '@objc' for valid, non-implicit, non-operator, members of classes
// (and extensions thereof) whose class hierarchies originate in Objective-C,
// e.g., which derive from NSObject, so long as the members have internal
// access or greater.
if (!canInferImplicitObjC(/*allowAnyAccess*/false))
return None;
// If this declaration is part of a class with implicitly @objc members,
// make it implicitly @objc. However, if the declaration cannot be represented
// as @objc, don't diagnose.
if (auto classDecl = VD->getDeclContext()->getSelfClassDecl()) {
// One cannot define @objc members of any foreign classes.
if (classDecl->isForeign())
return None;
if (classDecl->checkAncestry(AncestryFlags::ObjC))
return ObjCReason(ObjCReason::MemberOfObjCSubclass);
}
return None;
}
/// Determine whether the given type is a C integer type.
static bool isCIntegerType(Type type) {
auto nominal = type->getAnyNominal();
if (!nominal) return false;
ASTContext &ctx = nominal->getASTContext();
auto stdlibModule = ctx.getStdlibModule();
if (nominal->getParentModule() != stdlibModule)
return false;
// Check for each of the C integer type equivalents in the standard library.
auto matchesStdlibTypeNamed = [&](StringRef name) {
auto identifier = ctx.getIdentifier(name);
SmallVector<ValueDecl *, 2> foundDecls;
stdlibModule->lookupValue(identifier, NLKind::UnqualifiedLookup,
foundDecls);
for (auto found : foundDecls) {
auto foundType = dyn_cast<TypeDecl>(found);
if (!foundType)
continue;
if (foundType->getDeclaredInterfaceType()->isEqual(type))
return true;
}
return false;
};
#define MAP_BUILTIN_TYPE(_, __)
#define MAP_BUILTIN_INTEGER_TYPE(CLANG_BUILTIN_KIND, SWIFT_TYPE_NAME) \
if (matchesStdlibTypeNamed(#SWIFT_TYPE_NAME)) \
return true;
#include "swift/ClangImporter/BuiltinMappedTypes.def"
return false;
}
/// Determine whether the given enum should be @objc.
static bool isEnumObjC(EnumDecl *enumDecl) {
// FIXME: Use shouldMarkAsObjC once it loses it's TypeChecker argument.
// If there is no @objc attribute, it's not @objc.
if (!enumDecl->getAttrs().hasAttribute<ObjCAttr>())
return false;
Type rawType = enumDecl->getRawType();
// @objc enums must have a raw type.
if (!rawType) {
enumDecl->diagnose(diag::objc_enum_no_raw_type);
return false;
}
// If the raw type contains an error, we've already diagnosed it.
if (rawType->hasError())
return false;
// The raw type must be one of the C integer types.
if (!isCIntegerType(rawType)) {
SourceRange errorRange;
if (!enumDecl->getInherited().empty())
errorRange = enumDecl->getInherited().front().getSourceRange();
enumDecl->diagnose(diag::objc_enum_raw_type_not_integer, rawType)
.highlight(errorRange);
return false;
}
// We need at least one case to have a raw value.
if (enumDecl->getAllElements().empty()) {
enumDecl->diagnose(diag::empty_enum_raw_type);
}
return true;
}
/// Record that a declaration is @objc.
static void markAsObjC(ValueDecl *D, ObjCReason reason,
Optional<ForeignErrorConvention> errorConvention);
llvm::Expected<bool>
IsObjCRequest::evaluate(Evaluator &evaluator, ValueDecl *VD) const {
auto dc = VD->getDeclContext();
Optional<ObjCReason> isObjC;
if (dc->getSelfClassDecl() && !isa<TypeDecl>(VD)) {
// Members of classes can be @objc.
isObjC = shouldMarkAsObjC(VD, isa<ConstructorDecl>(VD));
}
else if (isa<ClassDecl>(VD)) {
// Classes can be @objc.
// Protocols and enums can also be @objc, but this is covered by the
// isObjC() check a the beginning.;
isObjC = shouldMarkAsObjC(VD, /*allowImplicit=*/false);
} else if (auto enumDecl = dyn_cast<EnumDecl>(VD)) {
// Enums can be @objc so long as they have a raw type that is representable
// as an arithmetic type in C.
if (isEnumObjC(enumDecl))
isObjC = ObjCReason(ObjCReason::ExplicitlyObjC);
} else if (auto enumElement = dyn_cast<EnumElementDecl>(VD)) {
// Enum elements can be @objc so long as the containing enum is @objc.
if (enumElement->getParentEnum()->isObjC()) {
if (enumElement->getAttrs().hasAttribute<ObjCAttr>())
isObjC = ObjCReason::ExplicitlyObjC;
else
isObjC = ObjCReason::ElementOfObjCEnum;
}
} else if (auto proto = dyn_cast<ProtocolDecl>(VD)) {
if (proto->getAttrs().hasAttribute<ObjCAttr>()) {
isObjC = ObjCReason(ObjCReason::ExplicitlyObjC);
// If the protocol is @objc, it may only refine other @objc protocols.
// FIXME: Revisit this restriction.
for (auto inherited : proto->getInheritedProtocols()) {
if (!inherited->isObjC()) {
proto->diagnose(diag::objc_protocol_inherits_non_objc_protocol,
proto->getDeclaredType(),
inherited->getDeclaredType());
inherited->diagnose(diag::kind_declname_declared_here,
DescriptiveDeclKind::Protocol,
inherited->getName());
isObjC = None;
}
}
}
} else if (isa<ProtocolDecl>(dc) && cast<ProtocolDecl>(dc)->isObjC()) {
// Members of @objc protocols are @objc.
isObjC = shouldMarkAsObjC(VD, isa<ConstructorDecl>(VD));
} else {
// Cannot be @objc.
}
// Perform some icky stateful hackery to mark this declaration as
// not being @objc.
auto makeNotObjC = [&] {
if (auto objcAttr = VD->getAttrs().getAttribute<ObjCAttr>()) {
objcAttr->setInvalid();
}
};
// If this declaration should not be exposed to Objective-C, we're done.
if (!isObjC) {
makeNotObjC();
return false;
}
if (auto accessor = dyn_cast<AccessorDecl>(VD)) {
auto storage = accessor->getStorage();
if (auto storageObjCAttr = storage->getAttrs().getAttribute<ObjCAttr>()) {
// If @objc on the storage declaration was inferred using a
// deprecated rule, but this accessor is @objc in its own right,
// complain.
ASTContext &ctx = dc->getASTContext();
if (storageObjCAttr && storageObjCAttr->isSwift3Inferred() &&
shouldDiagnoseObjCReason(*isObjC, ctx)) {
storage->diagnose(diag::accessor_swift3_objc_inference,
storage->getDescriptiveKind(), storage->getFullName(),
isa<SubscriptDecl>(storage), accessor->isSetter())
.fixItInsert(storage->getAttributeInsertionLoc(/*forModifier=*/false),
"@objc ");
}
}
}
// If needed, check whether this declaration is representable in Objective-C.
Optional<ForeignErrorConvention> errorConvention;
if (auto var = dyn_cast<VarDecl>(VD)) {
if (!isRepresentableInObjC(var, *isObjC)) {
makeNotObjC();
return false;
}
} else if (auto subscript = dyn_cast<SubscriptDecl>(VD)) {
if (!isRepresentableInObjC(subscript, *isObjC)) {
makeNotObjC();
return false;
}
} else if (isa<DestructorDecl>(VD)) {
// Destructors need no additional checking.
} else if (auto func = dyn_cast<AbstractFunctionDecl>(VD)) {
if (!isRepresentableInObjC(func, *isObjC, errorConvention)) {
makeNotObjC();
return false;
}
}
// Note that this declaration is exposed to Objective-C.
markAsObjC(VD, *isObjC, errorConvention);
return true;
}
/// Infer the Objective-C name for a given declaration.
static ObjCSelector inferObjCName(ValueDecl *decl) {
if (auto destructor = dyn_cast<DestructorDecl>(decl))
return destructor->getObjCSelector();
auto attr = decl->getAttrs().getAttribute<ObjCAttr>();
/// Set the @objc name.
ASTContext &ctx = decl->getASTContext();
auto setObjCName = [&](ObjCSelector selector) {
// If there already is an @objc attribute, update its name.
if (attr) {
const_cast<ObjCAttr *>(attr)->setName(selector, /*implicit=*/true);
return;
}
// Otherwise, create an @objc attribute with the implicit name.
attr = ObjCAttr::create(ctx, selector, /*implicitName=*/true);
decl->getAttrs().add(attr);
};
// If this declaration overrides an @objc declaration, use its name.
if (auto overridden = decl->getOverriddenDecl()) {
if (overridden->isObjC()) {
// Handle methods first.
if (auto overriddenFunc = dyn_cast<AbstractFunctionDecl>(overridden)) {
// Determine the selector of the overridden method.
ObjCSelector overriddenSelector = overriddenFunc->getObjCSelector();
// Determine whether there is a name conflict.
bool shouldFixName = !attr || !attr->hasName();
if (attr && attr->hasName() && *attr->getName() != overriddenSelector) {
// If the user explicitly wrote the incorrect name, complain.
if (!attr->isNameImplicit()) {
{
auto diag = ctx.Diags.diagnose(
attr->AtLoc,
diag::objc_override_method_selector_mismatch,
*attr->getName(), overriddenSelector);
fixDeclarationObjCName(diag, decl, attr->getName(),
overriddenSelector);
}
overriddenFunc->diagnose(diag::overridden_here);
}
shouldFixName = true;
}
// If we have to set the name, do so.
if (shouldFixName) {
// Override the name on the attribute.
setObjCName(overriddenSelector);
}
return overriddenSelector;
}
// Handle properties.
if (auto overriddenProp = dyn_cast<VarDecl>(overridden)) {
Identifier overriddenName = overriddenProp->getObjCPropertyName();
ObjCSelector overriddenNameAsSel(ctx, 0, overriddenName);
// Determine whether there is a name conflict.
bool shouldFixName = !attr || !attr->hasName();
if (attr && attr->hasName() &&
*attr->getName() != overriddenNameAsSel) {
// If the user explicitly wrote the wrong name, complain.
if (!attr->isNameImplicit()) {
ctx.Diags.diagnose(attr->AtLoc,
diag::objc_override_property_name_mismatch,
attr->getName()->getSelectorPieces()[0],
overriddenName)
.fixItReplaceChars(attr->getNameLocs().front(),
attr->getRParenLoc(),
overriddenName.str());
overridden->diagnose(diag::overridden_here);
}
shouldFixName = true;
}
// Fix the name, if needed.
if (shouldFixName) {
setObjCName(overriddenNameAsSel);
}
return overriddenNameAsSel;
}
}
}
// If the decl already has a name, do nothing; the protocol conformance
// checker will handle any mismatches.
if (attr && attr->hasName())
return *attr->getName();
// When no override determined the Objective-C name, look for
// requirements for which this declaration is a witness.
Optional<ObjCSelector> requirementObjCName;
ValueDecl *firstReq = nullptr;
for (auto req : findWitnessedObjCRequirements(decl)) {
// If this is the first requirement, take its name.
if (!requirementObjCName) {
requirementObjCName = req->getObjCRuntimeName();
firstReq = req;
continue;
}
// If this requirement has a different name from one we've seen,
// note the ambiguity.
if (*requirementObjCName != *req->getObjCRuntimeName()) {
decl->diagnose(diag::objc_ambiguous_inference,
decl->getDescriptiveKind(), decl->getFullName(),
*requirementObjCName, *req->getObjCRuntimeName());
// Note the candidates and what Objective-C names they provide.
auto diagnoseCandidate = [&](ValueDecl *req) {
auto proto = cast<ProtocolDecl>(req->getDeclContext());
auto diag = decl->diagnose(diag::objc_ambiguous_inference_candidate,
req->getFullName(),
proto->getFullName(),
*req->getObjCRuntimeName());
fixDeclarationObjCName(diag, decl,
decl->getObjCRuntimeName(/*skipIsObjC=*/true),
req->getObjCRuntimeName());
};
diagnoseCandidate(firstReq);
diagnoseCandidate(req);
// Suggest '@nonobjc' to suppress this error, and not try to
// infer @objc for anything.
decl->diagnose(diag::req_near_match_nonobjc, true)
.fixItInsert(decl->getAttributeInsertionLoc(false), "@nonobjc ");
break;
}
}
// If we have a name, install it via an @objc attribute.
if (requirementObjCName) {
setObjCName(*requirementObjCName);
return *requirementObjCName;
}
return *decl->getObjCRuntimeName(true);
}
/// Mark the given declaration as being Objective-C compatible (or
/// not) as appropriate.
///
/// If the declaration has a @nonobjc attribute, diagnose an error
/// using the given Reason, if present.
void markAsObjC(ValueDecl *D, ObjCReason reason,
Optional<ForeignErrorConvention> errorConvention) {
ASTContext &ctx = D->getASTContext();
// By now, the caller will have handled the case where an implicit @objc
// could be overridden by @nonobjc. If we see a @nonobjc and we are trying
// to add an @objc for whatever reason, diagnose an error.
if (auto *attr = D->getAttrs().getAttribute<NonObjCAttr>()) {
if (!shouldDiagnoseObjCReason(reason, ctx))
reason = ObjCReason::ImplicitlyObjC;
D->diagnose(diag::nonobjc_not_allowed,
getObjCDiagnosticAttrKind(reason));
attr->setInvalid();
}
if (auto method = dyn_cast<AbstractFunctionDecl>(D)) {
// Determine the foreign error convention.
if (auto baseMethod = method->getOverriddenDecl()) {
// If the overridden method has a foreign error convention,
// adopt it. Set the foreign error convention for a throwing
// method. Note that the foreign error convention affects the
// selector, so we perform this before inferring a selector.
if (method->hasThrows()) {
if (auto baseErrorConvention
= baseMethod->getForeignErrorConvention()) {
errorConvention = baseErrorConvention;
}
assert(errorConvention && "Missing error convention");
method->setForeignErrorConvention(*errorConvention);
}
} else if (method->hasThrows()) {
// Attach the foreign error convention.
assert(errorConvention && "Missing error convention");
method->setForeignErrorConvention(*errorConvention);
}
// Infer the Objective-C name for this method.
auto selector = inferObjCName(method);
// Swift does not permit class methods with Objective-C selectors 'load',
// 'alloc', or 'allocWithZone:'. Check for these cases.
if (!method->isInstanceMember()) {
auto isForbiddenSelector = [&](ObjCSelector sel)
-> Optional<Diag<unsigned, DeclName, ObjCSelector>> {
switch (sel.getNumArgs()) {
case 0:
if (sel.getSelectorPieces().front() == ctx.Id_load ||
sel.getSelectorPieces().front() == ctx.Id_alloc)
return diag::objc_class_method_not_permitted;
// Swift 3 and earlier allowed you to override `initialize`, but
// Swift's semantics do not guarantee that it will be called at
// the point you expect. It is disallowed in Swift 4 and later.
if (sel.getSelectorPieces().front() == ctx.Id_initialize)
return diag::objc_class_method_not_permitted;
return None;
case 1:
if (sel.getSelectorPieces().front() == ctx.Id_allocWithZone)
return diag::objc_class_method_not_permitted;
return None;
default:
return None;
}
};
if (auto diagID = isForbiddenSelector(selector)) {
auto diagInfo = getObjCMethodDiagInfo(method);
method->diagnose(*diagID, diagInfo.first, diagInfo.second, selector);
}
}
// Record the method in the class, if it's a member of one.
if (auto classDecl = D->getDeclContext()->getSelfClassDecl()) {
classDecl->recordObjCMethod(method, selector);
}
// Record the method in the source file.
if (auto sourceFile = method->getParentSourceFile()) {
sourceFile->ObjCMethods[selector].push_back(method);
}
} else if (isa<VarDecl>(D)) {
// Infer the Objective-C name for this property.
(void)inferObjCName(D);
// FIXME: We should have a class-based table to check for conflicts.
}
// Special handling for Swift 3 @objc inference rules that are no longer
// present in later versions of Swift.
if (reason == ObjCReason::MemberOfObjCSubclass) {
// If we've been asked to unconditionally warn about these deprecated
// @objc inference rules, do so now. However, we don't warn about
// accessors---just the main storage declarations.
if (ctx.LangOpts.WarnSwift3ObjCInference ==
Swift3ObjCInferenceWarnings::Complete &&
!(isa<AccessorDecl>(D) && cast<AccessorDecl>(D)->isGetterOrSetter())) {
D->diagnose(diag::objc_inference_swift3_objc_derived);
D->diagnose(diag::objc_inference_swift3_addobjc)
.fixItInsert(D->getAttributeInsertionLoc(/*forModifier=*/false),
"@objc ");
D->diagnose(diag::objc_inference_swift3_addnonobjc)
.fixItInsert(D->getAttributeInsertionLoc(/*forModifier=*/false),
"@nonobjc ");
}
// Mark the attribute as having used Swift 3 inference, or create an
// implicit @objc for that purpose.
auto attr = D->getAttrs().getAttribute<ObjCAttr>();
if (!attr) {
attr = ObjCAttr::createUnnamedImplicit(ctx);
D->getAttrs().add(attr);
}
attr->setSwift3Inferred();
}
}
void swift::diagnoseAttrsRequiringFoundation(SourceFile &SF) {
auto &Ctx = SF.getASTContext();
bool ImportsFoundationModule = false;
if (Ctx.LangOpts.EnableObjCInterop) {
if (!Ctx.LangOpts.EnableObjCAttrRequiresFoundation)
return;
if (SF.Kind == SourceFileKind::SIL)
return;
}
for (auto import : namelookup::getAllImports(&SF)) {
if (import.second->getName() == Ctx.Id_Foundation) {
ImportsFoundationModule = true;
break;
}
}
if (ImportsFoundationModule)
return;
for (auto Attr : SF.AttrsRequiringFoundation) {
if (!Ctx.LangOpts.EnableObjCInterop)
Ctx.Diags.diagnose(Attr->getLocation(), diag::objc_interop_disabled)
.fixItRemove(Attr->getRangeWithAt());
Ctx.Diags.diagnose(Attr->getLocation(),
diag::attr_used_without_required_module,
Attr, Ctx.Id_Foundation)
.highlight(Attr->getRangeWithAt());
}
}
/// Compute the information used to describe an Objective-C redeclaration.
std::pair<unsigned, DeclName> swift::getObjCMethodDiagInfo(
AbstractFunctionDecl *member) {
if (isa<ConstructorDecl>(member))
return { 0 + member->isImplicit(), member->getFullName() };
if (isa<DestructorDecl>(member))
return { 2 + member->isImplicit(), member->getFullName() };
if (auto accessor = dyn_cast<AccessorDecl>(member)) {
switch (accessor->getAccessorKind()) {
#define OBJC_ACCESSOR(ID, KEYWORD)
#define ACCESSOR(ID) \
case AccessorKind::ID:
#include "swift/AST/AccessorKinds.def"
llvm_unreachable("Not an Objective-C entry point");
case AccessorKind::Get:
if (auto var = dyn_cast<VarDecl>(accessor->getStorage()))
return { 5, var->getFullName() };
return { 6, Identifier() };
case AccessorKind::Set:
if (auto var = dyn_cast<VarDecl>(accessor->getStorage()))
return { 7, var->getFullName() };
return { 8, Identifier() };
}
llvm_unreachable("Unhandled AccessorKind in switch.");
}
// Normal method.
auto func = cast<FuncDecl>(member);
return { 4, func->getFullName() };
}
bool swift::fixDeclarationName(InFlightDiagnostic &diag, const ValueDecl *decl,
DeclName targetName) {
if (decl->isImplicit()) return false;
if (decl->getFullName() == targetName) return false;
// Handle properties directly.
if (auto var = dyn_cast<VarDecl>(decl)) {
// Replace the name.
SmallString<64> scratch;
diag.fixItReplace(var->getNameLoc(), targetName.getString(scratch));
return false;
}
// We only handle functions from here on.
auto func = dyn_cast<AbstractFunctionDecl>(decl);
if (!func) return true;
auto name = func->getFullName();
// Fix the name of the function itself.
if (name.getBaseName() != targetName.getBaseName()) {
diag.fixItReplace(func->getLoc(), targetName.getBaseName().userFacingName());
}
// Fix the argument names that need fixing.
assert(name.getArgumentNames().size()
== targetName.getArgumentNames().size());
auto params = func->getParameters();
for (unsigned i = 0, n = name.getArgumentNames().size(); i != n; ++i) {
auto origArg = name.getArgumentNames()[i];
auto targetArg = targetName.getArgumentNames()[i];
if (origArg == targetArg)
continue;
auto *param = params->get(i);
// The parameter has an explicitly-specified API name, and it's wrong.
if (param->getArgumentNameLoc() != param->getLoc() &&
param->getArgumentNameLoc().isValid()) {
// ... but the internal parameter name was right. Just zap the
// incorrect explicit specialization.
if (param->getName() == targetArg) {
diag.fixItRemoveChars(param->getArgumentNameLoc(),
param->getLoc());
continue;
}
// Fix the API name.
StringRef targetArgStr = targetArg.empty()? "_" : targetArg.str();
diag.fixItReplace(param->getArgumentNameLoc(), targetArgStr);
continue;
}
// The parameter did not specify a separate API name. Insert one.
if (targetArg.empty())
diag.fixItInsert(param->getLoc(), "_ ");
else {
llvm::SmallString<8> targetArgStr;
targetArgStr += targetArg.str();
targetArgStr += ' ';
diag.fixItInsert(param->getLoc(), targetArgStr);
}
}
return false;
}
bool swift::fixDeclarationObjCName(InFlightDiagnostic &diag, const ValueDecl *decl,
Optional<ObjCSelector> nameOpt,
Optional<ObjCSelector> targetNameOpt,
bool ignoreImpliedName) {
if (decl->isImplicit())
return false;
// Subscripts cannot be renamed, so handle them directly.
if (isa<SubscriptDecl>(decl)) {
diag.fixItInsert(decl->getAttributeInsertionLoc(/*forModifier=*/false),
"@objc ");
return false;
}
auto name = *nameOpt;
auto targetName = *targetNameOpt;
// Dig out the existing '@objc' attribute on the witness. We don't care
// about implicit ones because they don't have useful source location
// information.
auto attr = decl->getAttrs().getAttribute<ObjCAttr>();
if (attr && attr->isImplicit())
attr = nullptr;
// If there is an @objc attribute with an explicit, incorrect witness
// name, go fix the witness name.
if (attr && name != targetName &&
attr->hasName() && !attr->isNameImplicit()) {
// Find the source range covering the full name.
SourceLoc startLoc;
if (attr->getNameLocs().empty())
startLoc = attr->getRParenLoc();
else
startLoc = attr->getNameLocs().front();
// Replace the name with the name of the requirement.
SmallString<64> scratch;
diag.fixItReplaceChars(startLoc, attr->getRParenLoc(),
targetName.getString(scratch));
return false;
}
// We need to create or amend an @objc attribute with the appropriate name.
// Form the Fix-It text.
SourceLoc startLoc;
SmallString<64> fixItText;
{
assert((!attr || !attr->hasName() || attr->isNameImplicit() ||
name == targetName) && "Nothing to diagnose!");
llvm::raw_svector_ostream out(fixItText);
// If there is no @objc attribute, we need to add our own '@objc'.
if (!attr) {
startLoc = decl->getAttributeInsertionLoc(/*forModifier=*/false);
out << "@objc";
} else {
startLoc = Lexer::getLocForEndOfToken(decl->getASTContext().SourceMgr,
attr->getRange().End);
}
// If the names of the witness and requirement differ, we need to
// specify the name.
if (name != targetName || ignoreImpliedName) {
out << "(";
out << targetName;
out << ")";
}
if (!attr)
out << " ";
}
diag.fixItInsert(startLoc, fixItText);
return false;
}
namespace {
/// Produce a deterministic ordering of the given declarations.
class OrderDeclarations {
SourceManager &SrcMgr;
public:
OrderDeclarations(SourceManager &srcMgr) : SrcMgr(srcMgr) { }
bool operator()(ValueDecl *lhs, ValueDecl *rhs) const {
// If the declarations come from different modules, order based on the
// module.
ModuleDecl *lhsModule = lhs->getDeclContext()->getParentModule();
ModuleDecl *rhsModule = rhs->getDeclContext()->getParentModule();
if (lhsModule != rhsModule) {
return lhsModule->getName().str() < rhsModule->getName().str();
}
// If the two declarations are in the same source file, order based on
// location within that source file.
SourceFile *lhsSF = lhs->getDeclContext()->getParentSourceFile();
SourceFile *rhsSF = rhs->getDeclContext()->getParentSourceFile();
if (lhsSF == rhsSF) {
// If only one location is valid, the valid location comes first.
if (lhs->getLoc().isValid() != rhs->getLoc().isValid()) {
return lhs->getLoc().isValid();
}
// Prefer the declaration that comes first in the source file.
return SrcMgr.isBeforeInBuffer(lhs->getLoc(), rhs->getLoc());
}
// The declarations are in different source files (or unknown source
// files) of the same module. Order based on name.
// FIXME: This isn't a total ordering.
return lhs->getFullName() < rhs->getFullName();
}
};
} // end anonymous namespace
/// Lookup for an Objective-C method with the given selector in the
/// given class or any of its superclasses.
static AbstractFunctionDecl *lookupObjCMethodInClass(
ClassDecl *classDecl,
ObjCSelector selector,
bool isInstanceMethod,
bool isInitializer,
SourceManager &srcMgr,
bool inheritingInits = true) {
if (!classDecl)
return nullptr;
// Look for an Objective-C method in this class.
auto methods = classDecl->lookupDirect(selector, isInstanceMethod);
if (!methods.empty()) {
// If we aren't inheriting initializers, remove any initializers from the
// list.
if (!inheritingInits &&
std::find_if(methods.begin(), methods.end(),
[](AbstractFunctionDecl *func) {
return isa<ConstructorDecl>(func);
}) != methods.end()) {
SmallVector<AbstractFunctionDecl *, 4> nonInitMethods;
std::copy_if(methods.begin(), methods.end(),
std::back_inserter(nonInitMethods),
[&](AbstractFunctionDecl *func) {
return !isa<ConstructorDecl>(func);
});
if (nonInitMethods.empty())
return nullptr;
return *std::min_element(nonInitMethods.begin(), nonInitMethods.end(),
OrderDeclarations(srcMgr));
}
return *std::min_element(methods.begin(), methods.end(),
OrderDeclarations(srcMgr));
}
// Recurse into the superclass.
if (!classDecl->hasSuperclass())
return nullptr;
// Determine whether we are (still) inheriting initializers.
inheritingInits = inheritingInits &&
classDecl->inheritsSuperclassInitializers();
if (isInitializer && !inheritingInits)
return nullptr;
return lookupObjCMethodInClass(classDecl->getSuperclassDecl(), selector,
isInstanceMethod, isInitializer, srcMgr,
inheritingInits);
}
bool swift::diagnoseUnintendedObjCMethodOverrides(SourceFile &sf) {
auto &Ctx = sf.getASTContext();
auto &methods = sf.ObjCMethodList;
// If no Objective-C methods were defined in this file, we're done.
if (methods.empty())
return false;
// Sort the methods by declaration order.
std::sort(methods.begin(), methods.end(), OrderDeclarations(Ctx.SourceMgr));
// For each Objective-C method declared in this file, check whether
// it overrides something in one of its superclasses. We
// intentionally don't respect access control here, since everything
// is visible to the Objective-C runtime.
bool diagnosedAny = false;
for (auto method : methods) {
// If the method has an @objc override, we don't need to do any
// more checking.
if (auto overridden = method->getOverriddenDecl()) {
if (overridden->isObjC())
continue;
}
// Skip deinitializers.
if (isa<DestructorDecl>(method))
continue;
// Skip invalid declarations.
if (method->isInvalid())
continue;
// Skip declarations with an invalid 'override' attribute on them.
if (auto attr = method->getAttrs().getAttribute<OverrideAttr>(true)) {
if (attr->isInvalid())
continue;
}
auto classDecl = method->getDeclContext()->getSelfClassDecl();
if (!classDecl)
continue; // error-recovery path, only
if (!classDecl->hasSuperclass())
continue;
// Look for a method that we have overridden in one of our
// superclasses.
// Note: This should be treated as a lookup for intra-module dependency
// purposes, but a subclass already depends on its superclasses and any
// extensions for many other reasons.
auto selector = method->getObjCSelector();
AbstractFunctionDecl *overriddenMethod
= lookupObjCMethodInClass(classDecl->getSuperclassDecl(),
selector,
method->isObjCInstanceMethod(),
isa<ConstructorDecl>(method),
Ctx.SourceMgr);
if (!overriddenMethod)
continue;
// Ignore stub implementations.
if (auto overriddenCtor = dyn_cast<ConstructorDecl>(overriddenMethod)) {
if (overriddenCtor->hasStubImplementation())
continue;
}
// Diagnose the override.
auto methodDiagInfo = getObjCMethodDiagInfo(method);
auto overriddenDiagInfo = getObjCMethodDiagInfo(overriddenMethod);
Ctx.Diags.diagnose(method, diag::objc_override_other,
methodDiagInfo.first,
methodDiagInfo.second,
overriddenDiagInfo.first,
overriddenDiagInfo.second,
selector,
overriddenMethod->getDeclContext()
->getDeclaredInterfaceType());
const ValueDecl *overriddenDecl = overriddenMethod;
if (overriddenMethod->isImplicit())
if (auto accessor = dyn_cast<AccessorDecl>(overriddenMethod))
overriddenDecl = accessor->getStorage();
Ctx.Diags.diagnose(overriddenDecl, diag::objc_declared_here,
overriddenDiagInfo.first, overriddenDiagInfo.second);
diagnosedAny = true;
}
return diagnosedAny;
}
/// Retrieve the source file for the given Objective-C member conflict.
static MutableArrayRef<AbstractFunctionDecl *>
getObjCMethodConflictDecls(const SourceFile::ObjCMethodConflict &conflict) {
ClassDecl *classDecl = std::get<0>(conflict);
ObjCSelector selector = std::get<1>(conflict);
bool isInstanceMethod = std::get<2>(conflict);
return classDecl->lookupDirect(selector, isInstanceMethod);
}
/// Given a set of conflicting Objective-C methods, remove any methods
/// that are legitimately overridden in Objective-C, i.e., because
/// they occur in different modules, one is defined in the class, and
/// the other is defined in an extension (category) thereof.
static void removeValidObjCConflictingMethods(
MutableArrayRef<AbstractFunctionDecl *> &methods) {
// Erase any invalid or stub declarations. We don't want to complain about
// them, because we might already have complained about
// redeclarations based on Swift matching.
auto newEnd = std::remove_if(methods.begin(), methods.end(),
[&](AbstractFunctionDecl *method) {
if (method->isInvalid())
return true;
if (auto ad = dyn_cast<AccessorDecl>(method)) {
return ad->getStorage()->isInvalid();
}
if (auto ctor
= dyn_cast<ConstructorDecl>(method)) {
if (ctor->hasStubImplementation())
return true;
return false;
}
return false;
});
methods = methods.slice(0, newEnd - methods.begin());
}
bool swift::diagnoseObjCMethodConflicts(SourceFile &sf) {
// If there were no conflicts, we're done.
if (sf.ObjCMethodConflicts.empty())
return false;
auto &Ctx = sf.getASTContext();
OrderDeclarations ordering(Ctx.SourceMgr);
// Sort the set of conflicts so we get a deterministic order for
// diagnostics. We use the first conflicting declaration in each set to
// perform the sort.
auto localConflicts = sf.ObjCMethodConflicts;
std::sort(localConflicts.begin(), localConflicts.end(),
[&](const SourceFile::ObjCMethodConflict &lhs,
const SourceFile::ObjCMethodConflict &rhs) {
return ordering(getObjCMethodConflictDecls(lhs)[1],
getObjCMethodConflictDecls(rhs)[1]);
});
// Diagnose each conflict.
bool anyConflicts = false;
for (const auto &conflict : localConflicts) {
ObjCSelector selector = std::get<1>(conflict);
auto methods = getObjCMethodConflictDecls(conflict);
// Prune out cases where it is acceptable to have a conflict.
removeValidObjCConflictingMethods(methods);
if (methods.size() < 2)
continue;
// Diagnose the conflict.
anyConflicts = true;
// If the first method is in an extension and the second is not, swap them
// so the primary diagnostic is on the extension method.
if (isa<ExtensionDecl>(methods[0]->getDeclContext()) &&
!isa<ExtensionDecl>(methods[1]->getDeclContext())) {
std::swap(methods[0], methods[1]);
// Within a source file, use our canonical ordering.
} else if (methods[0]->getParentSourceFile() ==
methods[1]->getParentSourceFile() &&
!ordering(methods[0], methods[1])) {
std::swap(methods[0], methods[1]);
}
// Otherwise, fall back to the order in which the declarations were type
// checked.
auto originalMethod = methods.front();
auto conflictingMethods = methods.slice(1);
auto origDiagInfo = getObjCMethodDiagInfo(originalMethod);
for (auto conflictingDecl : conflictingMethods) {
auto diagInfo = getObjCMethodDiagInfo(conflictingDecl);
const ValueDecl *originalDecl = originalMethod;
if (originalMethod->isImplicit())
if (auto accessor = dyn_cast<AccessorDecl>(originalMethod))
originalDecl = accessor->getStorage();
if (diagInfo == origDiagInfo) {
Ctx.Diags.diagnose(conflictingDecl, diag::objc_redecl_same,
diagInfo.first, diagInfo.second, selector);
Ctx.Diags.diagnose(originalDecl, diag::invalid_redecl_prev,
originalDecl->getBaseName());
} else {
Ctx.Diags.diagnose(conflictingDecl, diag::objc_redecl,
diagInfo.first,
diagInfo.second,
origDiagInfo.first,
origDiagInfo.second,
selector);
Ctx.Diags.diagnose(originalDecl, diag::objc_declared_here,
origDiagInfo.first, origDiagInfo.second);
}
}
}
return anyConflicts;
}
/// Retrieve the source location associated with this declaration
/// context.
static SourceLoc getDeclContextLoc(DeclContext *dc) {
if (auto ext = dyn_cast<ExtensionDecl>(dc))
return ext->getLoc();
return cast<NominalTypeDecl>(dc)->getLoc();
}
bool swift::diagnoseObjCUnsatisfiedOptReqConflicts(SourceFile &sf) {
// If there are no unsatisfied, optional @objc requirements, we're done.
if (sf.ObjCUnsatisfiedOptReqs.empty())
return false;
auto &Ctx = sf.getASTContext();
// Sort the set of local unsatisfied requirements, so we get a
// deterministic order for diagnostics.
auto &localReqs = sf.ObjCUnsatisfiedOptReqs;
std::sort(localReqs.begin(), localReqs.end(),
[&](const SourceFile::ObjCUnsatisfiedOptReq &lhs,
const SourceFile::ObjCUnsatisfiedOptReq &rhs) -> bool {
return Ctx.SourceMgr.isBeforeInBuffer(getDeclContextLoc(lhs.first),
getDeclContextLoc(rhs.first));
});
// Check each of the unsatisfied optional requirements.
bool anyDiagnosed = false;
for (const auto &unsatisfied : localReqs) {
// Check whether there is a conflict here.
ClassDecl *classDecl = unsatisfied.first->getSelfClassDecl();
auto req = unsatisfied.second;
auto selector = req->getObjCSelector();
bool isInstanceMethod = req->isInstanceMember();
// FIXME: Also look in superclasses?
auto conflicts = classDecl->lookupDirect(selector, isInstanceMethod);
if (conflicts.empty())
continue;
// Diagnose the conflict.
auto reqDiagInfo = getObjCMethodDiagInfo(unsatisfied.second);
auto conflictDiagInfo = getObjCMethodDiagInfo(conflicts[0]);
auto protocolName
= cast<ProtocolDecl>(req->getDeclContext())->getFullName();
Ctx.Diags.diagnose(conflicts[0],
diag::objc_optional_requirement_conflict,
conflictDiagInfo.first,
conflictDiagInfo.second,
reqDiagInfo.first,
reqDiagInfo.second,
selector,
protocolName);
// Fix the name of the witness, if we can.
if (req->getFullName() != conflicts[0]->getFullName() &&
req->getKind() == conflicts[0]->getKind() &&
isa<AccessorDecl>(req) == isa<AccessorDecl>(conflicts[0])) {
// They're of the same kind: fix the name.
unsigned kind;
if (isa<ConstructorDecl>(req))
kind = 1;
else if (auto accessor = dyn_cast<AccessorDecl>(req))
kind = isa<SubscriptDecl>(accessor->getStorage()) ? 3 : 2;
else if (isa<FuncDecl>(req))
kind = 0;
else {
llvm_unreachable("unhandled @objc declaration kind");
}
auto diag = Ctx.Diags.diagnose(conflicts[0],
diag::objc_optional_requirement_swift_rename,
kind, req->getFullName());
// Fix the Swift name.
fixDeclarationName(diag, conflicts[0], req->getFullName());
// Fix the '@objc' attribute, if needed.
if (!conflicts[0]->canInferObjCFromRequirement(req))
fixDeclarationObjCName(diag, conflicts[0],
conflicts[0]->getObjCRuntimeName(),
req->getObjCRuntimeName(),
/*ignoreImpliedName=*/true);
}
// @nonobjc will silence this warning.
bool hasExplicitObjCAttribute = false;
if (auto objcAttr = conflicts[0]->getAttrs().getAttribute<ObjCAttr>())
hasExplicitObjCAttribute = !objcAttr->isImplicit();
if (!hasExplicitObjCAttribute)
Ctx.Diags.diagnose(conflicts[0], diag::req_near_match_nonobjc, true)
.fixItInsert(
conflicts[0]->getAttributeInsertionLoc(/*forModifier=*/false),
"@nonobjc ");
Ctx.Diags.diagnose(getDeclContextLoc(unsatisfied.first),
diag::protocol_conformance_here,
true,
classDecl->getFullName(),
protocolName);
Ctx.Diags.diagnose(req, diag::kind_declname_declared_here,
DescriptiveDeclKind::Requirement, reqDiagInfo.second);
anyDiagnosed = true;
}
return anyDiagnosed;
}