| //===--- MiscDiagnostics.cpp - AST-Level Diagnostics ----------------------===// |
| // |
| // 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 AST-level diagnostics. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "MiscDiagnostics.h" |
| #include "TypeChecker.h" |
| #include "swift/AST/ASTWalker.h" |
| #include "swift/AST/NameLookup.h" |
| #include "swift/AST/Pattern.h" |
| #include "swift/Basic/Defer.h" |
| #include "swift/Basic/SourceManager.h" |
| #include "swift/Basic/StringExtras.h" |
| #include "swift/Parse/Lexer.h" |
| #include "swift/Parse/Parser.h" |
| #include "llvm/ADT/MapVector.h" |
| #include "llvm/ADT/StringSwitch.h" |
| #include "llvm/Support/SaveAndRestore.h" |
| using namespace swift; |
| |
| /// Return true if this expression is an implicit promotion from T to T?. |
| static Expr *isImplicitPromotionToOptional(Expr *E) { |
| if (E->isImplicit()) |
| if (auto IIOE = dyn_cast<InjectIntoOptionalExpr>( |
| E->getSemanticsProvidingExpr())) |
| return IIOE->getSubExpr(); |
| return nullptr; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Diagnose assigning variable to itself. |
| //===----------------------------------------------------------------------===// |
| |
| static Decl *findSimpleReferencedDecl(const Expr *E) { |
| if (auto *LE = dyn_cast<LoadExpr>(E)) |
| E = LE->getSubExpr(); |
| |
| if (auto *DRE = dyn_cast<DeclRefExpr>(E)) |
| return DRE->getDecl(); |
| |
| return nullptr; |
| } |
| |
| static std::pair<Decl *, Decl *> findReferencedDecl(const Expr *E) { |
| if (auto *LE = dyn_cast<LoadExpr>(E)) |
| E = LE->getSubExpr(); |
| |
| if (auto *D = findSimpleReferencedDecl(E)) |
| return std::make_pair(nullptr, D); |
| |
| if (auto *MRE = dyn_cast<MemberRefExpr>(E)) { |
| if (auto *BaseDecl = findSimpleReferencedDecl(MRE->getBase())) |
| return std::make_pair(BaseDecl, MRE->getMember().getDecl()); |
| } |
| |
| return std::make_pair(nullptr, nullptr); |
| } |
| |
| /// Diagnose assigning variable to itself. |
| static void diagSelfAssignment(TypeChecker &TC, const Expr *E) { |
| auto *AE = dyn_cast<AssignExpr>(E); |
| if (!AE) |
| return; |
| |
| auto LHSDecl = findReferencedDecl(AE->getDest()); |
| auto RHSDecl = findReferencedDecl(AE->getSrc()); |
| if (LHSDecl.second && LHSDecl == RHSDecl) { |
| TC.diagnose(AE->getLoc(), LHSDecl.first ? diag::self_assignment_prop |
| : diag::self_assignment_var) |
| .highlight(AE->getDest()->getSourceRange()) |
| .highlight(AE->getSrc()->getSourceRange()); |
| } |
| } |
| |
| |
| /// Diagnose syntactic restrictions of expressions. |
| /// |
| /// - Module values may only occur as part of qualification. |
| /// - Metatype names cannot generally be used as values: they need a "T.self" |
| /// qualification unless used in narrow case (e.g. T() for construction). |
| /// - '_' may only exist on the LHS of an assignment expression. |
| /// - warn_unqualified_access values must not be accessed except via qualified |
| /// lookup. |
| /// - Partial application of some decls isn't allowed due to implementation |
| /// limitations. |
| /// - "&" (aka InOutExpressions) may only exist directly in function call |
| /// argument lists. |
| /// - 'self.init' and 'super.init' cannot be wrapped in a larger expression |
| /// or statement. |
| /// - Warn about promotions to optional in specific syntactic forms. |
| /// - Error about collection literals that default to Any collections in |
| /// invalid positions. |
| /// |
| static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E, |
| const DeclContext *DC, |
| bool isExprStmt) { |
| class DiagnoseWalker : public ASTWalker { |
| SmallPtrSet<Expr*, 4> AlreadyDiagnosedMetatypes; |
| SmallPtrSet<DeclRefExpr*, 4> AlreadyDiagnosedNoEscapes; |
| |
| // Keep track of acceptable DiscardAssignmentExpr's. |
| SmallPtrSet<DiscardAssignmentExpr*, 2> CorrectDiscardAssignmentExprs; |
| |
| /// Keep track of InOutExprs |
| SmallPtrSet<InOutExpr*, 2> AcceptableInOutExprs; |
| |
| /// Keep track of the arguments to CallExprs. |
| SmallPtrSet<Expr *, 2> CallArgs; |
| |
| bool IsExprStmt; |
| |
| public: |
| TypeChecker &TC; |
| const DeclContext *DC; |
| |
| DiagnoseWalker(TypeChecker &TC, const DeclContext *DC, bool isExprStmt) |
| : IsExprStmt(isExprStmt), TC(TC), DC(DC) {} |
| |
| // Selector for the partial_application_of_function_invalid diagnostic |
| // message. |
| struct PartialApplication { |
| unsigned level : 29; |
| enum : unsigned { |
| Function, |
| MutatingMethod, |
| SuperInit, |
| SelfInit, |
| }; |
| unsigned kind : 3; |
| }; |
| |
| // Partial applications of functions that are not permitted. This is |
| // tracked in post-order and unravelled as subsequent applications complete |
| // the call (or not). |
| llvm::SmallDenseMap<Expr*, PartialApplication,2> InvalidPartialApplications; |
| |
| ~DiagnoseWalker() { |
| for (auto &unapplied : InvalidPartialApplications) { |
| unsigned kind = unapplied.second.kind; |
| TC.diagnose(unapplied.first->getLoc(), |
| diag::partial_application_of_function_invalid, |
| kind); |
| } |
| } |
| |
| /// If this is an application of a function that cannot be partially |
| /// applied, arrange for us to check that it gets fully applied. |
| void recordUnsupportedPartialApply(ApplyExpr *expr, Expr *fnExpr) { |
| |
| if (isa<OtherConstructorDeclRefExpr>(fnExpr)) { |
| auto kind = expr->getArg()->isSuperExpr() |
| ? PartialApplication::SuperInit |
| : PartialApplication::SelfInit; |
| |
| // Partial applications of delegated initializers aren't allowed, and |
| // don't really make sense to begin with. |
| InvalidPartialApplications.insert({ expr, {1, kind} }); |
| return; |
| } |
| |
| auto fnDeclRef = dyn_cast<DeclRefExpr>(fnExpr); |
| if (!fnDeclRef) |
| return; |
| |
| auto fn = dyn_cast<FuncDecl>(fnDeclRef->getDecl()); |
| if (!fn) |
| return; |
| |
| unsigned kind = |
| fn->isInstanceMember() ? PartialApplication::MutatingMethod |
| : PartialApplication::Function; |
| |
| // Functions with inout parameters cannot be partially applied. |
| if (expr->getArg()->getType()->hasInOut()) { |
| // We need to apply all argument clauses. |
| InvalidPartialApplications.insert({ |
| fnExpr, {fn->getNumParameterLists(), kind} |
| }); |
| } |
| } |
| |
| /// This method is called in post-order over the AST to validate that |
| /// methods are fully applied when they can't support partial application. |
| void checkInvalidPartialApplication(Expr *E) { |
| if (auto AE = dyn_cast<ApplyExpr>(E)) { |
| Expr *fnExpr = AE->getSemanticFn(); |
| if (auto forceExpr = dyn_cast<ForceValueExpr>(fnExpr)) |
| fnExpr = forceExpr->getSubExpr()->getSemanticsProvidingExpr(); |
| if (auto dotSyntaxExpr = dyn_cast<DotSyntaxBaseIgnoredExpr>(fnExpr)) |
| fnExpr = dotSyntaxExpr->getRHS(); |
| |
| // Check to see if this is a potentially unsupported partial |
| // application. |
| recordUnsupportedPartialApply(AE, fnExpr); |
| |
| // If this is adding a level to an active partial application, advance |
| // it to the next level. |
| auto foundApplication = InvalidPartialApplications.find(fnExpr); |
| if (foundApplication == InvalidPartialApplications.end()) |
| return; |
| |
| unsigned level = foundApplication->second.level; |
| auto kind = foundApplication->second.kind; |
| assert(level > 0); |
| InvalidPartialApplications.erase(foundApplication); |
| if (level > 1) { |
| // We have remaining argument clauses. |
| InvalidPartialApplications.insert({ AE, {level - 1, kind} }); |
| } |
| return; |
| } |
| |
| } |
| |
| // Not interested in going outside a basic expression. |
| std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override { |
| return { false, S }; |
| } |
| std::pair<bool, Pattern*> walkToPatternPre(Pattern *P) override { |
| return { false, P }; |
| } |
| bool walkToDeclPre(Decl *D) override { return false; } |
| bool walkToTypeReprPre(TypeRepr *T) override { return true; } |
| |
| std::pair<bool, Expr *> walkToExprPre(Expr *E) override { |
| // See through implicit conversions of the expression. We want to be able |
| // to associate the parent of this expression with the ultimate callee. |
| auto Base = E; |
| while (auto Conv = dyn_cast<ImplicitConversionExpr>(Base)) |
| Base = Conv->getSubExpr(); |
| |
| // Record call arguments. |
| if (auto Call = dyn_cast<CallExpr>(Base)) |
| CallArgs.insert(Call->getArg()); |
| |
| if (auto *DRE = dyn_cast<DeclRefExpr>(Base)) { |
| // Verify metatype uses. |
| if (isa<TypeDecl>(DRE->getDecl())) { |
| if (isa<ModuleDecl>(DRE->getDecl())) |
| checkUseOfModule(DRE); |
| else |
| checkUseOfMetaTypeName(Base); |
| } |
| |
| // Verify noescape parameter uses. |
| checkNoEscapeParameterUse(DRE, nullptr); |
| |
| // Verify warn_unqualified_access uses. |
| checkUnqualifiedAccessUse(DRE); |
| } |
| if (auto *MRE = dyn_cast<MemberRefExpr>(Base)) { |
| if (isa<TypeDecl>(MRE->getMember().getDecl())) |
| checkUseOfMetaTypeName(Base); |
| |
| // Check whether there are needless words that could be omitted. |
| TC.checkOmitNeedlessWords(MRE); |
| } |
| if (isa<TypeExpr>(Base)) |
| checkUseOfMetaTypeName(Base); |
| |
| if (auto *SE = dyn_cast<SubscriptExpr>(E)) { |
| // Implicit InOutExpr's are allowed in the base of a subscript expr. |
| if (auto *IOE = dyn_cast<InOutExpr>(SE->getBase())) |
| if (IOE->isImplicit()) |
| AcceptableInOutExprs.insert(IOE); |
| } |
| |
| |
| // Check function calls, looking through implicit conversions on the |
| // function and inspecting the arguments directly. |
| if (auto *Call = dyn_cast<ApplyExpr>(E)) { |
| // Warn about surprising implicit optional promotions. |
| checkOptionalPromotions(Call); |
| |
| // Check for tuple splat. |
| checkTupleSplat(Call); |
| |
| // Check the callee, looking through implicit conversions. |
| auto Base = Call->getFn(); |
| while (auto Conv = dyn_cast<ImplicitConversionExpr>(Base)) |
| Base = Conv->getSubExpr(); |
| if (auto *DRE = dyn_cast<DeclRefExpr>(Base)) |
| checkNoEscapeParameterUse(DRE, Call); |
| |
| auto *Arg = Call->getArg(); |
| |
| // The argument could be shuffled if it includes default arguments, |
| // label differences, or other exciting things like that. |
| if (auto *TSE = dyn_cast<TupleShuffleExpr>(Arg)) |
| Arg = TSE->getSubExpr(); |
| |
| // The argument is either a ParenExpr or TupleExpr. |
| ArrayRef<Expr*> arguments; |
| if (auto *TE = dyn_cast<TupleExpr>(Arg)) |
| arguments = TE->getElements(); |
| else if (auto *PE = dyn_cast<ParenExpr>(Arg)) |
| arguments = PE->getSubExpr(); |
| else |
| arguments = Call->getArg(); |
| |
| // Check each argument. |
| for (auto arg : arguments) { |
| // InOutExpr's are allowed in argument lists directly. |
| if (auto *IOE = dyn_cast<InOutExpr>(arg)) { |
| if (isa<CallExpr>(Call)) |
| AcceptableInOutExprs.insert(IOE); |
| } |
| // InOutExprs can be wrapped in some implicit casts. |
| Expr *unwrapped = arg; |
| if (auto *IIO = dyn_cast<InjectIntoOptionalExpr>(arg)) |
| unwrapped = IIO->getSubExpr(); |
| if (auto *ICO = dyn_cast<ImplicitConversionExpr>(unwrapped)) { |
| if (isa<InOutToPointerExpr>(ICO) || |
| isa<ArrayToPointerExpr>(ICO) || |
| isa<ErasureExpr>(ICO)) |
| if (auto *IOE = dyn_cast<InOutExpr>(ICO->getSubExpr())) |
| AcceptableInOutExprs.insert(IOE); |
| } |
| |
| while (1) { |
| if (auto conv = dyn_cast<ImplicitConversionExpr>(arg)) |
| arg = conv->getSubExpr(); |
| else if (auto *PE = dyn_cast<ParenExpr>(arg)) |
| arg = PE->getSubExpr(); |
| else |
| break; |
| } |
| |
| if (auto *DRE = dyn_cast<DeclRefExpr>(arg)) |
| checkNoEscapeParameterUse(DRE, Call); |
| } |
| |
| // Check whether there are needless words that could be omitted. |
| TC.checkOmitNeedlessWords(Call); |
| } |
| |
| // If we have an assignment expression, scout ahead for acceptable _'s. |
| if (auto *AE = dyn_cast<AssignExpr>(E)) |
| markAcceptableDiscardExprs(AE->getDest()); |
| |
| /// Diagnose a '_' that isn't on the immediate LHS of an assignment. |
| if (auto *DAE = dyn_cast<DiscardAssignmentExpr>(E)) { |
| if (!CorrectDiscardAssignmentExprs.count(DAE) && |
| !DAE->getType()->is<ErrorType>()) |
| TC.diagnose(DAE->getLoc(), diag::discard_expr_outside_of_assignment); |
| } |
| |
| // Diagnose an '&' that isn't in an argument lists. |
| if (auto *IOE = dyn_cast<InOutExpr>(E)) { |
| if (!IOE->isImplicit() && !AcceptableInOutExprs.count(IOE) && |
| !IOE->getType()->is<ErrorType>()) |
| TC.diagnose(IOE->getLoc(), diag::inout_expr_outside_of_call) |
| .highlight(IOE->getSubExpr()->getSourceRange()); |
| } |
| |
| // Diagnose 'self.init' or 'super.init' nested in another expression. |
| if (auto *rebindSelfExpr = dyn_cast<RebindSelfInConstructorExpr>(E)) { |
| if (!Parent.isNull() || !IsExprStmt) { |
| bool isChainToSuper; |
| (void)rebindSelfExpr->getCalledConstructor(isChainToSuper); |
| TC.diagnose(E->getLoc(), diag::init_delegation_nested, |
| isChainToSuper, !IsExprStmt); |
| } |
| } |
| |
| return { true, E }; |
| } |
| |
| Expr *walkToExprPost(Expr *E) override { |
| checkInvalidPartialApplication(E); |
| return E; |
| } |
| |
| /// We have a collection literal with a defaulted type, e.g. of [Any]. Emit |
| /// an error if it was inferred to this type in an invalid context, which is |
| /// one in which the parent expression is not itself a collection literal. |
| void checkTypeDefaultedCollectionExpr(CollectionExpr *c) { |
| // If the parent is a non-expression, or is not itself a literal, then |
| // produce an error with a fixit to add the type as an explicit |
| // annotation. |
| if (c->getNumElements() == 0) |
| TC.diagnose(c->getLoc(), diag::collection_literal_empty) |
| .highlight(c->getSourceRange()); |
| else { |
| TC.diagnose(c->getLoc(), diag::collection_literal_heterogenous, |
| c->getType()) |
| .highlight(c->getSourceRange()) |
| .fixItInsertAfter(c->getEndLoc(), " as " + c->getType()->getString()); |
| } |
| } |
| |
| |
| /// Scout out the specified destination of an AssignExpr to recursively |
| /// identify DiscardAssignmentExpr in legal places. We can only allow them |
| /// in simple pattern-like expressions, so we reject anything complex here. |
| void markAcceptableDiscardExprs(Expr *E) { |
| if (!E) return; |
| |
| if (auto *PE = dyn_cast<ParenExpr>(E)) |
| return markAcceptableDiscardExprs(PE->getSubExpr()); |
| if (auto *TE = dyn_cast<TupleExpr>(E)) { |
| for (auto &elt : TE->getElements()) |
| markAcceptableDiscardExprs(elt); |
| return; |
| } |
| if (auto *DAE = dyn_cast<DiscardAssignmentExpr>(E)) |
| CorrectDiscardAssignmentExprs.insert(DAE); |
| |
| // Otherwise, we can't support this. |
| } |
| |
| |
| /// Warn on tuple splat, which is deprecated. For example: |
| /// |
| /// func f(a : Int, _ b : Int) {} |
| /// let x = (1,2) |
| /// f(x) |
| /// |
| void checkTupleSplat(ApplyExpr *Call) { |
| auto FT = Call->getFn()->getType()->getAs<AnyFunctionType>(); |
| // If this wasn't type checked correctly then don't worry about it. |
| if (!FT) return; |
| |
| // If we're passing multiple parameters, then this isn't a tuple splat. |
| auto arg = Call->getArg()->getSemanticsProvidingExpr(); |
| if (isa<TupleExpr>(arg) || isa<TupleShuffleExpr>(arg)) |
| return; |
| |
| // We care about whether the parameter list of the callee syntactically |
| // has more than one argument. It has to *syntactically* have a tuple |
| // type as its argument. A ParenType wrapping a TupleType is a single |
| // parameter. |
| if (isa<TupleType>(FT->getInput().getPointer())) { |
| auto TT = FT->getInput()->getAs<TupleType>(); |
| if (TT->getNumElements() > 1) { |
| TC.diagnose(Call->getLoc(), diag::tuple_splat_use, |
| TT->getNumElements()) |
| .highlight(Call->getArg()->getSourceRange()); |
| } |
| } |
| } |
| |
| void checkUseOfModule(DeclRefExpr *E) { |
| // Allow module values as a part of: |
| // - ignored base expressions; |
| // - expressions that failed to type check. |
| if (auto *ParentExpr = Parent.getAsExpr()) { |
| if (isa<DotSyntaxBaseIgnoredExpr>(ParentExpr) || |
| isa<UnresolvedDotExpr>(ParentExpr)) |
| return; |
| } |
| |
| TC.diagnose(E->getStartLoc(), diag::value_of_module_type); |
| } |
| |
| /// The DRE argument is a reference to a noescape parameter. Verify that |
| /// its uses are ok. |
| void checkNoEscapeParameterUse(DeclRefExpr *DRE, Expr *ParentExpr=nullptr) { |
| // This only cares about declarations of noescape function type. |
| auto AFT = DRE->getDecl()->getType()->getAs<AnyFunctionType>(); |
| if (!AFT || !AFT->isNoEscape()) |
| return; |
| |
| // Only diagnose this once. If we check and accept this use higher up in |
| // the AST, don't recheck here. |
| if (!AlreadyDiagnosedNoEscapes.insert(DRE).second) |
| return; |
| |
| // The only valid use of the noescape parameter is an immediate call, |
| // either as the callee or as an argument (in which case, the typechecker |
| // validates that the noescape bit didn't get stripped off). |
| if (ParentExpr && isa<ApplyExpr>(ParentExpr)) // param() |
| return; |
| |
| TC.diagnose(DRE->getStartLoc(), diag::invalid_noescape_use, |
| DRE->getDecl()->getName(), isa<ParamDecl>(DRE->getDecl())); |
| |
| // If we're a parameter, emit a helpful fixit to add @escaping |
| auto paramDecl = dyn_cast<ParamDecl>(DRE->getDecl()); |
| auto isAutoClosure = AFT->isAutoClosure(); |
| if (paramDecl && !isAutoClosure) { |
| TC.diagnose(paramDecl->getStartLoc(), diag::noescape_parameter, |
| paramDecl->getName()) |
| .fixItInsert(paramDecl->getTypeLoc().getSourceRange().Start, |
| "@escaping "); |
| } else if (isAutoClosure) |
| // TODO: add in a fixit for autoclosure |
| TC.diagnose(DRE->getDecl()->getLoc(), diag::noescape_autoclosure, |
| DRE->getDecl()->getName()); |
| } |
| |
| // Diagnose metatype values that don't appear as part of a property, |
| // method, or constructor reference. |
| void checkUseOfMetaTypeName(Expr *E) { |
| // If we've already checked this at a higher level, we're done. |
| if (!AlreadyDiagnosedMetatypes.insert(E).second) |
| return; |
| |
| // Allow references to types as a part of: |
| // - member references T.foo, T.Type, T.self, etc. |
| // - constructor calls T() |
| if (auto *ParentExpr = Parent.getAsExpr()) { |
| // This is the white-list of accepted syntactic forms. |
| if (isa<ErrorExpr>(ParentExpr) || |
| isa<DotSelfExpr>(ParentExpr) || // T.self |
| isa<CallExpr>(ParentExpr) || // T() |
| isa<MemberRefExpr>(ParentExpr) || // T.foo |
| isa<UnresolvedMemberExpr>(ParentExpr) || |
| isa<SelfApplyExpr>(ParentExpr) || // T.foo() T() |
| isa<UnresolvedDotExpr>(ParentExpr) || |
| isa<DotSyntaxBaseIgnoredExpr>(ParentExpr) || |
| isa<UnresolvedSpecializeExpr>(ParentExpr) || |
| isa<OpenExistentialExpr>(ParentExpr)) { |
| return; |
| } |
| |
| // Note: as a specific hack, produce a warning + Fix-It for |
| // the missing ".self" as the subexpression of a parenthesized |
| // expression, which is a historical bug. |
| if (isa<ParenExpr>(ParentExpr) && CallArgs.count(ParentExpr) > 0) { |
| TC.diagnose(E->getEndLoc(), diag::warn_value_of_metatype_missing_self, |
| E->getType()->getRValueInstanceType()) |
| .fixItInsertAfter(E->getEndLoc(), ".self"); |
| return; |
| } |
| } |
| |
| // Is this a protocol metatype? |
| |
| TC.diagnose(E->getStartLoc(), diag::value_of_metatype_type); |
| |
| // Add fix-t to insert '()', unless this is a protocol metatype. |
| bool isProtocolMetatype = false; |
| if (auto metaTy = E->getType()->getAs<MetatypeType>()) |
| isProtocolMetatype = metaTy->getInstanceType()->is<ProtocolType>(); |
| if (!isProtocolMetatype) { |
| TC.diagnose(E->getEndLoc(), diag::add_parens_to_type) |
| .fixItInsertAfter(E->getEndLoc(), "()"); |
| } |
| |
| // Add fix-it to insert ".self". |
| TC.diagnose(E->getEndLoc(), diag::add_self_to_type) |
| .fixItInsertAfter(E->getEndLoc(), ".self"); |
| } |
| |
| void checkUnqualifiedAccessUse(const DeclRefExpr *DRE) { |
| const Decl *D = DRE->getDecl(); |
| if (!D->getAttrs().hasAttribute<WarnUnqualifiedAccessAttr>()) |
| return; |
| |
| if (auto *parentExpr = Parent.getAsExpr()) { |
| if (auto *ignoredBase = dyn_cast<DotSyntaxBaseIgnoredExpr>(parentExpr)){ |
| if (!ignoredBase->isImplicit()) |
| return; |
| } |
| if (auto *calledBase = dyn_cast<DotSyntaxCallExpr>(parentExpr)) { |
| if (!calledBase->isImplicit()) |
| return; |
| } |
| } |
| |
| const auto *VD = cast<ValueDecl>(D); |
| const TypeDecl *declParent = |
| VD->getDeclContext()->getAsNominalTypeOrNominalTypeExtensionContext(); |
| if (!declParent) { |
| assert(VD->getDeclContext()->isModuleScopeContext()); |
| declParent = VD->getDeclContext()->getParentModule(); |
| } |
| |
| TC.diagnose(DRE->getLoc(), diag::warn_unqualified_access, |
| VD->getName(), VD->getDescriptiveKind(), |
| declParent->getDescriptiveKind(), declParent->getFullName()); |
| TC.diagnose(VD, diag::decl_declared_here, VD->getFullName()); |
| |
| if (VD->getDeclContext()->isTypeContext()) { |
| TC.diagnose(DRE->getLoc(), diag::fix_unqualified_access_member) |
| .fixItInsert(DRE->getStartLoc(), "self."); |
| } |
| |
| DeclContext *topLevelContext = DC->getModuleScopeContext(); |
| UnqualifiedLookup lookup(VD->getBaseName(), topLevelContext, &TC, |
| /*knownPrivate*/true); |
| |
| // Group results by module. Pick an arbitrary result from each module. |
| llvm::SmallDenseMap<const ModuleDecl*,const ValueDecl*,4> resultsByModule; |
| for (auto &result : lookup.Results) { |
| const ValueDecl *value = result.getValueDecl(); |
| resultsByModule.insert(std::make_pair(value->getModuleContext(),value)); |
| } |
| |
| // Sort by module name. |
| using ModuleValuePair = std::pair<const ModuleDecl *, const ValueDecl *>; |
| SmallVector<ModuleValuePair, 4> sortedResults{ |
| resultsByModule.begin(), resultsByModule.end() |
| }; |
| llvm::array_pod_sort(sortedResults.begin(), sortedResults.end(), |
| [](const ModuleValuePair *lhs, |
| const ModuleValuePair *rhs) { |
| return lhs->first->getName().compare(rhs->first->getName()); |
| }); |
| |
| auto topLevelDiag = diag::fix_unqualified_access_top_level; |
| if (sortedResults.size() > 1) |
| topLevelDiag = diag::fix_unqualified_access_top_level_multi; |
| |
| for (const ModuleValuePair &pair : sortedResults) { |
| DescriptiveDeclKind k = pair.second->getDescriptiveKind(); |
| |
| SmallString<32> namePlusDot = pair.first->getName().str(); |
| namePlusDot.push_back('.'); |
| |
| TC.diagnose(DRE->getLoc(), topLevelDiag, |
| namePlusDot, k, pair.first->getName()) |
| .fixItInsert(DRE->getStartLoc(), namePlusDot); |
| } |
| } |
| |
| /// Return true if this is 'nil' type checked as an Optional. This looks |
| /// like this: |
| /// (call_expr implicit type='Int?' |
| /// (constructor_ref_call_expr implicit |
| /// (declref_expr implicit decl=Optional.init(nilLiteral:) |
| static bool isTypeCheckedOptionalNil(Expr *E) { |
| auto CE = dyn_cast<CallExpr>(E->getSemanticsProvidingExpr()); |
| if (!CE || !CE->isImplicit()) |
| return false; |
| |
| auto CRCE = dyn_cast<ConstructorRefCallExpr>(CE->getSemanticFn()); |
| if (!CRCE || !CRCE->isImplicit()) return false; |
| |
| auto DRE = dyn_cast<DeclRefExpr>(CRCE->getSemanticFn()); |
| |
| SmallString<32> NameBuffer; |
| auto name = DRE->getDecl()->getFullName().getString(NameBuffer); |
| return name == "init(nilLiteral:)"; |
| } |
| |
| |
| /// Warn about surprising implicit optional promotions involving operands to |
| /// calls. Specifically, we warn about these expressions when the 'x' |
| /// operand is implicitly promoted to optional: |
| /// |
| /// x ?? y |
| /// x == nil // also != |
| /// |
| void checkOptionalPromotions(ApplyExpr *call) { |
| auto DRE = dyn_cast<DeclRefExpr>(call->getSemanticFn()); |
| auto args = dyn_cast<TupleExpr>(call->getArg()); |
| if (!DRE || !DRE->getDecl()->isOperator() || |
| !args || args->getNumElements() != 2) |
| return; |
| |
| auto lhs = args->getElement(0); |
| auto rhs = args->getElement(1); |
| auto calleeName = DRE->getDecl()->getName().str(); |
| |
| Expr *subExpr = nullptr; |
| if (calleeName == "??" && |
| (subExpr = isImplicitPromotionToOptional(lhs))) { |
| TC.diagnose(DRE->getLoc(), diag::use_of_qq_on_non_optional_value, |
| subExpr->getType()) |
| .highlight(lhs->getSourceRange()) |
| .fixItRemove(SourceRange(DRE->getLoc(), rhs->getEndLoc())); |
| return; |
| } |
| |
| if (calleeName == "==" || calleeName == "!=" || |
| calleeName == "===" || calleeName == "!==") { |
| if (((subExpr = isImplicitPromotionToOptional(lhs)) && |
| isTypeCheckedOptionalNil(rhs)) || |
| (isTypeCheckedOptionalNil(lhs) && |
| (subExpr = isImplicitPromotionToOptional(rhs)))) { |
| bool isTrue = calleeName == "!=" || calleeName == "!=="; |
| |
| TC.diagnose(DRE->getLoc(), diag::nonoptional_compare_to_nil, |
| subExpr->getType(), isTrue) |
| .highlight(lhs->getSourceRange()) |
| .highlight(rhs->getSourceRange()); |
| return; |
| } |
| } |
| } |
| }; |
| |
| DiagnoseWalker Walker(TC, DC, isExprStmt); |
| const_cast<Expr *>(E)->walk(Walker); |
| |
| // Diagnose uses of collection literals with defaulted types at the top |
| // level. |
| if (auto collection |
| = dyn_cast<CollectionExpr>(E->getSemanticsProvidingExpr())) { |
| if (collection->isTypeDefaulted()) { |
| Walker.checkTypeDefaultedCollectionExpr( |
| const_cast<CollectionExpr *>(collection)); |
| } |
| } |
| } |
| |
| |
| /// Diagnose recursive use of properties within their own accessors |
| static void diagRecursivePropertyAccess(TypeChecker &TC, const Expr *E, |
| const DeclContext *DC) { |
| auto fn = dyn_cast<FuncDecl>(DC); |
| if (!fn || !fn->isAccessor()) |
| return; |
| |
| auto var = dyn_cast<VarDecl>(fn->getAccessorStorageDecl()); |
| if (!var) // Ignore subscripts |
| return; |
| |
| class DiagnoseWalker : public ASTWalker { |
| TypeChecker &TC; |
| VarDecl *Var; |
| const FuncDecl *Accessor; |
| |
| public: |
| explicit DiagnoseWalker(TypeChecker &TC, VarDecl *var, |
| const FuncDecl *Accessor) |
| : TC(TC), Var(var), Accessor(Accessor) {} |
| |
| std::pair<bool, Expr *> walkToExprPre(Expr *E) override { |
| Expr *subExpr; |
| bool isStore = false; |
| |
| if (auto *AE = dyn_cast<AssignExpr>(E)) { |
| subExpr = AE->getDest(); |
| |
| // If we couldn't flatten this expression, don't explode. |
| if (!subExpr) |
| return { true, E }; |
| |
| isStore = true; |
| } else if (auto *IOE = dyn_cast<InOutExpr>(E)) { |
| subExpr = IOE->getSubExpr(); |
| isStore = true; |
| } else { |
| subExpr = E; |
| } |
| |
| if (auto *BOE = dyn_cast<BindOptionalExpr>(subExpr)) |
| subExpr = BOE; |
| |
| if (auto *DRE = dyn_cast<DeclRefExpr>(subExpr)) { |
| if (DRE->getDecl() == Var) { |
| // Handle local and top-level computed variables. |
| if (DRE->getAccessSemantics() != AccessSemantics::DirectToStorage) { |
| bool shouldDiagnose = false; |
| // Warn about any property access in the getter. |
| if (Accessor->isGetter()) |
| shouldDiagnose = !isStore; |
| // Warn about stores in the setter, but allow loads. |
| if (Accessor->isSetter()) |
| shouldDiagnose = isStore; |
| |
| // But silence the warning if the base was explicitly qualified. |
| if (dyn_cast_or_null<DotSyntaxBaseIgnoredExpr>(Parent.getAsExpr())) |
| shouldDiagnose = false; |
| |
| if (shouldDiagnose) { |
| TC.diagnose(subExpr->getLoc(), diag::recursive_accessor_reference, |
| Var->getName(), Accessor->isSetter()); |
| } |
| } |
| |
| // If this is a direct store in a "willSet", we reject this because |
| // it is about to get overwritten. |
| if (isStore && |
| DRE->getAccessSemantics() == AccessSemantics::DirectToStorage && |
| Accessor->getAccessorKind() == AccessorKind::IsWillSet) { |
| TC.diagnose(E->getLoc(), diag::store_in_willset, Var->getName()); |
| } |
| } |
| |
| |
| } else if (auto *MRE = dyn_cast<MemberRefExpr>(subExpr)) { |
| // Handle instance and type computed variables. |
| // Find MemberRefExprs that have an implicit "self" base. |
| if (MRE->getMember().getDecl() == Var && |
| isa<DeclRefExpr>(MRE->getBase()) && |
| MRE->getBase()->isImplicit()) { |
| |
| if (MRE->getAccessSemantics() != AccessSemantics::DirectToStorage) { |
| bool shouldDiagnose = false; |
| // Warn about any property access in the getter. |
| if (Accessor->isGetter()) |
| shouldDiagnose = !isStore; |
| // Warn about stores in the setter, but allow loads. |
| if (Accessor->isSetter()) |
| shouldDiagnose = isStore; |
| |
| if (shouldDiagnose) { |
| TC.diagnose(subExpr->getLoc(), diag::recursive_accessor_reference, |
| Var->getName(), Accessor->isSetter()); |
| TC.diagnose(subExpr->getLoc(), |
| diag::recursive_accessor_reference_silence) |
| .fixItInsert(subExpr->getStartLoc(), "self."); |
| } |
| } |
| |
| // If this is a direct store in a "willSet", we reject this because |
| // it is about to get overwritten. |
| if (isStore && |
| MRE->getAccessSemantics() == AccessSemantics::DirectToStorage && |
| Accessor->getAccessorKind() == AccessorKind::IsWillSet) { |
| TC.diagnose(subExpr->getLoc(), diag::store_in_willset, |
| Var->getName()); |
| } |
| } |
| |
| } |
| |
| return { true, E }; |
| } |
| }; |
| |
| DiagnoseWalker walker(TC, var, fn); |
| const_cast<Expr *>(E)->walk(walker); |
| } |
| |
| /// Look for any property references in closures that lack a "self." qualifier. |
| /// Within a closure, we require that the source code contain "self." explicitly |
| /// because 'self' is captured, not the property value. This is a common source |
| /// of confusion, so we force an explicit self. |
| static void diagnoseImplicitSelfUseInClosure(TypeChecker &TC, const Expr *E, |
| const DeclContext *DC) { |
| class DiagnoseWalker : public ASTWalker { |
| TypeChecker &TC; |
| unsigned InClosure; |
| public: |
| explicit DiagnoseWalker(TypeChecker &TC, bool isAlreadyInClosure) |
| : TC(TC), InClosure(isAlreadyInClosure) {} |
| |
| /// Return true if this is an implicit reference to self. |
| static bool isImplicitSelfUse(Expr *E) { |
| auto *DRE = dyn_cast<DeclRefExpr>(E); |
| return DRE && DRE->isImplicit() && isa<VarDecl>(DRE->getDecl()) && |
| cast<VarDecl>(DRE->getDecl())->isSelfParameter() && |
| // Metatype self captures don't extend the lifetime of an object. |
| !DRE->getType()->is<MetatypeType>(); |
| } |
| |
| /// Return true if this is a closure expression that will require "self." |
| /// qualification of member references. |
| static bool isClosureRequiringSelfQualification( |
| const AbstractClosureExpr *CE) { |
| // If the closure's type was inferred to be noescape, then it doesn't |
| // need qualification. |
| return !AnyFunctionRef(const_cast<AbstractClosureExpr *>(CE)) |
| .isKnownNoEscape(); |
| } |
| |
| |
| // Don't walk into nested decls. |
| bool walkToDeclPre(Decl *D) override { |
| return false; |
| } |
| |
| std::pair<bool, Expr *> walkToExprPre(Expr *E) override { |
| if (auto *CE = dyn_cast<AbstractClosureExpr>(E)) { |
| if (!CE->hasSingleExpressionBody()) |
| return { false, E }; |
| |
| // If this is a potentially-escaping closure expression, start looking |
| // for references to self if we aren't already. |
| if (isClosureRequiringSelfQualification(CE)) |
| ++InClosure; |
| } |
| |
| |
| // If we aren't in a closure, no diagnostics will be produced. |
| if (!InClosure) |
| return { true, E }; |
| |
| // If we see a property reference with an implicit base from within a |
| // closure, then reject it as requiring an explicit "self." qualifier. We |
| // do this in explicit closures, not autoclosures, because otherwise the |
| // transparence of autoclosures is lost. |
| if (auto *MRE = dyn_cast<MemberRefExpr>(E)) |
| if (isImplicitSelfUse(MRE->getBase())) { |
| TC.diagnose(MRE->getLoc(), |
| diag::property_use_in_closure_without_explicit_self, |
| MRE->getMember().getDecl()->getName()) |
| .fixItInsert(MRE->getLoc(), "self."); |
| return { false, E }; |
| } |
| |
| // Handle method calls with a specific diagnostic + fixit. |
| if (auto *DSCE = dyn_cast<DotSyntaxCallExpr>(E)) |
| if (isImplicitSelfUse(DSCE->getBase()) && |
| isa<DeclRefExpr>(DSCE->getFn())) { |
| auto MethodExpr = cast<DeclRefExpr>(DSCE->getFn()); |
| TC.diagnose(DSCE->getLoc(), |
| diag::method_call_in_closure_without_explicit_self, |
| MethodExpr->getDecl()->getName()) |
| .fixItInsert(DSCE->getLoc(), "self."); |
| return { false, E }; |
| } |
| |
| // Catch any other implicit uses of self with a generic diagnostic. |
| if (isImplicitSelfUse(E)) |
| TC.diagnose(E->getLoc(), diag::implicit_use_of_self_in_closure); |
| |
| return { true, E }; |
| } |
| |
| Expr *walkToExprPost(Expr *E) override { |
| if (auto *CE = dyn_cast<AbstractClosureExpr>(E)) { |
| if (isClosureRequiringSelfQualification(CE)) { |
| assert(InClosure); |
| --InClosure; |
| } |
| } |
| |
| return E; |
| } |
| }; |
| |
| bool isAlreadyInClosure = false; |
| if (DC->isLocalContext()) { |
| while (DC->getParent()->isLocalContext() && !isAlreadyInClosure) { |
| if (auto *closure = dyn_cast<AbstractClosureExpr>(DC)) |
| if (DiagnoseWalker::isClosureRequiringSelfQualification(closure)) |
| isAlreadyInClosure = true; |
| DC = DC->getParent(); |
| } |
| } |
| const_cast<Expr *>(E)->walk(DiagnoseWalker(TC, isAlreadyInClosure)); |
| } |
| |
| /// Diagnose an argument labeling issue, returning true if we successfully |
| /// diagnosed the issue. |
| bool swift::diagnoseArgumentLabelError(TypeChecker &TC, const Expr *expr, |
| ArrayRef<Identifier> newNames, |
| bool isSubscript, |
| InFlightDiagnostic *existingDiag) { |
| Optional<InFlightDiagnostic> diagOpt; |
| auto getDiag = [&]() -> InFlightDiagnostic & { |
| if (existingDiag) |
| return *existingDiag; |
| return *diagOpt; |
| }; |
| |
| auto tuple = dyn_cast<TupleExpr>(expr); |
| if (!tuple) { |
| llvm::SmallString<16> str; |
| // If the diagnostic is local, flush it before returning. |
| // This makes sure it's emitted before 'str' is destroyed. |
| SWIFT_DEFER { diagOpt.reset(); }; |
| |
| if (newNames[0].empty()) { |
| // This is probably a conversion from a value of labeled tuple type to |
| // a scalar. |
| // FIXME: We want this issue to disappear completely when single-element |
| // labelled tuples go away. |
| if (auto tupleTy = expr->getType()->getRValueType()->getAs<TupleType>()) { |
| int scalarFieldIdx = tupleTy->getElementForScalarInit(); |
| if (scalarFieldIdx >= 0) { |
| auto &field = tupleTy->getElement(scalarFieldIdx); |
| if (field.hasName()) { |
| str = "."; |
| str += field.getName().str(); |
| if (!existingDiag) { |
| diagOpt.emplace(TC.diagnose(expr->getStartLoc(), |
| diag::extra_named_single_element_tuple, |
| field.getName().str())); |
| } |
| getDiag().fixItInsertAfter(expr->getEndLoc(), str); |
| return true; |
| } |
| } |
| } |
| |
| // We don't know what to do with this. |
| return false; |
| } |
| |
| // This is a scalar-to-tuple conversion. Add the name. We "know" |
| // that we're inside a ParenExpr, because ParenExprs are required |
| // by the syntax and locator resolution looks through on level of |
| // them. |
| |
| // Look through the paren expression, if there is one. |
| if (auto parenExpr = dyn_cast<ParenExpr>(expr)) |
| expr = parenExpr->getSubExpr(); |
| |
| str += newNames[0].str(); |
| str += ": "; |
| if (!existingDiag) { |
| diagOpt.emplace(TC.diagnose(expr->getStartLoc(), |
| diag::missing_argument_labels, |
| false, str.str().drop_back(), isSubscript)); |
| } |
| getDiag().fixItInsert(expr->getStartLoc(), str); |
| return true; |
| } |
| |
| // Figure out how many extraneous, missing, and wrong labels are in |
| // the call. |
| unsigned numExtra = 0, numMissing = 0, numWrong = 0; |
| unsigned n = std::max(tuple->getNumElements(), (unsigned)newNames.size()); |
| |
| llvm::SmallString<16> missingBuffer; |
| llvm::SmallString<16> extraBuffer; |
| for (unsigned i = 0; i != n; ++i) { |
| Identifier oldName; |
| if (i < tuple->getNumElements()) |
| oldName = tuple->getElementName(i); |
| Identifier newName; |
| if (i < newNames.size()) |
| newName = newNames[i]; |
| |
| if (oldName == newName || |
| (tuple->hasTrailingClosure() && i == tuple->getNumElements()-1)) |
| continue; |
| |
| if (oldName.empty()) { |
| ++numMissing; |
| missingBuffer += newName.str(); |
| missingBuffer += ":"; |
| } else if (newName.empty()) { |
| ++numExtra; |
| extraBuffer += oldName.str(); |
| extraBuffer += ':'; |
| } else |
| ++numWrong; |
| } |
| |
| // Emit the diagnostic. |
| assert(numMissing > 0 || numExtra > 0 || numWrong > 0); |
| llvm::SmallString<16> haveBuffer; // note: diagOpt has references to this |
| llvm::SmallString<16> expectedBuffer; // note: diagOpt has references to this |
| |
| // If we had any wrong labels, or we have both missing and extra labels, |
| // emit the catch-all "wrong labels" diagnostic. |
| if (!existingDiag) { |
| bool plural = (numMissing + numExtra + numWrong) > 1; |
| if (numWrong > 0 || (numMissing > 0 && numExtra > 0)) { |
| for (unsigned i = 0, n = tuple->getNumElements(); i != n; ++i) { |
| auto haveName = tuple->getElementName(i); |
| if (haveName.empty()) |
| haveBuffer += '_'; |
| else |
| haveBuffer += haveName.str(); |
| haveBuffer += ':'; |
| } |
| |
| for (auto expected : newNames) { |
| if (expected.empty()) |
| expectedBuffer += '_'; |
| else |
| expectedBuffer += expected.str(); |
| expectedBuffer += ':'; |
| } |
| |
| StringRef haveStr = haveBuffer; |
| StringRef expectedStr = expectedBuffer; |
| diagOpt.emplace(TC.diagnose(expr->getLoc(), diag::wrong_argument_labels, |
| plural, haveStr, expectedStr, isSubscript)); |
| } else if (numMissing > 0) { |
| StringRef missingStr = missingBuffer; |
| diagOpt.emplace(TC.diagnose(expr->getLoc(), diag::missing_argument_labels, |
| plural, missingStr, isSubscript)); |
| } else { |
| assert(numExtra > 0); |
| StringRef extraStr = extraBuffer; |
| diagOpt.emplace(TC.diagnose(expr->getLoc(), diag::extra_argument_labels, |
| plural, extraStr, isSubscript)); |
| } |
| } |
| |
| // Emit Fix-Its to correct the names. |
| auto &diag = getDiag(); |
| for (unsigned i = 0, n = tuple->getNumElements(); i != n; ++i) { |
| Identifier oldName = tuple->getElementName(i); |
| Identifier newName; |
| if (i < newNames.size()) |
| newName = newNames[i]; |
| |
| if (oldName == newName || (i == n-1 && tuple->hasTrailingClosure())) |
| continue; |
| |
| if (newName.empty()) { |
| // Delete the old name. |
| diag.fixItRemoveChars(tuple->getElementNameLocs()[i], |
| tuple->getElement(i)->getStartLoc()); |
| continue; |
| } |
| |
| bool newNameIsReserved = !canBeArgumentLabel(newName.str()); |
| llvm::SmallString<16> newStr; |
| if (newNameIsReserved) |
| newStr += "`"; |
| newStr += newName.str(); |
| if (newNameIsReserved) |
| newStr += "`"; |
| |
| if (oldName.empty()) { |
| // Insert the name. |
| newStr += ": "; |
| diag.fixItInsert(tuple->getElement(i)->getStartLoc(), newStr); |
| continue; |
| } |
| |
| // Change the name. |
| diag.fixItReplace(tuple->getElementNameLocs()[i], newStr); |
| } |
| |
| // If the diagnostic is local, flush it before returning. |
| // This makes sure it's emitted before the message text buffers are destroyed. |
| diagOpt.reset(); |
| return true; |
| } |
| |
| bool swift::fixItOverrideDeclarationTypes(TypeChecker &TC, |
| InFlightDiagnostic &diag, |
| ValueDecl *decl, |
| const ValueDecl *base) { |
| // For now, just rewrite cases where the base uses a value type and the |
| // override uses a reference type, and the value type is bridged to the |
| // reference type. This is a way to migrate code that makes use of types |
| // that previously were not bridged to value types. |
| auto checkType = [&](Type overrideTy, Type baseTy, |
| SourceRange typeRange) -> bool { |
| if (typeRange.isInvalid()) |
| return false; |
| |
| auto normalizeType = [](Type ty) -> Type { |
| ty = ty->getInOutObjectType(); |
| if (Type unwrappedTy = ty->getAnyOptionalObjectType()) |
| ty = unwrappedTy; |
| return ty; |
| }; |
| |
| // Is the base type bridged? |
| Type normalizedBaseTy = normalizeType(baseTy); |
| const DeclContext *DC = decl->getDeclContext(); |
| Optional<Type> maybeBridged = |
| TC.Context.getBridgedToObjC(DC, normalizedBaseTy, &TC); |
| |
| // ...and just knowing that it's bridged isn't good enough if we don't |
| // know what it's bridged /to/. Also, don't do this check for trivial |
| // bridging---that doesn't count. |
| Type bridged = maybeBridged.getValueOr(Type()); |
| if (!bridged || bridged->isEqual(normalizedBaseTy)) |
| return false; |
| |
| // ...and is it bridged to the overridden type? |
| Type normalizedOverrideTy = normalizeType(overrideTy); |
| if (!bridged->isEqual(normalizedOverrideTy)) { |
| // If both are nominal types, check again, ignoring generic arguments. |
| auto *overrideNominal = normalizedOverrideTy->getAnyNominal(); |
| if (!overrideNominal || bridged->getAnyNominal() != overrideNominal) { |
| return false; |
| } |
| } |
| |
| Type newOverrideTy = baseTy; |
| |
| // Preserve optionality if we're dealing with a simple type. |
| OptionalTypeKind OTK; |
| if (Type unwrappedTy = newOverrideTy->getAnyOptionalObjectType()) |
| newOverrideTy = unwrappedTy; |
| if (overrideTy->getAnyOptionalObjectType(OTK)) |
| newOverrideTy = OptionalType::get(OTK, newOverrideTy); |
| |
| SmallString<32> baseTypeBuf; |
| llvm::raw_svector_ostream baseTypeStr(baseTypeBuf); |
| PrintOptions options; |
| options.SynthesizeSugarOnTypes = true; |
| |
| newOverrideTy->print(baseTypeStr, options); |
| diag.fixItReplace(typeRange, baseTypeStr.str()); |
| return true; |
| }; |
| |
| if (auto *var = dyn_cast<VarDecl>(decl)) { |
| SourceRange typeRange = var->getTypeSourceRangeForDiagnostics(); |
| return checkType(var->getType(), base->getType(), typeRange); |
| } |
| |
| if (auto *fn = dyn_cast<AbstractFunctionDecl>(decl)) { |
| auto *baseFn = cast<AbstractFunctionDecl>(base); |
| bool fixedAny = false; |
| if (fn->getParameterLists().back()->size() == |
| baseFn->getParameterLists().back()->size()) { |
| for_each(*fn->getParameterLists().back(), |
| *baseFn->getParameterLists().back(), |
| [&](ParamDecl *param, const ParamDecl *baseParam) { |
| fixedAny |= fixItOverrideDeclarationTypes(TC, diag, param, baseParam); |
| }); |
| } |
| if (auto *method = dyn_cast<FuncDecl>(decl)) { |
| auto *baseMethod = cast<FuncDecl>(base); |
| fixedAny |= checkType(method->getBodyResultType(), |
| baseMethod->getResultType(), |
| method->getBodyResultTypeLoc().getSourceRange()); |
| } |
| return fixedAny; |
| } |
| |
| if (auto *subscript = dyn_cast<SubscriptDecl>(decl)) { |
| auto *baseSubscript = cast<SubscriptDecl>(base); |
| bool fixedAny = false; |
| for_each(*subscript->getIndices(), |
| *baseSubscript->getIndices(), |
| [&](ParamDecl *param, const ParamDecl *baseParam) { |
| fixedAny |= fixItOverrideDeclarationTypes(TC, diag, param, baseParam); |
| }); |
| fixedAny |= checkType(subscript->getElementType(), |
| baseSubscript->getElementType(), |
| subscript->getElementTypeLoc().getSourceRange()); |
| return fixedAny; |
| } |
| |
| llvm_unreachable("unknown overridable member"); |
| } |
| |
| |
| |
| //===----------------------------------------------------------------------===// |
| // Diagnose availability. |
| //===----------------------------------------------------------------------===// |
| |
| void swift::fixItAvailableAttrRename(TypeChecker &TC, |
| InFlightDiagnostic &diag, |
| SourceRange referenceRange, |
| const AvailableAttr *attr, |
| const ApplyExpr *call) { |
| ParsedDeclName parsed = swift::parseDeclName(attr->Rename); |
| if (!parsed) |
| return; |
| |
| bool originallyWasKnownOperatorExpr = false; |
| if (call) { |
| originallyWasKnownOperatorExpr = |
| isa<BinaryExpr>(call) || |
| isa<PrefixUnaryExpr>(call) || |
| isa<PostfixUnaryExpr>(call); |
| } |
| if (parsed.isOperator() != originallyWasKnownOperatorExpr) |
| return; |
| |
| SourceManager &sourceMgr = TC.Context.SourceMgr; |
| |
| if (parsed.isInstanceMember()) { |
| // Replace the base of the call with the "self argument". |
| // We can only do a good job with the fix-it if we have the whole call |
| // expression. |
| // FIXME: Should we be validating the ContextName in some way? |
| if (!dyn_cast_or_null<CallExpr>(call)) |
| return; |
| |
| unsigned selfIndex = parsed.SelfIndex.getValue(); |
| const Expr *selfExpr = nullptr; |
| SourceLoc removeRangeStart; |
| SourceLoc removeRangeEnd; |
| |
| const Expr *argExpr = call->getArg(); |
| if (auto args = dyn_cast<TupleExpr>(argExpr)) { |
| size_t numElementsWithinParens = args->getNumElements(); |
| numElementsWithinParens -= args->hasTrailingClosure(); |
| if (selfIndex >= numElementsWithinParens) |
| return; |
| |
| if (parsed.IsGetter) { |
| if (numElementsWithinParens != 1) |
| return; |
| } else if (parsed.IsSetter) { |
| if (numElementsWithinParens != 2) |
| return; |
| } else { |
| if (parsed.ArgumentLabels.size() != args->getNumElements() - 1) |
| return; |
| } |
| |
| selfExpr = args->getElement(selfIndex); |
| |
| if (selfIndex + 1 == numElementsWithinParens) { |
| if (selfIndex > 0) { |
| // Remove from the previous comma to the close-paren (half-open). |
| removeRangeStart = args->getElement(selfIndex-1)->getEndLoc(); |
| removeRangeStart = Lexer::getLocForEndOfToken(sourceMgr, |
| removeRangeStart); |
| } else { |
| // Remove from after the open paren to the close paren (half-open). |
| removeRangeStart = Lexer::getLocForEndOfToken(sourceMgr, |
| argExpr->getStartLoc()); |
| } |
| |
| // Prefer the r-paren location, so that we get the right behavior when |
| // there's a trailing closure, but handle some implicit cases too. |
| removeRangeEnd = args->getRParenLoc(); |
| if (removeRangeEnd.isInvalid()) |
| removeRangeEnd = args->getEndLoc(); |
| |
| } else { |
| // Remove from the label to the start of the next argument (half-open). |
| SourceLoc labelLoc = args->getElementNameLoc(selfIndex); |
| if (labelLoc.isValid()) |
| removeRangeStart = labelLoc; |
| else |
| removeRangeStart = selfExpr->getStartLoc(); |
| |
| SourceLoc nextLabelLoc = args->getElementNameLoc(selfIndex + 1); |
| if (nextLabelLoc.isValid()) |
| removeRangeEnd = nextLabelLoc; |
| else |
| removeRangeEnd = args->getElement(selfIndex + 1)->getStartLoc(); |
| } |
| |
| // Avoid later argument label fix-its for this argument. |
| if (!parsed.isPropertyAccessor()) { |
| Identifier oldLabel = args->getElementName(selfIndex); |
| StringRef oldLabelStr; |
| if (!oldLabel.empty()) |
| oldLabelStr = oldLabel.str(); |
| parsed.ArgumentLabels.insert(parsed.ArgumentLabels.begin() + selfIndex, |
| oldLabelStr); |
| } |
| |
| } else { |
| if (selfIndex != 0 || !parsed.ArgumentLabels.empty()) |
| return; |
| selfExpr = cast<ParenExpr>(argExpr)->getSubExpr(); |
| // Remove from after the open paren to the close paren (half-open). |
| removeRangeStart = Lexer::getLocForEndOfToken(sourceMgr, |
| argExpr->getStartLoc()); |
| removeRangeEnd = argExpr->getEndLoc(); |
| } |
| |
| if (auto *inoutSelf = dyn_cast<InOutExpr>(selfExpr)) |
| selfExpr = inoutSelf->getSubExpr(); |
| |
| CharSourceRange selfExprRange = |
| Lexer::getCharSourceRangeFromSourceRange(sourceMgr, |
| selfExpr->getSourceRange()); |
| bool needsParens = !selfExpr->canAppendCallParentheses(); |
| |
| SmallString<64> selfReplace; |
| if (needsParens) |
| selfReplace.push_back('('); |
| selfReplace += sourceMgr.extractText(selfExprRange); |
| if (needsParens) |
| selfReplace.push_back(')'); |
| selfReplace.push_back('.'); |
| selfReplace += parsed.BaseName; |
| diag.fixItReplace(call->getFn()->getSourceRange(), selfReplace); |
| |
| if (!parsed.isPropertyAccessor()) |
| diag.fixItRemoveChars(removeRangeStart, removeRangeEnd); |
| |
| // Continue on to diagnose any argument label renames. |
| |
| } else if (parsed.BaseName == TC.Context.Id_init.str() && |
| dyn_cast_or_null<CallExpr>(call)) { |
| // For initializers, replace with a "call" of the context type...but only |
| // if we know we're doing a call (rather than a first-class reference). |
| if (parsed.isMember()) { |
| diag.fixItReplace(call->getFn()->getSourceRange(), parsed.ContextName); |
| |
| } else { |
| auto *dotCall = dyn_cast<DotSyntaxCallExpr>(call->getFn()); |
| if (!dotCall) |
| return; |
| |
| SourceLoc removeLoc = dotCall->getDotLoc(); |
| if (removeLoc.isInvalid()) |
| return; |
| |
| diag.fixItRemove(SourceRange(removeLoc, dotCall->getFn()->getEndLoc())); |
| } |
| |
| } else { |
| // Just replace the base name. |
| SmallString<64> baseReplace; |
| if (!parsed.ContextName.empty()) { |
| baseReplace += parsed.ContextName; |
| baseReplace += '.'; |
| } |
| baseReplace += parsed.BaseName; |
| diag.fixItReplace(referenceRange, baseReplace); |
| } |
| |
| if (!dyn_cast_or_null<CallExpr>(call)) |
| return; |
| |
| const Expr *argExpr = call->getArg(); |
| if (parsed.IsGetter) { |
| diag.fixItRemove(argExpr->getSourceRange()); |
| return; |
| } |
| |
| if (parsed.IsSetter) { |
| const Expr *newValueExpr = nullptr; |
| |
| if (auto args = dyn_cast<TupleExpr>(argExpr)) { |
| size_t newValueIndex = 0; |
| if (parsed.isInstanceMember()) { |
| assert(parsed.SelfIndex.getValue() == 0 || |
| parsed.SelfIndex.getValue() == 1); |
| newValueIndex = !parsed.SelfIndex.getValue(); |
| } |
| newValueExpr = args->getElement(newValueIndex); |
| } else { |
| newValueExpr = cast<ParenExpr>(argExpr)->getSubExpr(); |
| } |
| |
| diag.fixItReplaceChars(argExpr->getStartLoc(), newValueExpr->getStartLoc(), |
| " = "); |
| diag.fixItRemoveChars(Lexer::getLocForEndOfToken(sourceMgr, |
| newValueExpr->getEndLoc()), |
| Lexer::getLocForEndOfToken(sourceMgr, |
| argExpr->getEndLoc())); |
| return; |
| } |
| |
| if (!parsed.IsFunctionName) |
| return; |
| |
| SmallVector<Identifier, 4> argumentLabelIDs; |
| std::transform(parsed.ArgumentLabels.begin(), parsed.ArgumentLabels.end(), |
| std::back_inserter(argumentLabelIDs), |
| [&TC](StringRef labelStr) -> Identifier { |
| return labelStr.empty() ? Identifier() : TC.Context.getIdentifier(labelStr); |
| }); |
| |
| if (auto args = dyn_cast<TupleShuffleExpr>(argExpr)) { |
| argExpr = args->getSubExpr(); |
| |
| // Coerce the `argumentLabelIDs` to the user supplied arguments. |
| // e.g: |
| // @available(.., renamed: "new(w:x:y:z:)") |
| // func old(a: Int, b: Int..., c: String="", d: Int=0){} |
| // old(a: 1, b: 2, 3, 4, d: 5) |
| // coerce |
| // argumentLabelIDs = {"w", "x", "y", "z"} |
| // to |
| // argumentLabelIDs = {"w", "x", "", "", "z"} |
| auto elementMap = args->getElementMapping(); |
| if (elementMap.size() != argumentLabelIDs.size()) { |
| // Mismatched lengths; give up. |
| return; |
| } |
| auto I = argumentLabelIDs.begin(); |
| for (auto shuffleIdx : elementMap) { |
| switch (shuffleIdx) { |
| case TupleShuffleExpr::DefaultInitialize: |
| case TupleShuffleExpr::CallerDefaultInitialize: |
| // Defaulted: remove param label of it. |
| I = argumentLabelIDs.erase(I); |
| break; |
| case TupleShuffleExpr::Variadic: { |
| auto variadicArgsNum = args->getVariadicArgs().size(); |
| if (variadicArgsNum == 0) { |
| // No arguments: Remove param label of it. |
| I = argumentLabelIDs.erase(I); |
| } else if (variadicArgsNum == 1) { |
| // One argument: Just advance. |
| ++I; |
| } else { |
| // Two or more arguments: Insert empty labels after the first one. |
| I = argumentLabelIDs.insert(++I, --variadicArgsNum, Identifier()); |
| I += variadicArgsNum; |
| } |
| break; |
| } |
| default: |
| // Normal: Just advance. |
| assert(shuffleIdx == (I - argumentLabelIDs.begin()) && |
| "SE-0060 guarantee"); |
| ++I; |
| break; |
| } |
| } |
| } |
| |
| if (auto args = dyn_cast<TupleExpr>(argExpr)) { |
| if (argumentLabelIDs.size() != args->getNumElements()) { |
| // Mismatched lengths; give up. |
| return; |
| } |
| |
| auto argumentLabelsToCheck = llvm::makeArrayRef(argumentLabelIDs); |
| // The argument label for a trailing closure is ignored. |
| if (args->hasTrailingClosure()) |
| argumentLabelsToCheck = argumentLabelsToCheck.drop_back(); |
| |
| if (args->hasElementNames()) { |
| if (std::equal(argumentLabelsToCheck.begin(), argumentLabelsToCheck.end(), |
| args->getElementNames().begin())) { |
| // Already matching. |
| return; |
| } |
| |
| } else { |
| if (std::all_of(argumentLabelsToCheck.begin(),argumentLabelsToCheck.end(), |
| std::mem_fn(&Identifier::empty))) { |
| // Already matching (as in, there are no labels). |
| return; |
| } |
| } |
| |
| } else if (auto args = dyn_cast<ParenExpr>(argExpr)) { |
| if (args->hasTrailingClosure()) { |
| // The argument label for a trailing closure is ignored. |
| return; |
| } |
| |
| if (argumentLabelIDs.size() != 1) { |
| // Mismatched lengths; give up. |
| return; |
| } |
| |
| if (argumentLabelIDs.front().empty()) { |
| // Already matching (no labels). |
| return; |
| } |
| } else { |
| llvm_unreachable("Unexpected arg expression"); |
| } |
| |
| diagnoseArgumentLabelError(TC, argExpr, argumentLabelIDs, false, &diag); |
| } |
| |
| // Must be kept in sync with diag::availability_decl_unavailable_rename and |
| // others. |
| namespace { |
| enum class ReplacementDeclKind : unsigned { |
| None, |
| InstanceMethod, |
| Property, |
| }; |
| } |
| |
| static Optional<ReplacementDeclKind> |
| describeRename(ASTContext &ctx, const AvailableAttr *attr, const ValueDecl *D, |
| SmallVectorImpl<char> &nameBuf) { |
| ParsedDeclName parsed = swift::parseDeclName(attr->Rename); |
| if (!parsed) |
| return None; |
| |
| // Only produce special descriptions for renames to |
| // - instance members |
| // - properties (or global bindings) |
| // - class/static methods |
| // - initializers, unless the original was known to be an initializer |
| // Leave non-member renames alone, as well as renames from top-level types |
| // and bindings to member types and class/static properties. |
| if (!(parsed.isInstanceMember() || parsed.isPropertyAccessor() || |
| (parsed.isMember() && parsed.IsFunctionName) || |
| (parsed.BaseName == ctx.Id_init.str() && |
| !dyn_cast_or_null<ConstructorDecl>(D)))) { |
| return None; |
| } |
| |
| llvm::raw_svector_ostream name(nameBuf); |
| |
| if (!parsed.ContextName.empty()) |
| name << parsed.ContextName << '.'; |
| |
| if (parsed.IsFunctionName) { |
| // FIXME: duplicated from above. |
| SmallVector<Identifier, 4> argumentLabelIDs; |
| std::transform(parsed.ArgumentLabels.begin(), parsed.ArgumentLabels.end(), |
| std::back_inserter(argumentLabelIDs), |
| [&ctx](StringRef labelStr) -> Identifier { |
| return labelStr.empty() ? Identifier() : ctx.getIdentifier(labelStr); |
| }); |
| name << DeclName(ctx, ctx.getIdentifier(parsed.BaseName), argumentLabelIDs); |
| } else { |
| name << parsed.BaseName; |
| } |
| |
| if (parsed.isMember() && parsed.isPropertyAccessor()) |
| return ReplacementDeclKind::Property; |
| if (parsed.isInstanceMember() && parsed.IsFunctionName) |
| return ReplacementDeclKind::InstanceMethod; |
| |
| // We don't have enough information. |
| return ReplacementDeclKind::None; |
| } |
| |
| void TypeChecker::diagnoseDeprecated(SourceRange ReferenceRange, |
| const DeclContext *ReferenceDC, |
| const AvailableAttr *Attr, |
| DeclName Name, |
| const ApplyExpr *Call) { |
| // We match the behavior of clang to not report deprecation warnings |
| // inside declarations that are themselves deprecated on all deployment |
| // targets. |
| if (isInsideDeprecatedDeclaration(ReferenceRange, ReferenceDC)) { |
| return; |
| } |
| |
| if (!Context.LangOpts.DisableAvailabilityChecking) { |
| AvailabilityContext RunningOSVersions = |
| overApproximateAvailabilityAtLocation(ReferenceRange.Start,ReferenceDC); |
| if (RunningOSVersions.isKnownUnreachable()) { |
| // Suppress a deprecation warning if the availability checking machinery |
| // thinks the reference program location will not execute on any |
| // deployment target for the current platform. |
| return; |
| } |
| } |
| |
| StringRef Platform = Attr->prettyPlatformString(); |
| clang::VersionTuple DeprecatedVersion; |
| if (Attr->Deprecated) |
| DeprecatedVersion = Attr->Deprecated.getValue(); |
| |
| if (Attr->Message.empty() && Attr->Rename.empty()) { |
| diagnose(ReferenceRange.Start, diag::availability_deprecated, Name, |
| Attr->hasPlatform(), Platform, Attr->Deprecated.hasValue(), |
| DeprecatedVersion) |
| .highlight(Attr->getRange()); |
| return; |
| } |
| |
| SmallString<32> newNameBuf; |
| Optional<ReplacementDeclKind> replacementDeclKind = |
| describeRename(Context, Attr, /*decl*/nullptr, newNameBuf); |
| StringRef newName = replacementDeclKind ? newNameBuf.str() : Attr->Rename; |
| |
| if (!Attr->Message.empty()) { |
| EncodedDiagnosticMessage EncodedMessage(Attr->Message); |
| diagnose(ReferenceRange.Start, diag::availability_deprecated_msg, Name, |
| Attr->hasPlatform(), Platform, Attr->Deprecated.hasValue(), |
| DeprecatedVersion, EncodedMessage.Message) |
| .highlight(Attr->getRange()); |
| } else { |
| unsigned rawReplaceKind = static_cast<unsigned>( |
| replacementDeclKind.getValueOr(ReplacementDeclKind::None)); |
| diagnose(ReferenceRange.Start, diag::availability_deprecated_rename, Name, |
| Attr->hasPlatform(), Platform, Attr->Deprecated.hasValue(), |
| DeprecatedVersion, replacementDeclKind.hasValue(), rawReplaceKind, |
| newName) |
| .highlight(Attr->getRange()); |
| } |
| |
| if (!Attr->Rename.empty()) { |
| auto renameDiag = diagnose(ReferenceRange.Start, |
| diag::note_deprecated_rename, |
| newName); |
| fixItAvailableAttrRename(*this, renameDiag, ReferenceRange, Attr, Call); |
| } |
| } |
| |
| |
| void TypeChecker::diagnoseUnavailableOverride(ValueDecl *override, |
| const ValueDecl *base, |
| const AvailableAttr *attr) { |
| if (attr->Rename.empty()) { |
| if (attr->Message.empty()) |
| diagnose(override, diag::override_unavailable, override->getName()); |
| else |
| diagnose(override, diag::override_unavailable_msg, |
| override->getName(), attr->Message); |
| diagnose(base, diag::availability_marked_unavailable, |
| base->getFullName()); |
| return; |
| } |
| |
| diagnoseExplicitUnavailability(base, override->getLoc(), |
| override->getDeclContext(), |
| [&](InFlightDiagnostic &diag) { |
| ParsedDeclName parsedName = parseDeclName(attr->Rename); |
| if (!parsedName || parsedName.isPropertyAccessor() || |
| parsedName.isMember() || parsedName.isOperator()) { |
| return; |
| } |
| |
| // Only initializers should be named 'init'. |
| if (isa<ConstructorDecl>(override) ^ |
| (parsedName.BaseName == Context.Id_init.str())) { |
| return; |
| } |
| |
| if (!parsedName.IsFunctionName) { |
| diag.fixItReplace(override->getNameLoc(), parsedName.BaseName); |
| return; |
| } |
| |
| DeclName newName = parsedName.formDeclName(Context); |
| size_t numArgs = override->getFullName().getArgumentNames().size(); |
| if (!newName || newName.getArgumentNames().size() != numArgs) |
| return; |
| |
| fixDeclarationName(diag, override, newName); |
| }); |
| } |
| |
| /// Emit a diagnostic for references to declarations that have been |
| /// marked as unavailable, either through "unavailable" or "obsoleted:". |
| bool TypeChecker::diagnoseExplicitUnavailability(const ValueDecl *D, |
| SourceRange R, |
| const DeclContext *DC, |
| const ApplyExpr *call) { |
| return diagnoseExplicitUnavailability(D, R, DC, |
| [=](InFlightDiagnostic &diag) { |
| fixItAvailableAttrRename(*this, diag, R, AvailableAttr::isUnavailable(D), |
| call); |
| }); |
| } |
| |
| bool TypeChecker::diagnoseExplicitUnavailability( |
| const ValueDecl *D, |
| SourceRange R, |
| const DeclContext *DC, |
| llvm::function_ref<void(InFlightDiagnostic &)> attachRenameFixIts) { |
| auto *Attr = AvailableAttr::isUnavailable(D); |
| if (!Attr) |
| return false; |
| |
| // Suppress the diagnostic if we are in synthesized code inside |
| // a synthesized function and the reference is lexically |
| // contained in a declaration that is itself marked unavailable. |
| // The right thing to do here is to not synthesize that code in the |
| // first place. rdar://problem/20491640 |
| if (R.isInvalid() && isInsideImplicitFunction(R, DC) && |
| isInsideUnavailableDeclaration(R, DC)) { |
| return false; |
| } |
| |
| SourceLoc Loc = R.Start; |
| auto Name = D->getFullName(); |
| |
| switch (Attr->getUnconditionalAvailability()) { |
| case UnconditionalAvailabilityKind::Deprecated: |
| break; |
| |
| case UnconditionalAvailabilityKind::None: |
| case UnconditionalAvailabilityKind::Unavailable: |
| case UnconditionalAvailabilityKind::UnavailableInCurrentSwift: |
| case UnconditionalAvailabilityKind::UnavailableInSwift: { |
| bool inSwift = (Attr->getUnconditionalAvailability() == |
| UnconditionalAvailabilityKind::UnavailableInSwift); |
| |
| if (!Attr->Rename.empty()) { |
| SmallString<32> newNameBuf; |
| Optional<ReplacementDeclKind> replaceKind = |
| describeRename(Context, Attr, D, newNameBuf); |
| unsigned rawReplaceKind = static_cast<unsigned>( |
| replaceKind.getValueOr(ReplacementDeclKind::None)); |
| StringRef newName = replaceKind ? newNameBuf.str() : Attr->Rename; |
| |
| if (Attr->Message.empty()) { |
| auto diag = diagnose(Loc, diag::availability_decl_unavailable_rename, |
| Name, replaceKind.hasValue(), rawReplaceKind, |
| newName); |
| attachRenameFixIts(diag); |
| } else { |
| auto diag = diagnose(Loc, diag::availability_decl_unavailable_rename_msg, |
| Name, replaceKind.hasValue(), rawReplaceKind, |
| newName, Attr->Message); |
| attachRenameFixIts(diag); |
| } |
| } else if (Attr->Message.empty()) { |
| diagnose(Loc, inSwift ? diag::availability_decl_unavailable_in_swift |
| : diag::availability_decl_unavailable, |
| Name).highlight(R); |
| } else { |
| EncodedDiagnosticMessage EncodedMessage(Attr->Message); |
| diagnose(Loc, inSwift ? diag::availability_decl_unavailable_in_swift_msg |
| : diag::availability_decl_unavailable_msg, |
| Name, EncodedMessage.Message) |
| .highlight(R); |
| } |
| break; |
| } |
| } |
| |
| auto MinVersion = Context.LangOpts.getMinPlatformVersion(); |
| switch (Attr->getMinVersionAvailability(MinVersion)) { |
| case MinVersionComparison::Available: |
| case MinVersionComparison::PotentiallyUnavailable: |
| llvm_unreachable("These aren't considered unavailable"); |
| |
| case MinVersionComparison::Unavailable: |
| diagnose(D, diag::availability_marked_unavailable, Name) |
| .highlight(Attr->getRange()); |
| break; |
| |
| case MinVersionComparison::Obsoleted: |
| // FIXME: Use of the platformString here is non-awesome for application |
| // extensions. |
| diagnose(D, diag::availability_obsoleted, Name, |
| Attr->prettyPlatformString(), |
| *Attr->Obsoleted).highlight(Attr->getRange()); |
| break; |
| } |
| return true; |
| } |
| |
| namespace { |
| class AvailabilityWalker : public ASTWalker { |
| /// Describes how the next member reference will be treated as we traverse |
| /// the AST. |
| enum class MemberAccessContext : unsigned { |
| /// The member reference is in a context where an access will call |
| /// the getter. |
| Getter, |
| |
| /// The member reference is in a context where an access will call |
| /// the setter. |
| Setter, |
| |
| /// The member reference is in a context where it will be turned into |
| /// an inout argument. (Once this happens, we have to conservatively assume |
| /// that both the getter and setter could be called.) |
| InOut |
| }; |
| |
| TypeChecker &TC; |
| DeclContext *DC; |
| MemberAccessContext AccessContext = MemberAccessContext::Getter; |
| SmallVector<const Expr *, 16> ExprStack; |
| |
| public: |
| AvailabilityWalker( |
| TypeChecker &TC, DeclContext *DC) : TC(TC), DC(DC) {} |
| |
| virtual std::pair<bool, Expr *> walkToExprPre(Expr *E) override { |
| ExprStack.push_back(E); |
| |
| auto visitChildren = [&]() { return std::make_pair(true, E); }; |
| auto skipChildren = [&]() { |
| ExprStack.pop_back(); |
| return std::make_pair(false, E); |
| }; |
| |
| if (auto DR = dyn_cast<DeclRefExpr>(E)) |
| diagAvailability(DR->getDecl(), DR->getSourceRange(), |
| getEnclosingApplyExpr()); |
| if (auto MR = dyn_cast<MemberRefExpr>(E)) { |
| walkMemberRef(MR); |
| return skipChildren(); |
| } |
| if (auto OCDR = dyn_cast<OtherConstructorDeclRefExpr>(E)) |
| diagAvailability(OCDR->getDecl(), |
| OCDR->getConstructorLoc().getSourceRange(), |
| getEnclosingApplyExpr()); |
| if (auto DMR = dyn_cast<DynamicMemberRefExpr>(E)) |
| diagAvailability(DMR->getMember().getDecl(), |
| DMR->getNameLoc().getSourceRange(), |
| getEnclosingApplyExpr()); |
| if (auto DS = dyn_cast<DynamicSubscriptExpr>(E)) |
| diagAvailability(DS->getMember().getDecl(), DS->getSourceRange()); |
| if (auto S = dyn_cast<SubscriptExpr>(E)) { |
| if (S->hasDecl()) |
| diagAvailability(S->getDecl().getDecl(), S->getSourceRange()); |
| } |
| if (auto A = dyn_cast<AssignExpr>(E)) { |
| walkAssignExpr(A); |
| return skipChildren(); |
| } |
| if (auto IO = dyn_cast<InOutExpr>(E)) { |
| walkInOutExpr(IO); |
| return skipChildren(); |
| } |
| |
| return visitChildren(); |
| } |
| |
| virtual Expr *walkToExprPost(Expr *E) override { |
| assert(ExprStack.back() == E); |
| ExprStack.pop_back(); |
| |
| return E; |
| } |
| |
| private: |
| bool diagAvailability(const ValueDecl *D, SourceRange R, |
| const ApplyExpr *call = nullptr); |
| bool diagnoseIncDecRemoval(const ValueDecl *D, SourceRange R, |
| const AvailableAttr *Attr); |
| bool diagnoseMemoryLayoutMigration(const ValueDecl *D, SourceRange R, |
| const AvailableAttr *Attr, |
| const ApplyExpr *call); |
| |
| /// Walks up from a potential callee to the enclosing ApplyExpr. |
| const ApplyExpr *getEnclosingApplyExpr() const { |
| ArrayRef<const Expr *> parents = ExprStack; |
| assert(!parents.empty() && "must be called while visiting an expression"); |
| size_t idx = parents.size() - 1; |
| |
| do { |
| if (idx == 0) |
| return nullptr; |
| --idx; |
| } while (isa<DotSyntaxBaseIgnoredExpr>(parents[idx]) || // Mod.f(a) |
| isa<SelfApplyExpr>(parents[idx]) || // obj.f(a) |
| isa<IdentityExpr>(parents[idx]) || // (f)(a) |
| isa<ForceValueExpr>(parents[idx]) || // f!(a) |
| isa<BindOptionalExpr>(parents[idx])); // f?(a) |
| |
| auto *call = dyn_cast<ApplyExpr>(parents[idx]); |
| if (!call || call->getFn() != parents[idx+1]) |
| return nullptr; |
| return call; |
| } |
| |
| /// Walk an assignment expression, checking for availability. |
| void walkAssignExpr(AssignExpr *E) { |
| // We take over recursive walking of assignment expressions in order to |
| // walk the destination and source expressions in different member |
| // access contexts. |
| Expr *Dest = E->getDest(); |
| if (!Dest) { |
| return; |
| } |
| |
| // Check the Dest expression in a setter context. |
| // We have an implicit assumption here that the first MemberRefExpr |
| // encountered walking (pre-order) is the Dest is the destination of the |
| // write. For the moment this is fine -- but future syntax might violate |
| // this assumption. |
| walkInContext(E, Dest, MemberAccessContext::Setter); |
| |
| // Check RHS in getter context |
| Expr *Source = E->getSrc(); |
| if (!Source) { |
| return; |
| } |
| walkInContext(E, Source, MemberAccessContext::Getter); |
| } |
| |
| /// Walk a member reference expression, checking for availability. |
| void walkMemberRef(MemberRefExpr *E) { |
| // Walk the base in a getter context. |
| walkInContext(E, E->getBase(), MemberAccessContext::Getter); |
| |
| ValueDecl *D = E->getMember().getDecl(); |
| // Diagnose for the member declaration itself. |
| if (diagAvailability(D, E->getNameLoc().getSourceRange())) |
| return; |
| |
| if (TC.getLangOpts().DisableAvailabilityChecking) |
| return; |
| |
| if (auto *ASD = dyn_cast<AbstractStorageDecl>(D)) { |
| // Diagnose for appropriate accessors, given the access context. |
| diagStorageAccess(ASD, E->getSourceRange(), DC); |
| } |
| } |
| |
| /// Walk an inout expression, checking for availability. |
| void walkInOutExpr(InOutExpr *E) { |
| walkInContext(E, E->getSubExpr(), MemberAccessContext::InOut); |
| } |
| |
| /// Walk the given expression in the member access context. |
| void walkInContext(Expr *baseExpr, Expr *E, |
| MemberAccessContext AccessContext) { |
| llvm::SaveAndRestore<MemberAccessContext> |
| C(this->AccessContext, AccessContext); |
| E->walk(*this); |
| } |
| |
| /// Emit diagnostics, if necessary, for accesses to storage where |
| /// the accessor for the AccessContext is not available. |
| void diagStorageAccess(AbstractStorageDecl *D, |
| SourceRange ReferenceRange, |
| const DeclContext *ReferenceDC) const { |
| if (!D->hasAccessorFunctions()) { |
| return; |
| } |
| |
| // Check availability of accessor functions |
| switch (AccessContext) { |
| case MemberAccessContext::Getter: |
| diagAccessorAvailability(D->getGetter(), ReferenceRange, ReferenceDC, |
| /*ForInout=*/false); |
| break; |
| |
| case MemberAccessContext::Setter: |
| diagAccessorAvailability(D->getSetter(), ReferenceRange, ReferenceDC, |
| /*ForInout=*/false); |
| break; |
| |
| case MemberAccessContext::InOut: |
| diagAccessorAvailability(D->getGetter(), ReferenceRange, ReferenceDC, |
| /*ForInout=*/true); |
| |
| diagAccessorAvailability(D->getSetter(), ReferenceRange, ReferenceDC, |
| /*ForInout=*/true); |
| break; |
| } |
| } |
| |
| /// Emit a diagnostic, if necessary for a potentially unavailable accessor. |
| /// Returns true if a diagnostic was emitted. |
| void diagAccessorAvailability(FuncDecl *D, SourceRange ReferenceRange, |
| const DeclContext *ReferenceDC, |
| bool ForInout) const { |
| if (!D) { |
| return; |
| } |
| auto MaybeUnavail = TC.checkDeclarationAvailability(D, ReferenceRange.Start, |
| DC); |
| if (MaybeUnavail.hasValue()) { |
| TC.diagnosePotentialAccessorUnavailability(D, ReferenceRange, ReferenceDC, |
| MaybeUnavail.getValue(), |
| ForInout); |
| } |
| } |
| }; |
| } |
| |
| |
| /// Diagnose uses of unavailable declarations. Returns true if a diagnostic |
| /// was emitted. |
| bool AvailabilityWalker::diagAvailability(const ValueDecl *D, SourceRange R, |
| const ApplyExpr *call) { |
| if (!D) |
| return false; |
| |
| if (auto *attr = AvailableAttr::isUnavailable(D)) { |
| if (diagnoseIncDecRemoval(D, R, attr)) |
| return true; |
| if (call && diagnoseMemoryLayoutMigration(D, R, attr, call)) |
| return true; |
| } |
| |
| if (TC.diagnoseExplicitUnavailability(D, R, DC, call)) |
| return true; |
| |
| // Diagnose for deprecation |
| if (const AvailableAttr *Attr = TypeChecker::getDeprecated(D)) { |
| TC.diagnoseDeprecated(R, DC, Attr, D->getFullName(), call); |
| } |
| |
| if (TC.getLangOpts().DisableAvailabilityChecking) |
| return false; |
| |
| // Diagnose for potential unavailability |
| auto maybeUnavail = TC.checkDeclarationAvailability(D, R.Start, DC); |
| if (maybeUnavail.hasValue()) { |
| TC.diagnosePotentialUnavailability(D, R, DC, maybeUnavail.getValue()); |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /// Return true if the specified type looks like an integer of floating point |
| /// type. |
| static bool isIntegerOrFloatingPointType(Type ty, DeclContext *DC, |
| TypeChecker &TC) { |
| auto integerType = |
| TC.getProtocol(SourceLoc(), |
| KnownProtocolKind::ExpressibleByIntegerLiteral); |
| auto floatingType = |
| TC.getProtocol(SourceLoc(), |
| KnownProtocolKind::ExpressibleByFloatLiteral); |
| if (!integerType || !floatingType) return false; |
| |
| return |
| TC.conformsToProtocol(ty, integerType, DC, |
| ConformanceCheckFlags::InExpression) || |
| TC.conformsToProtocol(ty, floatingType, DC, |
| ConformanceCheckFlags::InExpression); |
| } |
| |
| |
| /// If this is a call to an unavailable ++ / -- operator, try to diagnose it |
| /// with a fixit hint and return true. If not, or if we fail, return false. |
| bool AvailabilityWalker::diagnoseIncDecRemoval(const ValueDecl *D, |
| SourceRange R, |
| const AvailableAttr *Attr) { |
| // We can only produce a fixit if we're talking about ++ or --. |
| bool isInc = D->getNameStr() == "++"; |
| if (!isInc && D->getNameStr() != "--") |
| return false; |
| |
| // We can only handle the simple cases of lvalue++ and ++lvalue. This is |
| // always modeled as: |
| // (postfix_unary_expr (declrefexpr ++), (inoutexpr (lvalue))) |
| // if not, bail out. |
| if (ExprStack.size() != 2 || |
| !isa<DeclRefExpr>(ExprStack[1]) || |
| !(isa<PostfixUnaryExpr>(ExprStack[0]) || |
| isa<PrefixUnaryExpr>(ExprStack[0]))) |
| return false; |
| |
| auto call = cast<ApplyExpr>(ExprStack[0]); |
| |
| // If the expression type is integer or floating point, then we can rewrite it |
| // to "lvalue += 1". |
| std::string replacement; |
| if (isIntegerOrFloatingPointType(call->getType(), DC, TC)) |
| replacement = isInc ? " += 1" : " -= 1"; |
| else { |
| // Otherwise, it must be an index type. Rewrite to: |
| // "lvalue = lvalue.successor()". |
| auto &SM = TC.Context.SourceMgr; |
| auto CSR = Lexer::getCharSourceRangeFromSourceRange(SM, |
| call->getArg()->getSourceRange()); |
| replacement = " = " + SM.extractText(CSR).str(); |
| replacement += isInc ? ".successor()" : ".predecessor()"; |
| } |
| |
| if (!replacement.empty()) { |
| // If we emit a deprecation diagnostic, produce a fixit hint as well. |
| auto diag = TC.diagnose(R.Start, diag::availability_decl_unavailable_msg, |
| D->getFullName(), "it has been removed in Swift 3"); |
| if (isa<PrefixUnaryExpr>(call)) { |
| // Prefix: remove the ++ or --. |
| diag.fixItRemove(call->getFn()->getSourceRange()); |
| diag.fixItInsertAfter(call->getArg()->getEndLoc(), replacement); |
| } else { |
| // Postfix: replace the ++ or --. |
| diag.fixItReplace(call->getFn()->getSourceRange(), replacement); |
| } |
| |
| return true; |
| } |
| |
| |
| return false; |
| } |
| |
| /// If this is a call to an unavailable sizeof family functions, diagnose it |
| /// with a fixit hint and return true. If not, or if we fail, return false. |
| bool AvailabilityWalker::diagnoseMemoryLayoutMigration(const ValueDecl *D, |
| SourceRange R, |
| const AvailableAttr *Attr, |
| const ApplyExpr *call) { |
| |
| if (!D->getModuleContext()->isStdlibModule()) |
| return false; |
| |
| std::pair<StringRef, bool> KindValue |
| = llvm::StringSwitch<std::pair<StringRef, bool>>(D->getNameStr()) |
| .Case("sizeof", {"size", false}) |
| .Case("alignof", {"alignment", false}) |
| .Case("strideof", {"stride", false}) |
| .Case("sizeofValue", {"size", true}) |
| .Case("alignofValue", {"alignment", true}) |
| .Case("strideofValue", {"stride", true}) |
| .Default({}); |
| |
| if (KindValue.first.empty()) |
| return false; |
| |
| auto Kind = KindValue.first; |
| auto isValue = KindValue.second; |
| |
| auto args = dyn_cast<ParenExpr>(call->getArg()); |
| if (!args) |
| return false; |
| |
| auto subject = args->getSubExpr(); |
| if (!isValue) { |
| // sizeof(x.dynamicType) is equivalent to sizeofValue(x) |
| if (auto DTE = dyn_cast<DynamicTypeExpr>(subject)) { |
| subject = DTE->getBase(); |
| isValue = true; |
| } |
| } |
| |
| EncodedDiagnosticMessage EncodedMessage(Attr->Message); |
| auto diag = TC.diagnose(R.Start, diag::availability_decl_unavailable_msg, |
| D->getFullName(), EncodedMessage.Message); |
| diag.highlight(R); |
| |
| StringRef Prefix = "MemoryLayout<"; |
| StringRef Suffix = ">."; |
| if (isValue) { |
| auto valueType = subject->getType()->getRValueType(); |
| if (!valueType || valueType->is<ErrorType>()) { |
| // If we dont have good argument, We cannot emit fix-it. |
| return true; |
| } |
| |
| // NOTE: We are destructively replacing the source text here. |
| // For instance, `sizeof(x.doSomethig())` => `MemoryLayout<T>.size` where |
| // T is return type of `doSomething()`. If that function have any |
| // side effects, it will break the source. |
| diag.fixItReplace(call->getSourceRange(), |
| (Prefix + valueType->getString() + Suffix + Kind).str()); |
| } else { |
| SourceRange PrefixRange(call->getStartLoc(), args->getLParenLoc()); |
| SourceRange SuffixRange(args->getRParenLoc()); |
| |
| // We must truncate `.self`. |
| // E.g. sizeof(T.self) => MemoryLayout<T>.size |
| if (auto *DSE = dyn_cast<DotSelfExpr>(subject)) |
| SuffixRange.Start = DSE->getDotLoc(); |
| |
| diag |
| .fixItReplace(PrefixRange, Prefix) |
| .fixItReplace(SuffixRange, (Suffix + Kind).str()); |
| } |
| |
| return true; |
| } |
| |
| /// Diagnose uses of unavailable declarations. |
| static void diagAvailability(TypeChecker &TC, const Expr *E, |
| DeclContext *DC) { |
| AvailabilityWalker walker(TC, DC); |
| const_cast<Expr*>(E)->walk(walker); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Per func/init diagnostics |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| class VarDeclUsageChecker : public ASTWalker { |
| TypeChecker &TC; |
| |
| // Keep track of some information about a variable. |
| enum { |
| RK_Read = 1, ///< Whether it was ever read. |
| RK_Written = 2, ///< Whether it was ever written or passed inout. |
| |
| RK_CaptureList = 4 ///< Var is an entry in a capture list. |
| }; |
| |
| /// These are all of the variables that we are tracking. VarDecls get added |
| /// to this when the declaration is seen. We use a MapVector to keep the |
| /// diagnostics emission in deterministic order. |
| llvm::SmallMapVector<VarDecl*, unsigned, 32> VarDecls; |
| |
| /// This is a mapping from an OpaqueValue to the expression that initialized |
| /// it. |
| llvm::SmallDenseMap<OpaqueValueExpr*, Expr*> OpaqueValueMap; |
| |
| /// This is a mapping from VarDecls to the if/while/guard statement that they |
| /// occur in, when they are in a pattern in a StmtCondition. |
| llvm::SmallDenseMap<VarDecl*, LabeledConditionalStmt*> StmtConditionForVD; |
| |
| bool sawError = false; |
| |
| VarDeclUsageChecker(const VarDeclUsageChecker &) = delete; |
| void operator=(const VarDeclUsageChecker &) = delete; |
| |
| public: |
| VarDeclUsageChecker(TypeChecker &TC, AbstractFunctionDecl *AFD) : TC(TC) { |
| // Track the parameters of the function. |
| for (auto PL : AFD->getParameterLists()) |
| for (auto param : *PL) |
| if (shouldTrackVarDecl(param)) |
| VarDecls[param] = 0; |
| |
| } |
| |
| VarDeclUsageChecker(TypeChecker &TC, VarDecl *VD) : TC(TC) { |
| // Track a specific VarDecl |
| VarDecls[VD] = 0; |
| } |
| |
| void suppressDiagnostics() { |
| sawError = true; // set this flag so that no diagnostics will be emitted on delete. |
| } |
| |
| // After we have scanned the entire region, diagnose variables that could be |
| // declared with a narrower usage kind. |
| ~VarDeclUsageChecker(); |
| |
| /// Check to see if the specified VarDecl is part of a larger |
| /// PatternBindingDecl, where some other bound variable was mutated. In this |
| /// case we don't want to generate a "variable never mutated" warning, because |
| /// it would require splitting up the destructuring of the tuple, which is |
| /// more code turmoil than the warning is worth. |
| bool isVarDeclPartOfPBDThatHadSomeMutation(VarDecl *VD) { |
| auto *PBD = VD->getParentPatternBinding(); |
| if (!PBD) return false; |
| |
| bool sawMutation = false; |
| for (const auto &PBE : PBD->getPatternList()) { |
| PBE.getPattern()->forEachVariable([&](VarDecl *VD) { |
| auto it = VarDecls.find(VD); |
| sawMutation |= it != VarDecls.end() && (it->second & RK_Written); |
| }); |
| } |
| return sawMutation; |
| } |
| |
| bool isVarDeclEverWritten(VarDecl *VD) { |
| return (VarDecls[VD] & RK_Written) != 0; |
| } |
| |
| bool shouldTrackVarDecl(VarDecl *VD) { |
| // If the variable is implicit, ignore it. |
| if (VD->isImplicit() || VD->getLoc().isInvalid()) |
| return false; |
| |
| // If the variable is computed, ignore it. |
| if (!VD->hasStorage()) |
| return false; |
| |
| // If the variable was invalid, ignore it and notice that the code is |
| // malformed. |
| if (VD->isInvalid() || !VD->hasType()) { |
| sawError = true; |
| return false; |
| } |
| |
| // If the variable is already unnamed, ignore it. |
| if (!VD->hasName() || VD->getName().str() == "_") |
| return false; |
| |
| return true; |
| } |
| |
| void addMark(Decl *D, unsigned Flag) { |
| auto *vd = dyn_cast<VarDecl>(D); |
| if (!vd) return; |
| |
| auto vdi = VarDecls.find(vd); |
| if (vdi != VarDecls.end()) |
| vdi->second |= Flag; |
| } |
| |
| void markBaseOfAbstractStorageDeclStore(Expr *E, ConcreteDeclRef decl); |
| |
| void markStoredOrInOutExpr(Expr *E, unsigned Flags); |
| |
| // We generally walk into declarations, other than types and nested functions. |
| // FIXME: peek into capture lists of nested functions. |
| bool walkToDeclPre(Decl *D) override { |
| if (isa<TypeDecl>(D)) |
| return false; |
| |
| // If this is a VarDecl, then add it to our list of things to track. |
| if (auto *vd = dyn_cast<VarDecl>(D)) |
| if (shouldTrackVarDecl(vd)) { |
| unsigned defaultFlags = 0; |
| // If this VarDecl is nested inside of a CaptureListExpr, remember that |
| // fact for better diagnostics. |
| if (dyn_cast_or_null<CaptureListExpr>(Parent.getAsExpr())) |
| defaultFlags = RK_CaptureList; |
| VarDecls[vd] |= defaultFlags; |
| } |
| |
| if (auto *afd = dyn_cast<AbstractFunctionDecl>(D)) { |
| // If this is a nested function with a capture list, mark any captured |
| // variables. |
| if (afd->isBodyTypeChecked()) { |
| for (const auto &capture : afd->getCaptureInfo().getCaptures()) |
| addMark(capture.getDecl(), RK_Read|RK_Written); |
| } else { |
| // If the body hasn't been type checked yet, be super-conservative and |
| // mark all variables as used. This can be improved later, e.g. by |
| // walking the untype-checked body to look for things that could |
| // possibly be used. |
| VarDecls.clear(); |
| } |
| |
| // Don't walk into it though, it may not even be type checked yet. |
| return false; |
| } |
| |
| |
| // Note that we ignore the initialization behavior of PatternBindingDecls, |
| // but we do want to walk into them, because we want to see any uses or |
| // other things going on in the initializer expressions. |
| return true; |
| } |
| |
| /// The heavy lifting happens when visiting expressions. |
| std::pair<bool, Expr *> walkToExprPre(Expr *E) override; |
| |
| /// handle #if directives. |
| void handleIfConfig(IfConfigStmt *ICS); |
| |
| /// Custom handling for statements. |
| std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override { |
| // The body of #if statements are not walked into, we need custom processing |
| // for them. |
| if (auto *ICS = dyn_cast<IfConfigStmt>(S)) |
| handleIfConfig(ICS); |
| |
| // Keep track of an association between vardecls and the StmtCondition that |
| // they are bound in for IfStmt, GuardStmt, WhileStmt, etc. |
| if (auto LCS = dyn_cast<LabeledConditionalStmt>(S)) { |
| for (auto &cond : LCS->getCond()) |
| if (auto pat = cond.getPatternOrNull()) { |
| pat->forEachVariable([&](VarDecl *VD) { |
| StmtConditionForVD[VD] = LCS; |
| }); |
| } |
| } |
| |
| return { true, S }; |
| } |
| |
| }; |
| } |
| |
| |
| // After we have scanned the entire region, diagnose variables that could be |
| // declared with a narrower usage kind. |
| VarDeclUsageChecker::~VarDeclUsageChecker() { |
| // If we saw an ErrorExpr somewhere in the body, then we have a malformed AST |
| // and we know stuff got dropped. Instead of producing these diagnostics, |
| // lets let the bigger issues get resolved first. |
| if (sawError) |
| return; |
| |
| for (auto elt : VarDecls) { |
| auto *var = elt.first; |
| unsigned access = elt.second; |
| |
| // If this is a 'let' value, any stores to it are actually initializations, |
| // not mutations. |
| if (var->isLet()) |
| access &= ~RK_Written; |
| |
| // If this variable has WeakStorageType, then it can be mutated in ways we |
| // don't know. |
| if (var->getType()->is<WeakStorageType>()) |
| access |= RK_Written; |
| |
| // If this is a vardecl with 'inout' type, then it is an inout argument to a |
| // function, never diagnose anything related to it. |
| if (var->getType()->is<InOutType>()) |
| continue; |
| |
| // Consider parameters to always have been read. It is common to name a |
| // parameter and not use it (e.g. because you are an override or want the |
| // named keyword, etc). Warning to rewrite it to _ is more annoying than |
| // it is useful. |
| if (isa<ParamDecl>(var)) |
| access |= RK_Read; |
| |
| // Diagnose variables that were never used (other than their |
| // initialization). |
| // |
| if ((access & (RK_Read|RK_Written)) == 0) { |
| // If this is a member in a capture list, just say it is unused. We could |
| // produce a fixit hint with a parent map, but this is a lot of effort for |
| // a narrow case. |
| if (access & RK_CaptureList) { |
| TC.diagnose(var->getLoc(), diag::capture_never_used, |
| var->getName()); |
| continue; |
| } |
| |
| // If the source of the VarDecl is a trivial PatternBinding with only a |
| // single binding, rewrite the whole thing into an assignment. |
| // let x = foo() |
| // -> |
| // _ = foo() |
| if (auto *pbd = var->getParentPatternBinding()) |
| if (pbd->getSingleVar() == var && pbd->getInit(0) != nullptr) { |
| unsigned varKind = var->isLet(); |
| TC.diagnose(var->getLoc(), diag::pbd_never_used, |
| var->getName(), varKind) |
| .fixItReplace(SourceRange(pbd->getLoc(), var->getLoc()), "_"); |
| continue; |
| } |
| |
| // If the variable is defined in a pattern in an if/while/guard statement, |
| // see if we can produce a tuned fixit. When we have something like: |
| // |
| // if let x = <expr> { |
| // |
| // we prefer to rewrite it to: |
| // |
| // if <expr> != nil { |
| // |
| if (auto SC = StmtConditionForVD[var]) { |
| // We only handle the "if let" case right now, since it is vastly the |
| // most common situation that people run into. |
| if (SC->getCond().size() == 1) { |
| auto pattern = SC->getCond()[0].getPattern(); |
| if (auto OSP = dyn_cast<OptionalSomePattern>(pattern)) |
| if (auto LP = dyn_cast<VarPattern>(OSP->getSubPattern())) |
| if (isa<NamedPattern>(LP->getSubPattern())) { |
| auto initExpr = SC->getCond()[0].getInitializer(); |
| if (initExpr->getStartLoc().isValid()) { |
| unsigned noParens = initExpr->canAppendCallParentheses(); |
| |
| // If the subexpr is an "as?" cast, we can rewrite it to |
| // be an "is" test. |
| bool isIsTest = false; |
| if (isa<ConditionalCheckedCastExpr>(initExpr) && |
| !initExpr->isImplicit()) { |
| noParens = isIsTest = true; |
| } |
| |
| auto diagIF = TC.diagnose(var->getLoc(), |
| diag::pbd_never_used_stmtcond, |
| var->getName()); |
| auto introducerLoc = SC->getCond()[0].getIntroducerLoc(); |
| diagIF.fixItReplaceChars(introducerLoc, |
| initExpr->getStartLoc(), |
| &"("[noParens]); |
| |
| if (isIsTest) { |
| // If this was an "x as? T" check, rewrite it to "x is T". |
| auto CCE = cast<ConditionalCheckedCastExpr>(initExpr); |
| diagIF.fixItReplace(SourceRange(CCE->getLoc(), |
| CCE->getQuestionLoc()), |
| "is"); |
| } else { |
| diagIF.fixItInsertAfter(initExpr->getEndLoc(), |
| &") != nil"[noParens]); |
| } |
| continue; |
| } |
| } |
| } |
| } |
| |
| // Otherwise, this is something more complex, perhaps |
| // let (a,b) = foo() |
| // Just rewrite the one variable with a _. |
| unsigned varKind = var->isLet(); |
| TC.diagnose(var->getLoc(), diag::variable_never_used, |
| var->getName(), varKind) |
| .fixItReplace(var->getLoc(), "_"); |
| continue; |
| } |
| |
| // If this is a mutable 'var', and it was never written to, suggest |
| // upgrading to 'let'. We do this even for a parameter. |
| if (!var->isLet() && (access & RK_Written) == 0 && |
| // Don't warn if we have something like "let (x,y) = ..." and 'y' was |
| // never mutated, but 'x' was. |
| !isVarDeclPartOfPBDThatHadSomeMutation(var)) { |
| SourceLoc FixItLoc; |
| |
| // Try to find the location of the 'var' so we can produce a fixit. If |
| // this is a simple PatternBinding, use its location. |
| if (auto *PBD = var->getParentPatternBinding()) { |
| if (PBD->getSingleVar() == var) |
| FixItLoc = PBD->getLoc(); |
| } else if (auto *pattern = var->getParentPattern()) { |
| VarPattern *foundVP = nullptr; |
| pattern->forEachNode([&](Pattern *P) { |
| if (auto *VP = dyn_cast<VarPattern>(P)) |
| if (VP->getSingleVar() == var) |
| foundVP = VP; |
| }); |
| |
| if (foundVP && !foundVP->isLet()) |
| FixItLoc = foundVP->getLoc(); |
| } |
| |
| // If this is a parameter explicitly marked 'var', remove it. |
| unsigned varKind = isa<ParamDecl>(var); |
| if (FixItLoc.isInvalid()) |
| TC.diagnose(var->getLoc(), diag::variable_never_mutated, |
| var->getName(), varKind); |
| else |
| TC.diagnose(var->getLoc(), diag::variable_never_mutated, |
| var->getName(), varKind) |
| .fixItReplace(FixItLoc, "let"); |
| continue; |
| } |
| |
| // If this is a variable that was only written to, emit a warning. |
| if ((access & RK_Read) == 0) { |
| TC.diagnose(var->getLoc(), diag::variable_never_read, var->getName(), |
| isa<ParamDecl>(var)); |
| continue; |
| } |
| } |
| } |
| |
| /// Handle a store to "x.y" where 'base' is the expression for x and 'decl' is |
| /// the decl for 'y'. |
| void VarDeclUsageChecker:: |
| markBaseOfAbstractStorageDeclStore(Expr *base, ConcreteDeclRef decl) { |
| // If the base is a class or an rvalue, then this store just loads the base. |
| if (base->getType()->isAnyClassReferenceType() || |
| !(base->getType()->isLValueType() || base->getType()->is<InOutType>())) { |
| base->walk(*this); |
| return; |
| } |
| |
| // If the store is to a non-mutating member, then this is just a load, even |
| // if the base is an inout expr. |
| auto *ASD = cast<AbstractStorageDecl>(decl.getDecl()); |
| if (ASD->isSettable(nullptr) && ASD->isSetterNonMutating()) { |
| // Sema conservatively converts the base to inout expr when it is an lvalue. |
| // Look through it because we know it isn't actually doing a load/store. |
| if (auto *ioe = dyn_cast<InOutExpr>(base)) |
| base = ioe->getSubExpr(); |
| base->walk(*this); |
| return; |
| } |
| |
| // Otherwise this is a read and write of the base. |
| return markStoredOrInOutExpr(base, RK_Written|RK_Read); |
| } |
| |
| |
| void VarDeclUsageChecker::markStoredOrInOutExpr(Expr *E, unsigned Flags) { |
| // Sema leaves some subexpressions null, which seems really unfortunate. It |
| // should replace them with ErrorExpr. |
| if (E == nullptr || !E->getType() || E->getType()->is<ErrorType>()) { |
| sawError = true; |
| return; |
| } |
| |
| // Ignore parens and other easy cases. |
| E = E->getSemanticsProvidingExpr(); |
| |
| // If we found a decl that is being assigned to, then mark it. |
| if (auto *DRE = dyn_cast<DeclRefExpr>(E)) { |
| addMark(DRE->getDecl(), Flags); |
| return; |
| } |
| |
| if (auto *TE = dyn_cast<TupleExpr>(E)) { |
| for (auto &elt : TE->getElements()) |
| markStoredOrInOutExpr(elt, Flags); |
| return; |
| } |
| |
| // If this is an assignment into a mutating subscript lvalue expr, then we |
| // are mutating the base expression. We also need to visit the index |
| // expressions as loads though. |
| if (auto *SE = dyn_cast<SubscriptExpr>(E)) { |
| // The index of the subscript is evaluated as an rvalue. |
| SE->getIndex()->walk(*this); |
| if (SE->hasDecl()) |
| markBaseOfAbstractStorageDeclStore(SE->getBase(), SE->getDecl()); |
| else // FIXME: Should not be needed! |
| markStoredOrInOutExpr(SE->getBase(), RK_Written|RK_Read); |
| |
| return; |
| } |
| |
| if (auto *ioe = dyn_cast<InOutExpr>(E)) |
| return markStoredOrInOutExpr(ioe->getSubExpr(), RK_Written|RK_Read); |
| |
| if (auto *MRE = dyn_cast<MemberRefExpr>(E)) { |
| markBaseOfAbstractStorageDeclStore(MRE->getBase(), MRE->getMember()); |
| return; |
| } |
| |
| if (auto *TEE = dyn_cast<TupleElementExpr>(E)) |
| return markStoredOrInOutExpr(TEE->getBase(), Flags); |
| |
| if (auto *FVE = dyn_cast<ForceValueExpr>(E)) |
| return markStoredOrInOutExpr(FVE->getSubExpr(), Flags); |
| |
| if (auto *BOE = dyn_cast<BindOptionalExpr>(E)) |
| return markStoredOrInOutExpr(BOE->getSubExpr(), Flags); |
| |
| // If this is an OpaqueValueExpr that we've seen a mapping for, jump to the |
| // mapped value. |
| if (auto *OVE = dyn_cast<OpaqueValueExpr>(E)) |
| if (auto *expr = OpaqueValueMap[OVE]) |
| return markStoredOrInOutExpr(expr, Flags); |
| |
| // If we don't know what kind of expression this is, assume it's a reference |
| // and mark it as a read. |
| E->walk(*this); |
| } |
| |
| |
| |
| /// The heavy lifting happens when visiting expressions. |
| std::pair<bool, Expr *> VarDeclUsageChecker::walkToExprPre(Expr *E) { |
| // Sema leaves some subexpressions null, which seems really unfortunate. It |
| // should replace them with ErrorExpr. |
| if (E == nullptr || !E->getType() || E->getType()->is<ErrorType>()) { |
| sawError = true; |
| return { false, E }; |
| } |
| |
| // If this is a DeclRefExpr found in a random place, it is a load of the |
| // vardecl. |
| if (auto *DRE = dyn_cast<DeclRefExpr>(E)) |
| addMark(DRE->getDecl(), RK_Read); |
| |
| // If this is an AssignExpr, see if we're mutating something that we know |
| // about. |
| if (auto *assign = dyn_cast<AssignExpr>(E)) { |
| markStoredOrInOutExpr(assign->getDest(), RK_Written); |
| |
| // Don't walk into the LHS of the assignment, only the RHS. |
| assign->getSrc()->walk(*this); |
| return { false, E }; |
| } |
| |
| // '&x' is a read and write of 'x'. |
| if (auto *io = dyn_cast<InOutExpr>(E)) { |
| markStoredOrInOutExpr(io->getSubExpr(), RK_Read|RK_Written); |
| // Don't bother walking into this. |
| return { false, E }; |
| } |
| |
| // If we see an OpenExistentialExpr, remember the mapping for its OpaqueValue. |
| if (auto *oee = dyn_cast<OpenExistentialExpr>(E)) |
| OpaqueValueMap[oee->getOpaqueValue()] = oee->getExistentialValue(); |
| |
| // If we saw an ErrorExpr, take note of this. |
| if (isa<ErrorExpr>(E)) |
| sawError = true; |
| |
| return { true, E }; |
| } |
| |
| /// handle #if directives. All of the active clauses are already walked by the |
| /// AST walker, but we also want to handle the inactive ones to avoid false |
| /// positives. |
| void VarDeclUsageChecker::handleIfConfig(IfConfigStmt *ICS) { |
| struct ConservativeDeclMarker : public ASTWalker { |
| VarDeclUsageChecker &VDUC; |
| ConservativeDeclMarker(VarDeclUsageChecker &VDUC) : VDUC(VDUC) {} |
| |
| Expr *walkToExprPost(Expr *E) override { |
| // If we see a bound reference to a decl in an inactive #if block, then |
| // conservatively mark it read and written. This will silence "variable |
| // unused" and "could be marked let" warnings for it. |
| if (auto *DRE = dyn_cast<DeclRefExpr>(E)) |
| VDUC.addMark(DRE->getDecl(), RK_Read|RK_Written); |
| return E; |
| } |
| }; |
| |
| for (auto &clause : ICS->getClauses()) { |
| // Active clauses are handled by the normal AST walk. |
| if (clause.isActive) continue; |
| |
| for (auto elt : clause.Elements) |
| elt.walk(ConservativeDeclMarker(*this)); |
| } |
| } |
| |
| /// Perform diagnostics for func/init/deinit declarations. |
| void swift::performAbstractFuncDeclDiagnostics(TypeChecker &TC, |
| AbstractFunctionDecl *AFD) { |
| assert(AFD->getBody() && "Need a body to check"); |
| |
| // Don't produce these diagnostics for implicitly generated code. |
| if (AFD->getLoc().isInvalid() || AFD->isImplicit() || AFD->isInvalid()) |
| return; |
| |
| // Check for unused variables, as well as variables that are could be |
| // declared as constants. |
| AFD->getBody()->walk(VarDeclUsageChecker(TC, AFD)); |
| } |
| |
| /// Diagnose C style for loops. |
| |
| namespace { |
| enum class OperatorKind : char { |
| Greater, |
| Smaller, |
| NotEqual, |
| }; |
| |
| static Expr *endConditionValueForConvertingCStyleForLoop(const ForStmt *FS, |
| VarDecl *loopVar, OperatorKind &OpKind) { |
| auto *Cond = FS->getCond().getPtrOrNull(); |
| if (!Cond) |
| return nullptr; |
| auto callExpr = dyn_cast<CallExpr>(Cond); |
| if (!callExpr) |
| return nullptr; |
| auto dotSyntaxExpr = dyn_cast<DotSyntaxCallExpr>(callExpr->getFn()); |
| if (!dotSyntaxExpr) |
| return nullptr; |
| auto binaryExpr = dyn_cast<BinaryExpr>(dotSyntaxExpr->getBase()); |
| if (!binaryExpr) |
| return nullptr; |
| auto binaryFuncExpr = dyn_cast<DeclRefExpr>(binaryExpr->getFn()); |
| if (!binaryFuncExpr) |
| return nullptr; |
| |
| // Verify that the condition is a simple != or < comparison to the loop variable. |
| auto comparisonOpName = binaryFuncExpr->getDecl()->getNameStr(); |
| if (comparisonOpName == "!=") |
| OpKind = OperatorKind::NotEqual; |
| else if (comparisonOpName == "<") |
| OpKind = OperatorKind::Smaller; |
| else if (comparisonOpName == ">") |
| OpKind = OperatorKind::Greater; |
| else |
| return nullptr; |
| |
| auto args = binaryExpr->getArg()->getElements(); |
| auto loadExpr = dyn_cast<LoadExpr>(args[0]); |
| if (!loadExpr) |
| return nullptr; |
| auto declRefExpr = dyn_cast<DeclRefExpr>(loadExpr->getSubExpr()); |
| if (!declRefExpr) |
| return nullptr; |
| if (declRefExpr->getDecl() != loopVar) |
| return nullptr; |
| return args[1]; |
| } |
| |
| static bool unaryOperatorCheckForConvertingCStyleForLoop(const ForStmt *FS, |
| VarDecl *loopVar, |
| StringRef OpName) { |
| auto *Increment = FS->getIncrement().getPtrOrNull(); |
| if (!Increment) |
| return false; |
| ApplyExpr *unaryExpr = dyn_cast<PrefixUnaryExpr>(Increment); |
| if (!unaryExpr) |
| unaryExpr = dyn_cast<PostfixUnaryExpr>(Increment); |
| if (!unaryExpr) |
| return false; |
| auto inoutExpr = dyn_cast<InOutExpr>(unaryExpr->getArg()); |
| if (!inoutExpr) |
| return false; |
| auto incrementDeclRefExpr = dyn_cast<DeclRefExpr>(inoutExpr->getSubExpr()); |
| if (!incrementDeclRefExpr) |
| return false; |
| auto unaryFuncExpr = dyn_cast<DeclRefExpr>(unaryExpr->getFn()); |
| if (!unaryFuncExpr) |
| return false; |
| if (unaryFuncExpr->getDecl()->getNameStr() != OpName) |
| return false; |
| return incrementDeclRefExpr->getDecl() == loopVar; |
| } |
| |
| |
| static bool unaryIncrementForConvertingCStyleForLoop(const ForStmt *FS, |
| VarDecl *loopVar) { |
| return unaryOperatorCheckForConvertingCStyleForLoop(FS, loopVar, "++"); |
| } |
| |
| static bool unaryDecrementForConvertingCStyleForLoop(const ForStmt *FS, |
| VarDecl *loopVar) { |
| return unaryOperatorCheckForConvertingCStyleForLoop(FS, loopVar, "--"); |
| } |
| |
| static bool binaryOperatorCheckForConvertingCStyleForLoop(TypeChecker &TC, |
| const ForStmt *FS, |
| VarDecl *loopVar, |
| StringRef OpName) { |
| auto *Increment = FS->getIncrement().getPtrOrNull(); |
| if (!Increment) |
| return false; |
| ApplyExpr *binaryExpr = dyn_cast<BinaryExpr>(Increment); |
| if (!binaryExpr) |
| return false; |
| auto binaryFuncExpr = dyn_cast<DeclRefExpr>(binaryExpr->getFn()); |
| if (!binaryFuncExpr) |
| return false; |
| if (binaryFuncExpr->getDecl()->getNameStr() != OpName) |
| return false; |
| auto argTupleExpr = dyn_cast<TupleExpr>(binaryExpr->getArg()); |
| if (!argTupleExpr) |
| return false; |
| auto addOneConstExpr = argTupleExpr->getElement(1); |
| |
| // Rather than unwrapping expressions all the way down implicit constructors, etc, just check that the |
| // source text for the += argument is "1". |
| SourceLoc constEndLoc = Lexer::getLocForEndOfToken(TC.Context.SourceMgr, addOneConstExpr->getEndLoc()); |
| auto range = CharSourceRange(TC.Context.SourceMgr, addOneConstExpr->getStartLoc(), constEndLoc); |
| if (range.str() != "1") |
| return false; |
| |
| auto inoutExpr = dyn_cast<InOutExpr>(argTupleExpr->getElement(0)); |
| if (!inoutExpr) |
| return false; |
| auto declRefExpr = dyn_cast<DeclRefExpr>(inoutExpr->getSubExpr()); |
| if (!declRefExpr) |
| return false; |
| return declRefExpr->getDecl() == loopVar; |
| |
| } |
| |
| static bool plusEqualOneIncrementForConvertingCStyleForLoop(TypeChecker &TC, |
| const ForStmt *FS, |
| VarDecl *loopVar) { |
| return binaryOperatorCheckForConvertingCStyleForLoop(TC, FS, loopVar, "+="); |
| } |
| |
| static bool minusEqualOneDecrementForConvertingCStyleForLoop(TypeChecker &TC, |
| const ForStmt *FS, |
| VarDecl *loopVar) { |
| return binaryOperatorCheckForConvertingCStyleForLoop(TC, FS, loopVar, "-="); |
| } |
| |
| static void checkCStyleForLoop(TypeChecker &TC, const ForStmt *FS) { |
| // If we're missing semi-colons we'll already be erroring out, and this may |
| // not even have been intended as C-style. |
| if (FS->getFirstSemicolonLoc().isInvalid() || FS->getSecondSemicolonLoc().isInvalid()) |
| return; |
| |
| InFlightDiagnostic diagnostic = TC.diagnose(FS->getStartLoc(), diag::deprecated_c_style_for_stmt); |
| |
| // Try to construct a fix it using for-each: |
| |
| // Verify that there is only one loop variable, and it is declared here. |
| auto initializers = FS->getInitializerVarDecls(); |
| PatternBindingDecl *loopVarDecl = initializers.size() == 2 ? |
| dyn_cast<PatternBindingDecl>(initializers[0]) : nullptr; |
| if (!loopVarDecl || loopVarDecl->getNumPatternEntries() != 1) |
| return; |
| |
| VarDecl *loopVar = dyn_cast<VarDecl>(initializers[1]); |
| Expr *startValue = loopVarDecl->getInit(0); |
| OperatorKind OpKind; |
| Expr *endValue = endConditionValueForConvertingCStyleForLoop(FS, loopVar, OpKind); |
| bool strideByOne = unaryIncrementForConvertingCStyleForLoop(FS, loopVar) || |
| plusEqualOneIncrementForConvertingCStyleForLoop(TC, FS, loopVar); |
| bool strideBackByOne = unaryDecrementForConvertingCStyleForLoop(FS, loopVar) || |
| minusEqualOneDecrementForConvertingCStyleForLoop(TC, FS, loopVar); |
| |
| if (!loopVar || !startValue || !endValue || (!strideByOne && !strideBackByOne)) |
| return; |
| |
| assert(strideBackByOne != strideByOne && "cannot be both increment and decrement."); |
| |
| // Verify that the loop variable is invariant inside the body. |
| VarDeclUsageChecker checker(TC, loopVar); |
| checker.suppressDiagnostics(); |
| FS->getBody()->walk(checker); |
| |
| if (checker.isVarDeclEverWritten(loopVar)) { |
| diagnostic.flush(); |
| return; |
| } |
| |
| SourceLoc loopPatternEnd = |
| Lexer::getLocForEndOfToken(TC.Context.SourceMgr, |
| loopVarDecl->getPattern(0)->getEndLoc()); |
| SourceLoc endOfIncrementLoc = |
| Lexer::getLocForEndOfToken(TC.Context.SourceMgr, |
| FS->getIncrement().getPtrOrNull()->getEndLoc()); |
| |
| if (strideByOne && OpKind != OperatorKind::Greater) { |
| diagnostic |
| .fixItRemoveChars(loopVarDecl->getLoc(), loopVar->getLoc()) |
| .fixItReplaceChars(loopPatternEnd, startValue->getStartLoc(), " in ") |
| .fixItReplaceChars(FS->getFirstSemicolonLoc(), endValue->getStartLoc(), |
| " ..< ") |
| .fixItRemoveChars(FS->getSecondSemicolonLoc(), endOfIncrementLoc); |
| return; |
| } else if (strideBackByOne && OpKind != OperatorKind::Smaller) { |
| SourceLoc startValueEnd = Lexer::getLocForEndOfToken(TC.Context.SourceMgr, |
| startValue->getEndLoc()); |
| |
| StringRef endValueStr = CharSourceRange(TC.Context.SourceMgr, endValue->getStartLoc(), |
| Lexer::getLocForEndOfToken(TC.Context.SourceMgr, endValue->getEndLoc())).str(); |
| |
| diagnostic |
| .fixItRemoveChars(loopVarDecl->getLoc(), loopVar->getLoc()) |
| .fixItReplaceChars(loopPatternEnd, startValue->getStartLoc(), " in ") |
| .fixItInsert(startValue->getStartLoc(), (llvm::Twine("((") + endValueStr + " + 1)...").str()) |
| .fixItInsert(startValueEnd, ").reversed()") |
| .fixItRemoveChars(FS->getFirstSemicolonLoc(), endOfIncrementLoc); |
| } |
| } |
| }// Anonymous namespace end. |
| |
| // Perform MiscDiagnostics on Switch Statements. |
| static void checkSwitch(TypeChecker &TC, const SwitchStmt *stmt) { |
| // We want to warn about "case .Foo, .Bar where 1 != 100:" since the where |
| // clause only applies to the second case, and this is surprising. |
| for (auto cs : stmt->getCases()) { |
| // The case statement can have multiple case items, each can have a where. |
| // If we find a "where", and there is a preceding item without a where, and |
| // if they are on the same source line, then warn. |
| auto items = cs->getCaseLabelItems(); |
| |
| // Don't do any work for the vastly most common case. |
| if (items.size() == 1) continue; |
| |
| // Ignore the first item, since it can't have preceding ones. |
| for (unsigned i = 1, e = items.size(); i != e; ++i) { |
| // Must have a where clause. |
| auto where = items[i].getGuardExpr(); |
| if (!where) |
| continue; |
| |
| // Preceding item must not. |
| if (items[i-1].getGuardExpr()) |
| continue; |
| |
| // Must be on the same source line. |
| auto prevLoc = items[i-1].getStartLoc(); |
| auto thisLoc = items[i].getStartLoc(); |
| if (prevLoc.isInvalid() || thisLoc.isInvalid()) |
| continue; |
| |
| auto &SM = TC.Context.SourceMgr; |
| auto prevLineCol = SM.getLineAndColumn(prevLoc); |
| if (SM.getLineNumber(thisLoc) != prevLineCol.first) |
| continue; |
| |
| TC.diagnose(items[i].getWhereLoc(), diag::where_on_one_item) |
| .highlight(items[i].getPattern()->getSourceRange()) |
| .highlight(where->getSourceRange()); |
| |
| // Whitespace it out to the same column as the previous item. |
| std::string whitespace(prevLineCol.second-1, ' '); |
| TC.diagnose(thisLoc, diag::add_where_newline) |
| .fixItInsert(thisLoc, "\n"+whitespace); |
| |
| auto whereRange = SourceRange(items[i].getWhereLoc(), |
| where->getEndLoc()); |
| auto charRange = Lexer::getCharSourceRangeFromSourceRange(SM, whereRange); |
| auto whereText = SM.extractText(charRange); |
| TC.diagnose(prevLoc, diag::duplicate_where) |
| .fixItInsertAfter(items[i-1].getEndLoc(), " " + whereText.str()) |
| .highlight(items[i-1].getSourceRange()); |
| } |
| } |
| } |
| |
| // Perform checkStmtConditionTrailingClosure for single expression. |
| static void checkStmtConditionTrailingClosure(TypeChecker &TC, const Expr *E) { |
| if (E == nullptr || isa<ErrorExpr>(E)) return; |
| |
| // Shallow walker. just dig into implicit expression. |
| class DiagnoseWalker : public ASTWalker { |
| TypeChecker &TC; |
| |
| void diagnoseIt(const CallExpr *E) { |
| auto argsExpr = E->getArg(); |
| auto argsTy = argsExpr->getType(); |
| |
| // Ignore invalid argument type. Some diagnostics are already emitted. |
| if (!argsTy || argsTy->is<ErrorType>()) return; |
| |
| if (auto TSE = dyn_cast<TupleShuffleExpr>(argsExpr)) |
| argsExpr = TSE->getSubExpr(); |
| |
| SmallString<16> replacement; |
| SourceLoc lastLoc; |
| SourceRange closureRange; |
| if (auto PE = dyn_cast<ParenExpr>(argsExpr)) { |
| // Ignore non-trailing-closure. |
| if (!PE->hasTrailingClosure()) return; |
| |
| closureRange = PE->getSubExpr()->getSourceRange(); |
| lastLoc = PE->getLParenLoc(); |
| if (lastLoc.isValid()) { |
| // Empty paren: e.g. if funcName() { 1 } { ... } |
| replacement = ""; |
| } else { |
| // Bare trailing closure: e.g. if funcName { 1 } { ... } |
| replacement = "("; |
| lastLoc = E->getFn()->getEndLoc(); |
| } |
| } else if (auto TE = dyn_cast<TupleExpr>(argsExpr)) { |
| // Ignore non-trailing-closure. |
| if (!TE->hasTrailingClosure()) return; |
| |
| // Tuple + trailing closure: e.g. if funcName(x: 1) { 1 } { ... } |
| auto numElements = TE->getNumElements(); |
| assert(numElements >= 2 && "Unexpected num of elements in TupleExpr"); |
| closureRange = TE->getElement(numElements - 1)->getSourceRange(); |
| lastLoc = TE->getElement(numElements - 2)->getEndLoc(); |
| replacement = ", "; |
| } else { |
| // Can't be here. |
| return; |
| } |
| |
| // Add argument label of the closure that is going to be enclosed in |
| // parens. |
| if (auto TT = argsTy->getAs<TupleType>()) { |
| assert(TT->getNumElements() != 0 && "Unexpected empty TupleType"); |
| auto closureLabel = TT->getElement(TT->getNumElements() - 1).getName(); |
| if (!closureLabel.empty()) { |
| replacement += closureLabel.str(); |
| replacement += ": "; |
| } |
| } |
| |
| // Emit diagnostics. |
| lastLoc = Lexer::getLocForEndOfToken(TC.Context.SourceMgr, lastLoc); |
| TC.diagnose(closureRange.Start, diag::trailing_closure_requires_parens) |
| .fixItReplaceChars(lastLoc, closureRange.Start, replacement) |
| .fixItInsertAfter(closureRange.End, ")"); |
| } |
| |
| public: |
| DiagnoseWalker(TypeChecker &tc) : TC(tc) { } |
| |
| virtual std::pair<bool, Expr *> walkToExprPre(Expr *E) override { |
| // Dig into implicit expression. |
| if (E->isImplicit()) return { true, E }; |
| // Diagnose call expression. |
| if (auto CE = dyn_cast<CallExpr>(E)) |
| diagnoseIt(CE); |
| // Don't dig any further. |
| return { false, E }; |
| } |
| }; |
| |
| DiagnoseWalker Walker(TC); |
| const_cast<Expr *>(E)->walk(Walker); |
| } |
| |
| /// \brief Diagnose trailing closure in statement-conditions. |
| /// |
| /// Conditional statements, including 'for' or `switch` doesn't allow ambiguous |
| /// trailing closures in these conditions part. Even if the parser can recover |
| /// them, we force them to disambiguate. |
| // |
| /// E.g.: |
| /// if let _ = arr?.map {$0+1} { ... } |
| /// for _ in numbers.filter {$0 > 4} { ... } |
| static void checkStmtConditionTrailingClosure(TypeChecker &TC, const Stmt *S) { |
| if (auto LCS = dyn_cast<LabeledConditionalStmt>(S)) { |
| for (auto elt : LCS->getCond()) { |
| if (elt.getKind() == StmtConditionElement::CK_PatternBinding) |
| checkStmtConditionTrailingClosure(TC, elt.getInitializer()); |
| else if (elt.getKind() == StmtConditionElement::CK_Boolean) |
| checkStmtConditionTrailingClosure(TC, elt.getBoolean()); |
| // No trailing closure for CK_Availability: e.g. `if #available() {}`. |
| } |
| } else if (auto SS = dyn_cast<SwitchStmt>(S)) { |
| checkStmtConditionTrailingClosure(TC, SS->getSubjectExpr()); |
| } else if (auto FES = dyn_cast<ForEachStmt>(S)) { |
| checkStmtConditionTrailingClosure(TC, FES->getSequence()); |
| checkStmtConditionTrailingClosure(TC, FES->getWhere()); |
| } else if (auto DCS = dyn_cast<DoCatchStmt>(S)) { |
| for (auto CS : DCS->getCatches()) |
| checkStmtConditionTrailingClosure(TC, CS->getGuardExpr()); |
| } |
| } |
| |
| static Optional<ObjCSelector> |
| parseObjCSelector(ASTContext &ctx, StringRef string) { |
| // Find the first colon. |
| auto colonPos = string.find(':'); |
| |
| // If there is no colon, we have a nullary selector. |
| if (colonPos == StringRef::npos) { |
| if (string.empty() || !Lexer::isIdentifier(string)) return None; |
| return ObjCSelector(ctx, 0, { ctx.getIdentifier(string) }); |
| } |
| |
| SmallVector<Identifier, 2> pieces; |
| do { |
| // Check whether we have a valid selector piece. |
| auto piece = string.substr(0, colonPos); |
| if (piece.empty()) { |
| pieces.push_back(Identifier()); |
| } else { |
| if (!Lexer::isIdentifier(piece)) return None; |
| pieces.push_back(ctx.getIdentifier(piece)); |
| } |
| |
| // Move to the next piece. |
| string = string.substr(colonPos+1); |
| colonPos = string.find(':'); |
| } while (colonPos != StringRef::npos); |
| |
| // If anything remains of the string, it's not a selector. |
| if (!string.empty()) return None; |
| |
| return ObjCSelector(ctx, pieces.size(), pieces); |
| } |
| |
| |
| namespace { |
| |
| class ObjCSelectorWalker : public ASTWalker { |
| TypeChecker &TC; |
| const DeclContext *DC; |
| Type SelectorTy; |
| |
| /// Determine whether a reference to the given method via its |
| /// enclosing class/protocol is ambiguous (and, therefore, needs to |
| /// be disambiguated with a coercion). |
| bool isSelectorReferenceAmbiguous(AbstractFunctionDecl *method) { |
| // Determine the name we would search for. If there are no |
| // argument names, our lookup will be based solely on the base |
| // name. |
| DeclName lookupName = method->getFullName(); |
| if (lookupName.getArgumentNames().empty()) |
| lookupName = lookupName.getBaseName(); |
| |
| // Look for members with the given name. |
| auto nominal = |
| method->getDeclContext()->getAsNominalTypeOrNominalTypeExtensionContext(); |
| auto result = TC.lookupMember(const_cast<DeclContext *>(DC), |
| nominal->getInterfaceType(), lookupName, |
| (defaultMemberLookupOptions | |
| NameLookupFlags::KnownPrivate)); |
| |
| // If we didn't find multiple methods, there is no ambiguity. |
| if (result.size() < 2) return false; |
| |
| // If we found more than two methods, it's ambiguous. |
| if (result.size() > 2) return true; |
| |
| // Dig out the methods. |
| auto firstMethod = dyn_cast<FuncDecl>(result[0].Decl); |
| auto secondMethod = dyn_cast<FuncDecl>(result[1].Decl); |
| if (!firstMethod || !secondMethod) return true; |
| |
| // If one is a static/class method and the other is not... |
| if (firstMethod->isStatic() == secondMethod->isStatic()) return true; |
| |
| // ... overload resolution will prefer the static method. Check |
| // that it has the correct selector. We don't even care that it's |
| // the same method we're asking for, just that it has the right |
| // selector. |
| FuncDecl *staticMethod = |
| firstMethod->isStatic() ? firstMethod : secondMethod; |
| return staticMethod->getObjCSelector() != method->getObjCSelector(); |
| } |
| |
| public: |
| ObjCSelectorWalker(TypeChecker &tc, const DeclContext *dc, Type selectorTy) |
| : TC(tc), DC(dc), SelectorTy(selectorTy) { } |
| |
| virtual std::pair<bool, Expr *> walkToExprPre(Expr *expr) override { |
| StringLiteralExpr *stringLiteral = dyn_cast<StringLiteralExpr>(expr); |
| bool fromStringLiteral = false; |
| bool hadParens = false; |
| if (stringLiteral) { |
| // Is this a string literal that has type 'Selector'. |
| if (!stringLiteral->getType() || |
| !stringLiteral->getType()->isEqual(SelectorTy)) |
| return { true, expr }; |
| |
| fromStringLiteral = true; |
| |
| // FIXME: hadParens |
| } else { |
| // Is this an initialization of 'Selector'? |
| auto call = dyn_cast<CallExpr>(expr); |
| if (!call) return { true, expr }; |
| |
| // That produce Selectors. |
| if (!call->getType() || !call->getType()->isEqual(SelectorTy)) |
| return { true, expr }; |
| |
| // Via a constructor. |
| ConstructorDecl *ctor = nullptr; |
| if (auto ctorRefCall = dyn_cast<ConstructorRefCallExpr>(call->getFn())) { |
| if (auto ctorRef = dyn_cast<DeclRefExpr>(ctorRefCall->getFn())) |
| ctor = dyn_cast<ConstructorDecl>(ctorRef->getDecl()); |
| else if (auto otherCtorRef = |
| dyn_cast<OtherConstructorDeclRefExpr>(ctorRefCall->getFn())) |
| ctor = otherCtorRef->getDecl(); |
| } |
| |
| if (!ctor) return { true, expr }; |
| |
| // Make sure the constructor is within Selector. |
| auto ctorContextType = ctor->getDeclContext()->getDeclaredTypeOfContext(); |
| if (!ctorContextType || !ctorContextType->isEqual(SelectorTy)) |
| return { true, expr }; |
| |
| auto argNames = ctor->getFullName().getArgumentNames(); |
| if (argNames.size() != 1) return { true, expr }; |
| |
| // Is this the init(stringLiteral:) initializer or init(_:) initializer? |
| if (argNames[0] == TC.Context.Id_stringLiteral) |
| fromStringLiteral = true; |
| else if (!argNames[0].empty()) |
| return { true, expr }; |
| |
| Expr *arg = call->getArg(); |
| |
| if (auto paren = dyn_cast<ParenExpr>(arg)) |
| arg = paren->getSubExpr(); |
| else if (auto tuple = dyn_cast<TupleExpr>(arg)) |
| arg = tuple->getElement(0); |
| else |
| return { true, expr }; |
| |
| // Track whether we had parentheses around the string literal. |
| if (auto paren = dyn_cast<ParenExpr>(arg)) { |
| hadParens = true; |
| arg = paren->getSubExpr(); |
| } |
| |
| // Check whether we have a string literal. |
| stringLiteral = dyn_cast<StringLiteralExpr>(arg); |
| if (!stringLiteral) return { true, expr }; |
| } |
| |
| /// Retrieve the parent expression that coerces to Selector, if |
| /// there is one. |
| auto getParentCoercion = [&]() -> CoerceExpr * { |
| auto parentExpr = Parent.getAsExpr(); |
| if (!parentExpr) return nullptr; |
| |
| auto coerce = dyn_cast<CoerceExpr>(parentExpr); |
| if (!coerce) return nullptr; |
| |
| if (coerce->getType() && coerce->getType()->isEqual(SelectorTy)) |
| return coerce; |
| |
| return nullptr; |
| }; |
| |
| // Local function that adds the constructor syntax around string |
| // literals implicitly treated as a Selector. |
| auto addSelectorConstruction = [&](InFlightDiagnostic &diag) { |
| if (!fromStringLiteral) return; |
| |
| // Introduce the beginning part of the Selector construction. |
| diag.fixItInsert(stringLiteral->getLoc(), "Selector("); |
| |
| if (auto coerce = getParentCoercion()) { |
| // If the string literal was coerced to Selector, replace the |
| // coercion with the ")". |
| SourceLoc endLoc = Lexer::getLocForEndOfToken(TC.Context.SourceMgr, |
| expr->getEndLoc()); |
| diag.fixItReplace(SourceRange(endLoc, coerce->getEndLoc()), ")"); |
| } else { |
| // Otherwise, just insert the closing ")". |
| diag.fixItInsertAfter(stringLiteral->getEndLoc(), ")"); |
| } |
| }; |
| |
| // Try to parse the string literal as an Objective-C selector, and complain |
| // if it isn't one. |
| auto selector = parseObjCSelector(TC.Context, stringLiteral->getValue()); |
| if (!selector) { |
| auto diag = TC.diagnose(stringLiteral->getLoc(), |
| diag::selector_literal_invalid); |
| diag.highlight(stringLiteral->getSourceRange()); |
| addSelectorConstruction(diag); |
| return { true, expr }; |
| } |
| |
| // Look for methods with this selector. |
| SmallVector<AbstractFunctionDecl *, 8> allMethods; |
| DC->lookupAllObjCMethods(*selector, allMethods); |
| |
| // If we didn't find any methods, complain. |
| if (allMethods.empty()) { |
| // If this was Selector(("selector-name")), suppress, the |
| // diagnostic. |
| if (!fromStringLiteral && hadParens) |
| return { true, expr }; |
| |
| { |
| auto diag = TC.diagnose(stringLiteral->getLoc(), |
| diag::selector_literal_undeclared, |
| *selector); |
| addSelectorConstruction(diag); |
| } |
| |
| // If the result was from a Selector("selector-name"), add a |
| // separate note that suggests wrapping the selector in |
| // parentheses to silence the warning. |
| if (!fromStringLiteral) { |
| TC.diagnose(stringLiteral->getLoc(), |
| diag::selector_construction_suppress_warning) |
| .fixItInsert(stringLiteral->getStartLoc(), "(") |
| .fixItInsertAfter(stringLiteral->getEndLoc(), ")"); |
| } |
| |
| return { true, expr }; |
| } |
| |
| // Find the "best" method that has this selector, so we can report |
| // that. |
| AbstractFunctionDecl *bestMethod = nullptr; |
| for (auto method : allMethods) { |
| // If this is the first method, use it. |
| if (!bestMethod) { |
| bestMethod = method; |
| continue; |
| } |
| |
| // If referencing the best method would produce an ambiguity and |
| // referencing the new method would not, we have a new "best". |
| if (isSelectorReferenceAmbiguous(bestMethod) && |
| !isSelectorReferenceAmbiguous(method)) { |
| bestMethod = method; |
| continue; |
| } |
| |
| // If this method is within a protocol... |
| if (auto proto = |
| method->getDeclContext()->getAsProtocolOrProtocolExtensionContext()) { |
| // If the best so far is not from a protocol, or is from a |
| // protocol that inherits this protocol, we have a new best. |
| auto bestProto = bestMethod->getDeclContext() |
| ->getAsProtocolOrProtocolExtensionContext(); |
| if (!bestProto || bestProto->inheritsFrom(proto)) |
| bestMethod = method; |
| continue; |
| } |
| |
| // This method is from a class. |
| auto classDecl = |
| method->getDeclContext()->getAsClassOrClassExtensionContext(); |
| |
| // If the best method was from a protocol, keep it. |
| auto bestClassDecl = |
| bestMethod->getDeclContext()->getAsClassOrClassExtensionContext(); |
| if (!bestClassDecl) continue; |
| |
| // If the best method was from a subclass of the place where |
| // this method was declared, we have a new best. |
| while (auto superclassTy = bestClassDecl->getSuperclass()) { |
| auto superclassDecl = superclassTy->getClassOrBoundGenericClass(); |
| if (!superclassDecl) break; |
| |
| if (classDecl == superclassDecl) { |
| bestMethod = method; |
| break; |
| } |
| |
| bestClassDecl = superclassDecl; |
| } |
| } |
| |
| // If we have a best method, reference it. |
| if (bestMethod) { |
| // Form the replacement #selector expression. |
| SmallString<32> replacement; |
| { |
| llvm::raw_svector_ostream out(replacement); |
| auto nominal = bestMethod->getDeclContext() |
| ->getAsNominalTypeOrNominalTypeExtensionContext(); |
| out << "#selector("; |
| |
| DeclName name; |
| auto bestFunc = dyn_cast<FuncDecl>(bestMethod); |
| bool isAccessor = bestFunc && bestFunc->isAccessor(); |
| if (isAccessor) { |
| switch (bestFunc->getAccessorKind()) { |
| case AccessorKind::NotAccessor: |
| llvm_unreachable("not an accessor"); |
| |
| case AccessorKind::IsGetter: |
| out << "getter: "; |
| name = bestFunc->getAccessorStorageDecl()->getFullName(); |
| break; |
| |
| case AccessorKind::IsSetter: |
| case AccessorKind::IsWillSet: |
| case AccessorKind::IsDidSet: |
| out << "setter: "; |
| name = bestFunc->getAccessorStorageDecl()->getFullName(); |
| break; |
| |
| case AccessorKind::IsMaterializeForSet: |
| case AccessorKind::IsAddressor: |
| case AccessorKind::IsMutableAddressor: |
| llvm_unreachable("cannot be @objc"); |
| } |
| } else { |
| name = bestMethod->getFullName(); |
| } |
| |
| out << nominal->getName().str() << "." << name.getBaseName().str(); |
| auto argNames = name.getArgumentNames(); |
| |
| // Only print the parentheses if there are some argument |
| // names, because "()" would indicate a call. |
| if (argNames.size() > 0) { |
| out << "("; |
| for (auto argName : argNames) { |
| if (argName.empty()) out << "_"; |
| else out << argName.str(); |
| out << ":"; |
| } |
| out << ")"; |
| } |
| |
| // If there will be an ambiguity when referring to the method, |
| // introduce a coercion to resolve it to the method we found. |
| if (!isAccessor && isSelectorReferenceAmbiguous(bestMethod)) { |
| if (auto fnType = |
| bestMethod->getInterfaceType()->getAs<FunctionType>()) { |
| // For static/class members, drop the metatype argument. |
| if (bestMethod->isStatic()) |
| fnType = fnType->getResult()->getAs<FunctionType>(); |
| |
| // Drop the argument labels. |
| // FIXME: They never should have been in the type anyway. |
| Type type = fnType->getUnlabeledType(TC.Context); |
| |
| // Coerce to this type. |
| out << " as "; |
| type.print(out); |
| } |
| } |
| |
| out << ")"; |
| } |
| |
| // Emit the diagnostic. |
| SourceRange replacementRange = expr->getSourceRange(); |
| if (auto coerce = getParentCoercion()) |
| replacementRange.End = coerce->getEndLoc(); |
| |
| TC.diagnose(expr->getLoc(), |
| fromStringLiteral ? diag::selector_literal_deprecated_suggest |
| : diag::selector_construction_suggest) |
| .fixItReplace(replacementRange, replacement); |
| return { true, expr }; |
| } |
| |
| // If we couldn't pick a method to use for #selector, just wrap |
| // the string literal in Selector(...). |
| if (fromStringLiteral) { |
| auto diag = TC.diagnose(stringLiteral->getLoc(), |
| diag::selector_literal_deprecated); |
| addSelectorConstruction(diag); |
| return { true, expr }; |
| } |
| |
| return { true, expr }; |
| } |
| |
| }; |
| } |
| |
| static void diagDeprecatedObjCSelectors(TypeChecker &tc, const DeclContext *dc, |
| const Expr *expr) { |
| auto selectorTy = tc.getObjCSelectorType(const_cast<DeclContext *>(dc)); |
| if (!selectorTy) return; |
| |
| const_cast<Expr *>(expr)->walk(ObjCSelectorWalker(tc, dc, selectorTy)); |
| } |
| |
| |
| |
| /// Diagnose things like this, where 'i' is an Int, not an Int? |
| /// if let x: Int = i { |
| static void |
| checkImplicitPromotionsInCondition(const StmtConditionElement &cond, |
| TypeChecker &TC) { |
| auto *p = cond.getPatternOrNull(); |
| if (!p) return; |
| |
| if (auto *subExpr = isImplicitPromotionToOptional(cond.getInitializer())) { |
| // If the subexpression was actually optional, then the pattern must be |
| // checking for a type, which forced it to be promoted to a double optional |
| // type. |
| if (auto ooType = subExpr->getType()->getAnyOptionalObjectType()) { |
| if (auto TP = dyn_cast<TypedPattern>(p)) |
| // Check for 'if let' to produce a tuned diagnostic. |
| if (isa<OptionalSomePattern>(TP->getSubPattern()) && |
| TP->getSubPattern()->isImplicit()) { |
| TC.diagnose(cond.getIntroducerLoc(), diag::optional_check_promotion, |
| subExpr->getType()) |
| .highlight(subExpr->getSourceRange()) |
| .fixItReplace(TP->getTypeLoc().getSourceRange(), |
| ooType->getString()); |
| return; |
| } |
| TC.diagnose(cond.getIntroducerLoc(), |
| diag::optional_pattern_match_promotion, |
| subExpr->getType(), cond.getInitializer()->getType()) |
| .highlight(subExpr->getSourceRange()); |
| return; |
| } |
| |
| TC.diagnose(cond.getIntroducerLoc(), diag::optional_check_nonoptional, |
| subExpr->getType()) |
| .highlight(subExpr->getSourceRange()); |
| } |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // High-level entry points. |
| //===----------------------------------------------------------------------===// |
| |
| /// \brief Emit diagnostics for syntactic restrictions on a given expression. |
| void swift::performSyntacticExprDiagnostics(TypeChecker &TC, const Expr *E, |
| const DeclContext *DC, |
| bool isExprStmt) { |
| diagSelfAssignment(TC, E); |
| diagSyntacticUseRestrictions(TC, E, DC, isExprStmt); |
| diagRecursivePropertyAccess(TC, E, DC); |
| diagnoseImplicitSelfUseInClosure(TC, E, DC); |
| diagAvailability(TC, E, const_cast<DeclContext*>(DC)); |
| if (TC.Context.LangOpts.EnableObjCInterop) |
| diagDeprecatedObjCSelectors(TC, DC, E); |
| } |
| |
| void swift::performStmtDiagnostics(TypeChecker &TC, const Stmt *S) { |
| TC.checkUnsupportedProtocolType(const_cast<Stmt *>(S)); |
| |
| if (auto forStmt = dyn_cast<ForStmt>(S)) |
| checkCStyleForLoop(TC, forStmt); |
| |
| if (auto switchStmt = dyn_cast<SwitchStmt>(S)) |
| checkSwitch(TC, switchStmt); |
| |
| checkStmtConditionTrailingClosure(TC, S); |
| |
| // Check for implicit optional promotions in stmt-condition patterns. |
| if (auto *lcs = dyn_cast<LabeledConditionalStmt>(S)) |
| for (const auto &elt : lcs->getCond()) |
| checkImplicitPromotionsInCondition(elt, TC); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Utility functions |
| //===----------------------------------------------------------------------===// |
| |
| void swift::fixItAccessibility(InFlightDiagnostic &diag, ValueDecl *VD, |
| Accessibility desiredAccess, bool isForSetter) { |
| StringRef fixItString; |
| switch (desiredAccess) { |
| case Accessibility::Private: fixItString = "private "; break; |
| case Accessibility::FilePrivate: fixItString = "fileprivate "; break; |
| case Accessibility::Internal: fixItString = "internal "; break; |
| case Accessibility::Public: fixItString = "public "; break; |
| case Accessibility::Open: fixItString = "open "; break; |
| } |
| |
| DeclAttributes &attrs = VD->getAttrs(); |
| DeclAttribute *attr; |
| if (isForSetter) { |
| attr = attrs.getAttribute<SetterAccessibilityAttr>(); |
| cast<AbstractStorageDecl>(VD)->overwriteSetterAccessibility(desiredAccess); |
| } else { |
| attr = attrs.getAttribute<AccessibilityAttr>(); |
| VD->overwriteAccessibility(desiredAccess); |
| |
| if (auto *ASD = dyn_cast<AbstractStorageDecl>(VD)) { |
| if (auto *getter = ASD->getGetter()) |
| getter->overwriteAccessibility(desiredAccess); |
| |
| if (auto *setterAttr = attrs.getAttribute<SetterAccessibilityAttr>()) { |
| if (setterAttr->getAccess() > desiredAccess) |
| fixItAccessibility(diag, VD, desiredAccess, true); |
| } else { |
| ASD->overwriteSetterAccessibility(desiredAccess); |
| } |
| } |
| } |
| |
| if (isForSetter && VD->getFormalAccess() == desiredAccess) { |
| assert(attr); |
| attr->setInvalid(); |
| // Remove the setter attribute. |
| diag.fixItRemove(attr->Range); |
| |
| } else if (attr) { |
| // This uses getLocation() instead of getRange() because we don't want to |
| // replace the "(set)" part of a setter attribute. |
| diag.fixItReplace(attr->getLocation(), fixItString.drop_back()); |
| attr->setInvalid(); |
| |
| } else if (auto var = dyn_cast<VarDecl>(VD)) { |
| if (auto PBD = var->getParentPatternBinding()) |
| diag.fixItInsert(PBD->getStartLoc(), fixItString); |
| |
| } else { |
| diag.fixItInsert(VD->getStartLoc(), fixItString); |
| } |
| } |
| |
| /// Retrieve the type name to be used for determining whether we can |
| /// omit needless words. |
| static OmissionTypeName getTypeNameForOmission(Type type) { |
| if (!type) |
| return ""; |
| |
| ASTContext &ctx = type->getASTContext(); |
| Type boolType; |
| if (auto boolDecl = ctx.getBoolDecl()) |
| boolType = boolDecl->getDeclaredInterfaceType(); |
| Type objcBoolType; |
| if (auto objcBoolDecl = ctx.getObjCBoolDecl()) |
| objcBoolType = objcBoolDecl->getDeclaredInterfaceType(); |
| |
| /// Determine the options associated with the given type. |
| auto getOptions = [&](Type type) { |
| // Look for Boolean types. |
| OmissionTypeOptions options; |
| |
| // Look for Boolean types. |
| if (boolType && type->isEqual(boolType)) { |
| // Swift.Bool |
| options |= OmissionTypeFlags::Boolean; |
| } else if (objcBoolType && type->isEqual(objcBoolType)) { |
| // ObjectiveC.ObjCBool |
| options |= OmissionTypeFlags::Boolean; |
| } |
| |
| return options; |
| }; |
| |
| do { |
| // Look through typealiases. |
| if (auto aliasTy = dyn_cast<NameAliasType>(type.getPointer())) { |
| type = aliasTy->getDecl()->getUnderlyingType(); |
| continue; |
| } |
| |
| // Strip off lvalue/inout types. |
| Type newType = type->getLValueOrInOutObjectType(); |
| if (newType.getPointer() != type.getPointer()) { |
| type = newType; |
| continue; |
| } |
| |
| // Look through reference-storage types. |
| newType = type->getReferenceStorageReferent(); |
| if (newType.getPointer() != type.getPointer()) { |
| type = newType; |
| continue; |
| } |
| |
| // Look through parentheses. |
| if (auto parenTy = dyn_cast<ParenType>(type.getPointer())) { |
| type = parenTy->getUnderlyingType(); |
| continue; |
| } |
| |
| // Look through optionals. |
| if (auto optObjectTy = type->getAnyOptionalObjectType()) { |
| type = optObjectTy; |
| continue; |
| } |
| |
| break; |
| } while (true); |
| |
| // Nominal types. |
| if (auto nominal = type->getAnyNominal()) { |
| // If we have a collection, get the element type. |
| if (auto bound = type->getAs<BoundGenericType>()) { |
| ASTContext &ctx = nominal->getASTContext(); |
| auto args = bound->getGenericArgs(); |
| if (!args.empty() && |
| (bound->getDecl() == ctx.getArrayDecl() || |
| bound->getDecl() == ctx.getSetDecl())) { |
| return OmissionTypeName(nominal->getName().str(), |
| getOptions(bound), |
| getTypeNameForOmission(args[0]).Name); |
| } |
| } |
| |
| // AnyObject -> "Object". |
| if (type->isAnyObject()) |
| return "Object"; |
| |
| return OmissionTypeName(nominal->getName().str(), getOptions(type)); |
| } |
| |
| // Generic type parameters. |
| if (auto genericParamTy = type->getAs<GenericTypeParamType>()) { |
| if (auto genericParam = genericParamTy->getDecl()) |
| return genericParam->getName().str(); |
| |
| return ""; |
| } |
| |
| // Dependent members. |
| if (auto dependentMemberTy = type->getAs<DependentMemberType>()) { |
| return dependentMemberTy->getName().str(); |
| } |
| |
| // Archetypes. |
| if (auto archetypeTy = type->getAs<ArchetypeType>()) { |
| return archetypeTy->getName().str(); |
| } |
| |
| // Function types. |
| if (auto funcTy = type->getAs<AnyFunctionType>()) { |
| if (funcTy->getRepresentation() == AnyFunctionType::Representation::Block) |
| return "Block"; |
| |
| return "Function"; |
| } |
| return ""; |
| } |
| |
| Optional<DeclName> TypeChecker::omitNeedlessWords(AbstractFunctionDecl *afd) { |
| auto &Context = afd->getASTContext(); |
| |
| if (!afd->hasType()) |
| validateDecl(afd); |
| |
| if (afd->isInvalid() || isa<DestructorDecl>(afd)) |
| return None; |
| |
| DeclName name = afd->getFullName(); |
| if (!name) |
| return None; |
| |
| // String'ify the arguments. |
| StringRef baseNameStr = name.getBaseName().str(); |
| SmallVector<StringRef, 4> argNameStrs; |
| for (auto arg : name.getArgumentNames()) { |
| if (arg.empty()) |
| argNameStrs.push_back(""); |
| else |
| argNameStrs.push_back(arg.str()); |
| } |
| |
| // String'ify the parameter types. |
| SmallVector<OmissionTypeName, 4> paramTypes; |
| |
| // Always look at the parameters in the last parameter list. |
| for (auto param : *afd->getParameterLists().back()) { |
| paramTypes.push_back(getTypeNameForOmission(param->getType()) |
| .withDefaultArgument(param->isDefaultArgument())); |
| } |
| |
| // Handle contextual type, result type, and returnsSelf. |
| Type contextType = afd->getDeclContext()->getDeclaredInterfaceType(); |
| Type resultType; |
| bool returnsSelf = false; |
| |
| if (auto func = dyn_cast<FuncDecl>(afd)) { |
| resultType = func->getResultType(); |
| returnsSelf = func->hasDynamicSelf(); |
| } else if (isa<ConstructorDecl>(afd)) { |
| resultType = contextType; |
| returnsSelf = true; |
| } |
| |
| // Figure out the first parameter name. |
| StringRef firstParamName; |
| auto params = afd->getParameterList(afd->getImplicitSelfDecl() ? 1 : 0); |
| if (params->size() != 0 && !params->get(0)->getName().empty()) |
| firstParamName = params->get(0)->getName().str(); |
| |
| // Find the set of property names. |
| const InheritedNameSet *allPropertyNames = nullptr; |
| if (contextType) { |
| if (auto classDecl = contextType->getClassOrBoundGenericClass()) { |
| allPropertyNames = Context.getAllPropertyNames(classDecl, |
| afd->isInstanceMember()); |
| } |
| } |
| |
| StringScratchSpace scratch; |
| if (!swift::omitNeedlessWords(baseNameStr, argNameStrs, firstParamName, |
| getTypeNameForOmission(resultType), |
| getTypeNameForOmission(contextType), |
| paramTypes, returnsSelf, false, |
| allPropertyNames, scratch)) |
| return None; |
| |
| /// Retrieve a replacement identifier. |
| auto getReplacementIdentifier = [&](StringRef name, |
| Identifier old) -> Identifier{ |
| if (name.empty()) |
| return Identifier(); |
| |
| if (!old.empty() && name == old.str()) |
| return old; |
| |
| return Context.getIdentifier(name); |
| }; |
| |
| Identifier newBaseName = getReplacementIdentifier(baseNameStr, |
| name.getBaseName()); |
| SmallVector<Identifier, 4> newArgNames; |
| auto oldArgNames = name.getArgumentNames(); |
| for (unsigned i = 0, n = argNameStrs.size(); i != n; ++i) { |
| newArgNames.push_back(getReplacementIdentifier(argNameStrs[i], |
| oldArgNames[i])); |
| } |
| |
| return DeclName(Context, newBaseName, newArgNames); |
| } |
| |
| Optional<Identifier> TypeChecker::omitNeedlessWords(VarDecl *var) { |
| auto &Context = var->getASTContext(); |
| |
| if (!var->hasType()) |
| validateDecl(var); |
| |
| if (var->isInvalid() || !var->hasType()) |
| return None; |
| |
| if (var->getName().empty()) |
| return None; |
| |
| auto name = var->getName().str(); |
| |
| // Dig out the context type. |
| Type contextType = var->getDeclContext()->getDeclaredInterfaceType(); |
| if (!contextType) |
| return None; |
| |
| // Dig out the type of the variable. |
| Type type = var->getInterfaceType()->getReferenceStorageReferent() |
| ->getLValueOrInOutObjectType(); |
| while (auto optObjectTy = type->getAnyOptionalObjectType()) |
| type = optObjectTy; |
| |
| // Find the set of property names. |
| const InheritedNameSet *allPropertyNames = nullptr; |
| if (contextType) { |
| if (auto classDecl = contextType->getClassOrBoundGenericClass()) { |
| allPropertyNames = Context.getAllPropertyNames(classDecl, |
| var->isInstanceMember()); |
| } |
| } |
| |
| |
| // Omit needless words. |
| StringScratchSpace scratch; |
| OmissionTypeName typeName = getTypeNameForOmission(var->getType()); |
| OmissionTypeName contextTypeName = getTypeNameForOmission(contextType); |
| if (::omitNeedlessWords(name, { }, "", typeName, contextTypeName, { }, |
| /*returnsSelf=*/false, true, allPropertyNames, |
| scratch)) { |
| return Context.getIdentifier(name); |
| } |
| |
| return None; |
| } |
| |
| void TypeChecker::checkOmitNeedlessWords(AbstractFunctionDecl *afd) { |
| if (!Context.LangOpts.WarnOmitNeedlessWords) |
| return; |
| |
| auto newName = omitNeedlessWords(afd); |
| if (!newName) |
| return; |
| |
| auto name = afd->getFullName(); |
| InFlightDiagnostic diag = diagnose(afd->getLoc(), diag::omit_needless_words, |
| name, *newName); |
| fixDeclarationName(diag, afd, *newName); |
| } |
| |
| void TypeChecker::checkOmitNeedlessWords(VarDecl *var) { |
| if (!Context.LangOpts.WarnOmitNeedlessWords) |
| return; |
| |
| auto newName = omitNeedlessWords(var); |
| if (!newName) |
| return; |
| |
| auto name = var->getName(); |
| diagnose(var->getLoc(), diag::omit_needless_words, name, *newName) |
| .fixItReplace(var->getLoc(), newName->str()); |
| } |
| |
| /// Find the source ranges of extraneous default arguments within a |
| /// call to the given function. |
| static bool hasExtraneousDefaultArguments(AbstractFunctionDecl *afd, |
| Expr *arg, DeclName name, |
| SmallVectorImpl<SourceRange> &ranges, |
| SmallVectorImpl<unsigned> &removedArgs) { |
| if (!afd->getClangDecl()) |
| return false; |
| |
| if (afd->isInvalid()) |
| return false; |
| |
| if (auto shuffle = dyn_cast<TupleShuffleExpr>(arg)) |
| arg = shuffle->getSubExpr(); |
| |
| TupleExpr *argTuple = dyn_cast<TupleExpr>(arg); |
| ParenExpr *argParen = dyn_cast<ParenExpr>(arg); |
| |
| ASTContext &ctx = afd->getASTContext(); |
| // Skip over the implicit 'self'. |
| auto *bodyParams = afd->getParameterList(afd->getImplicitSelfDecl()?1:0); |
| |
| Optional<unsigned> firstRemoved; |
| Optional<unsigned> lastRemoved; |
| unsigned numElementsInParens; |
| if (argTuple) { |
| numElementsInParens = (argTuple->getNumElements() - |
| argTuple->hasTrailingClosure()); |
| } else if (argParen) { |
| numElementsInParens = 1 - argParen->hasTrailingClosure(); |
| } else { |
| numElementsInParens = 0; |
| } |
| |
| for (unsigned i = 0; i != numElementsInParens; ++i) { |
| auto param = bodyParams->get(i); |
| if (!param->isDefaultArgument()) |
| continue; |
| |
| auto defaultArg = param->getDefaultArgumentKind(); |
| |
| // Never consider removing the first argument for a "set" method |
| // with an unnamed first argument. |
| if (i == 0 && |
| !name.getBaseName().empty() && |
| camel_case::getFirstWord(name.getBaseName().str()) == "set" && |
| name.getArgumentNames().size() > 0 && |
| name.getArgumentNames()[0].empty()) |
| continue; |
| |
| SourceRange removalRange; |
| if (argTuple && i < argTuple->getNumElements()) { |
| // Check whether the supplied argument is the same as the |
| // default argument. |
| if (defaultArg != inferDefaultArgumentKind(argTuple->getElement(i))) |
| continue; |
| |
| // Figure out where to start removing this argument. |
| if (i == 0) { |
| // Start removing right after the opening parenthesis. |
| removalRange.Start = argTuple->getLParenLoc(); |
| } else { |
| // Start removing right after the preceding argument, so we |
| // consume the comma as well. |
| removalRange.Start = argTuple->getElement(i-1)->getEndLoc(); |
| } |
| |
| // Adjust to the end of the starting token. |
| removalRange.Start |
| = Lexer::getLocForEndOfToken(ctx.SourceMgr, removalRange.Start); |
| |
| // Figure out where to finish removing this element. |
| if (i == 0 && i < numElementsInParens - 1) { |
| // We're the first of several arguments; consume the |
| // following comma as well. |
| removalRange.End = argTuple->getElementNameLoc(i+1); |
| if (removalRange.End.isInvalid()) |
| removalRange.End = argTuple->getElement(i+1)->getStartLoc(); |
| } else if (i < numElementsInParens - 1) { |
| // We're in the middle; consume through the end of this |
| // element. |
| removalRange.End |
| = Lexer::getLocForEndOfToken(ctx.SourceMgr, |
| argTuple->getElement(i)->getEndLoc()); |
| } else { |
| // We're at the end; consume up to the closing parentheses. |
| removalRange.End = argTuple->getRParenLoc(); |
| } |
| } else if (argParen) { |
| // Check whether we have a default argument. |
| if (defaultArg != inferDefaultArgumentKind(argParen->getSubExpr())) |
| continue; |
| |
| removalRange = SourceRange(argParen->getSubExpr()->getStartLoc(), |
| argParen->getRParenLoc()); |
| } else { |
| continue; |
| } |
| |
| if (removalRange.isInvalid()) |
| continue; |
| |
| // Note that we're removing this argument. |
| removedArgs.push_back(i); |
| |
| // If we hadn't removed anything before, this is the first |
| // removal. |
| if (!firstRemoved) { |
| ranges.push_back(removalRange); |
| firstRemoved = i; |
| lastRemoved = i; |
| continue; |
| } |
| |
| // If the previous removal range was the previous argument, |
| // combine the ranges. |
| if (*lastRemoved == i - 1) { |
| ranges.back().End = removalRange.End; |
| lastRemoved = i; |
| continue; |
| } |
| |
| // Otherwise, add this new removal range. |
| ranges.push_back(removalRange); |
| lastRemoved = i; |
| } |
| |
| // If there is a single removal range that covers everything but |
| // the trailing closure at the end, also zap the parentheses. |
| if (ranges.size() == 1 && |
| *firstRemoved == 0 && *lastRemoved == bodyParams->size() - 2 && |
| argTuple && argTuple->hasTrailingClosure()) { |
| ranges.front().Start = argTuple->getLParenLoc(); |
| ranges.front().End |
| = Lexer::getLocForEndOfToken(ctx.SourceMgr, argTuple->getRParenLoc()); |
| } |
| |
| return !ranges.empty(); |
| } |
| |
| void TypeChecker::checkOmitNeedlessWords(ApplyExpr *apply) { |
| if (!Context.LangOpts.WarnOmitNeedlessWords) |
| return; |
| |
| // Find the callee. |
| ApplyExpr *innermostApply = apply; |
| unsigned numApplications = 0; |
| while (auto fnApply = dyn_cast<ApplyExpr>( |
| innermostApply->getFn()->getValueProvidingExpr())) { |
| innermostApply = fnApply; |
| ++numApplications; |
| } |
| if (numApplications != 1) |
| return; |
| |
| DeclRefExpr *fnRef |
| = dyn_cast<DeclRefExpr>(innermostApply->getFn()->getValueProvidingExpr()); |
| if (!fnRef) |
| return; |
| |
| auto *afd = dyn_cast<AbstractFunctionDecl>(fnRef->getDecl()); |
| if (!afd) |
| return; |
| |
| // Determine whether the callee has any needless words in it. |
| auto newName = omitNeedlessWords(afd); |
| |
| bool renamed; |
| if (!newName) { |
| newName = afd->getFullName(); |
| renamed = false; |
| } else { |
| renamed = true; |
| } |
| |
| // Determine whether there are any extraneous default arguments to be zapped. |
| SmallVector<SourceRange, 2> removedDefaultArgRanges; |
| SmallVector<unsigned, 2> removedArgs; |
| bool anyExtraneousDefaultArgs |
| = hasExtraneousDefaultArguments(afd, apply->getArg(), *newName, |
| removedDefaultArgRanges, |
| removedArgs); |
| |
| if (!renamed && !anyExtraneousDefaultArgs) |
| return; |
| |
| // Make sure to apply the fix at the right application level. |
| auto name = afd->getFullName(); |
| |
| // Dig out the argument tuple. |
| Expr *arg = apply->getArg(); |
| if (auto shuffle = dyn_cast<TupleShuffleExpr>(arg)) |
| arg = shuffle->getSubExpr(); |
| TupleExpr *argTuple = dyn_cast<TupleExpr>(arg); |
| ParenExpr *argParen = dyn_cast<ParenExpr>(arg); |
| |
| if (argParen && !argTuple) |
| arg = argParen->getSubExpr(); |
| |
| InFlightDiagnostic diag |
| = renamed ? diagnose(fnRef->getLoc(), diag::omit_needless_words, |
| name, *newName) |
| : diagnose(fnRef->getLoc(), diag::extraneous_default_args_in_call, |
| name); |
| |
| // Fix the base name. |
| if (newName->getBaseName() != name.getBaseName()) { |
| diag.fixItReplace(fnRef->getLoc(), newName->getBaseName().str()); |
| } |
| |
| // Fix the argument names. |
| auto oldArgNames = name.getArgumentNames(); |
| auto newArgNames = newName->getArgumentNames(); |
| unsigned currentRemovedArg = 0; |
| if (argTuple) { |
| for (unsigned i = 0, n = newArgNames.size(); i != n; ++i) { |
| // If this argument was completely removed, don't emit any |
| // Fix-Its for it. |
| if (currentRemovedArg < removedArgs.size() && |
| removedArgs[currentRemovedArg] == i) { |
| ++currentRemovedArg; |
| continue; |
| } |
| |
| // Check whether the name changed. |
| auto newArgName = newArgNames[i]; |
| if (oldArgNames[i] == newArgName) continue; |
| |
| if (i >= argTuple->getNumElements()) break; |
| if (argTuple->getElementName(i) != oldArgNames[i]) continue; |
| |
| auto nameLoc = argTuple->getElementNameLoc(i); |
| if (nameLoc.isInvalid()) { |
| // Add the argument label. |
| diag.fixItInsert(argTuple->getElement(i)->getStartLoc(), |
| (newArgName.str() + ": ").str()); |
| } else if (newArgName.empty()) { |
| // Delete the argument label. |
| diag.fixItRemoveChars(nameLoc, argTuple->getElement(i)->getStartLoc()); |
| } else { |
| // Fix the argument label. |
| diag.fixItReplace(nameLoc, newArgName.str()); |
| } |
| } |
| } else if (newArgNames.size() > 0 && !newArgNames[0].empty() && |
| (!argParen || !argParen->hasTrailingClosure()) && |
| removedArgs.empty()) { |
| // Add the argument label. |
| auto newArgName = newArgNames[0]; |
| diag.fixItInsert(arg->getStartLoc(), (newArgName.str() + ": ").str()); |
| } |
| |
| // Remove all of the defaulted arguments. |
| for (auto extraneous : removedDefaultArgRanges) { |
| diag.fixItRemoveChars(extraneous.Start, extraneous.End); |
| } |
| } |
| |
| void TypeChecker::checkOmitNeedlessWords(MemberRefExpr *memberRef) { |
| if (!Context.LangOpts.WarnOmitNeedlessWords) |
| return; |
| |
| auto var = dyn_cast<VarDecl>(memberRef->getMember().getDecl()); |
| if (!var) |
| return; |
| |
| // Check whether any needless words were omitted. |
| auto newName = omitNeedlessWords(var); |
| if (!newName) |
| return; |
| |
| // Fix the name. |
| auto name = var->getName(); |
| diagnose(memberRef->getNameLoc(), diag::omit_needless_words, name, *newName) |
| .fixItReplace(memberRef->getNameLoc().getSourceRange(), newName->str()); |
| } |