//===--- 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 https://swift.org/LICENSE.txt for license information // See https://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( E->getSemanticsProvidingExpr())) return IIOE->getSubExpr(); return nullptr; } //===----------------------------------------------------------------------===// // Diagnose assigning variable to itself. //===----------------------------------------------------------------------===// static Decl *findSimpleReferencedDecl(const Expr *E) { if (auto *LE = dyn_cast(E)) E = LE->getSubExpr(); if (auto *DRE = dyn_cast(E)) return DRE->getDecl(); return nullptr; } static std::pair findReferencedDecl(const Expr *E) { if (auto *LE = dyn_cast(E)) E = LE->getSubExpr(); if (auto *D = findSimpleReferencedDecl(E)) return std::make_pair(nullptr, D); if (auto *MRE = dyn_cast(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(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 AlreadyDiagnosedMetatypes; SmallPtrSet AlreadyDiagnosedNoEscapes; // Keep track of acceptable DiscardAssignmentExpr's. SmallPtrSet CorrectDiscardAssignmentExprs; /// Keep track of InOutExprs SmallPtrSet AcceptableInOutExprs; /// Keep track of the arguments to CallExprs. SmallPtrSet 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 unraveled as subsequent applications complete // the call (or not). llvm::SmallDenseMap 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(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(fnExpr); if (!fnDeclRef) return; auto fn = dyn_cast(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(E)) { Expr *fnExpr = AE->getSemanticFn(); if (auto forceExpr = dyn_cast(fnExpr)) fnExpr = forceExpr->getSubExpr()->getSemanticsProvidingExpr(); if (auto dotSyntaxExpr = dyn_cast(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 walkToStmtPre(Stmt *S) override { return { false, S }; } std::pair walkToPatternPre(Pattern *P) override { return { false, P }; } bool walkToDeclPre(Decl *D) override { return false; } bool walkToTypeReprPre(TypeRepr *T) override { return true; } std::pair 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(Base)) Base = Conv->getSubExpr(); // Record call arguments. if (auto Call = dyn_cast(Base)) CallArgs.insert(Call->getArg()); if (auto *DRE = dyn_cast(Base)) { // Verify metatype uses. if (isa(DRE->getDecl())) { if (isa(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(Base)) { if (isa(MRE->getMember().getDecl())) checkUseOfMetaTypeName(Base); } if (isa(Base)) checkUseOfMetaTypeName(Base); if (auto *SE = dyn_cast(E)) { // Implicit InOutExpr's are allowed in the base of a subscript expr. if (auto *IOE = dyn_cast(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(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(Base)) Base = Conv->getSubExpr(); if (auto *DRE = dyn_cast(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(Arg)) Arg = TSE->getSubExpr(); // The argument is either a ParenExpr or TupleExpr. ArrayRef arguments; SmallVector Scratch; if (auto *TE = dyn_cast(Arg)) arguments = TE->getElements(); else if (auto *PE = dyn_cast(Arg)) { Scratch.push_back(PE->getSubExpr()); arguments = makeArrayRef(Scratch); } else { Scratch.push_back(Call->getArg()); arguments = makeArrayRef(Scratch); } // Check each argument. for (auto arg : arguments) { // InOutExpr's are allowed in argument lists directly. if (auto *IOE = dyn_cast(arg)) { if (isa(Call)) AcceptableInOutExprs.insert(IOE); } // InOutExprs can be wrapped in some implicit casts. Expr *unwrapped = arg; if (auto *IIO = dyn_cast(arg)) unwrapped = IIO->getSubExpr(); if (auto *ICO = dyn_cast(unwrapped)) { if (isa(ICO) || isa(ICO) || isa(ICO)) if (auto *IOE = dyn_cast(ICO->getSubExpr())) AcceptableInOutExprs.insert(IOE); } while (1) { if (auto conv = dyn_cast(arg)) arg = conv->getSubExpr(); else if (auto *PE = dyn_cast(arg)) arg = PE->getSubExpr(); else break; } if (auto *DRE = dyn_cast(arg)) checkNoEscapeParameterUse(DRE, Call); } } // If we have an assignment expression, scout ahead for acceptable _'s. if (auto *AE = dyn_cast(E)) markAcceptableDiscardExprs(AE->getDest()); /// Diagnose a '_' that isn't on the immediate LHS of an assignment. if (auto *DAE = dyn_cast(E)) { if (!CorrectDiscardAssignmentExprs.count(DAE) && !DAE->getType()->hasError()) TC.diagnose(DAE->getLoc(), diag::discard_expr_outside_of_assignment); } // Diagnose an '&' that isn't in an argument lists. if (auto *IOE = dyn_cast(E)) { if (!IOE->isImplicit() && !AcceptableInOutExprs.count(IOE) && !IOE->getType()->hasError()) 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(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_heterogeneous, 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(E)) return markAcceptableDiscardExprs(PE->getSubExpr()); if (auto *TE = dyn_cast(E)) { for (auto &elt : TE->getElements()) markAcceptableDiscardExprs(elt); return; } if (auto *DAE = dyn_cast(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(); // 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(arg) || isa(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(FT->getInput().getPointer())) { auto TT = FT->getInput()->getAs(); 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(ParentExpr) || isa(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()->getInterfaceType()->getAs(); 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(ParentExpr)) // param() return; TC.diagnose(DRE->getStartLoc(), diag::invalid_noescape_use, DRE->getDecl()->getName(), isa(DRE->getDecl())); // If we're a parameter, emit a helpful fixit to add @escaping auto paramDecl = dyn_cast(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(ParentExpr) || isa(ParentExpr) || // T.self isa(ParentExpr) || // T() isa(ParentExpr) || // T.foo isa(ParentExpr) || isa(ParentExpr) || // T.foo() T() isa(ParentExpr) || isa(ParentExpr) || isa(ParentExpr) || isa(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(ParentExpr) && CallArgs.count(ParentExpr) > 0) { auto diag = TC.diagnose(E->getEndLoc(), diag::warn_value_of_metatype_missing_self, E->getType()->getRValueInstanceType()); if (E->canAppendCallParentheses()) { diag.fixItInsertAfter(E->getEndLoc(), ".self"); } else { diag.fixItInsert(E->getStartLoc(), "("); diag.fixItInsertAfter(E->getEndLoc(), ").self"); } return; } } // Is this a protocol metatype? TC.diagnose(E->getStartLoc(), diag::value_of_metatype_type); // Add fix-it to insert '()', only if this is a metatype of // non-existential type and has any initializers. bool isExistential = false; if (auto metaTy = E->getType()->getAs()) isExistential = metaTy->getInstanceType()->isAnyExistentialType(); if (!isExistential && !TC.lookupConstructors(const_cast(DC), E->getType()).empty()) { TC.diagnose(E->getEndLoc(), diag::add_parens_to_type) .fixItInsertAfter(E->getEndLoc(), "()"); } // Add fix-it to insert ".self". auto diag = TC.diagnose(E->getEndLoc(), diag::add_self_to_type); if (E->canAppendCallParentheses()) { diag.fixItInsertAfter(E->getEndLoc(), ".self"); } else { diag.fixItInsert(E->getStartLoc(), "("); diag.fixItInsertAfter(E->getEndLoc(), ").self"); } } void checkUnqualifiedAccessUse(const DeclRefExpr *DRE) { const Decl *D = DRE->getDecl(); if (!D->getAttrs().hasAttribute()) return; if (auto *parentExpr = Parent.getAsExpr()) { if (auto *ignoredBase = dyn_cast(parentExpr)){ if (!ignoredBase->isImplicit()) return; } if (auto *calledBase = dyn_cast(parentExpr)) { if (!calledBase->isImplicit()) return; } } const auto *VD = cast(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 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; SmallVector 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(E->getSemanticsProvidingExpr()); if (!CE || !CE->isImplicit()) return false; auto CRCE = dyn_cast(CE->getSemanticFn()); if (!CRCE || !CRCE->isImplicit()) return false; auto DRE = dyn_cast(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(call->getSemanticFn()); auto args = dyn_cast(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(E)->walk(Walker); // Diagnose uses of collection literals with defaulted types at the top // level. if (auto collection = dyn_cast(E->getSemanticsProvidingExpr())) { if (collection->isTypeDefaulted()) { Walker.checkTypeDefaultedCollectionExpr( const_cast(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(DC); if (!fn || !fn->isAccessor()) return; auto var = dyn_cast(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 walkToExprPre(Expr *E) override { Expr *subExpr; bool isStore = false; if (auto *AE = dyn_cast(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(E)) { subExpr = IOE->getSubExpr(); isStore = true; } else { subExpr = E; } if (auto *BOE = dyn_cast(subExpr)) subExpr = BOE; if (auto *DRE = dyn_cast(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(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(subExpr)) { // Handle instance and type computed variables. // Find MemberRefExprs that have an implicit "self" base. if (MRE->getMember().getDecl() == Var && isa(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(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(E); return DRE && DRE->isImplicit() && isa(DRE->getDecl()) && cast(DRE->getDecl())->isSelfParameter() && // Metatype self captures don't extend the lifetime of an object. !DRE->getType()->is(); } /// 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(CE)) .isKnownNoEscape(); } // Don't walk into nested decls. bool walkToDeclPre(Decl *D) override { return false; } std::pair walkToExprPre(Expr *E) override { if (auto *CE = dyn_cast(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(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(E)) if (isImplicitSelfUse(DSCE->getBase()) && isa(DSCE->getFn())) { auto MethodExpr = cast(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(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(DC)) if (DiagnoseWalker::isClosureRequiringSelfQualification(closure)) isAlreadyInClosure = true; DC = DC->getParent(); } } const_cast(E)->walk(DiagnoseWalker(TC, isAlreadyInClosure)); } bool TypeChecker::getDefaultGenericArgumentsString( SmallVectorImpl &buf, const swift::GenericTypeDecl *typeDecl, llvm::function_ref getPreferredType) { llvm::raw_svector_ostream genericParamText{buf}; genericParamText << "<"; auto printGenericParamSummary = [&](const GenericTypeParamType *genericParamTy) { const GenericTypeParamDecl *genericParam = genericParamTy->getDecl(); if (Type result = getPreferredType(genericParam)) { result.print(genericParamText); return; } ArrayRef protocols = genericParam->getConformingProtocols(); if (Type superclass = genericParam->getSuperclass()) { if (protocols.empty()) { superclass.print(genericParamText); return; } genericParamText << "<#" << genericParam->getName() << ": "; superclass.print(genericParamText); for (const ProtocolDecl *proto : protocols) { if (proto->isSpecificProtocol(KnownProtocolKind::AnyObject)) continue; genericParamText << " & " << proto->getName(); } genericParamText << "#>"; return; } if (protocols.empty()) { genericParamText << Context.Id_Any; return; } if (protocols.size() == 1 && (protocols.front()->isObjC() || protocols.front()->isSpecificProtocol(KnownProtocolKind::AnyObject))) { genericParamText << protocols.front()->getName(); return; } genericParamText << "<#" << genericParam->getName() << ": "; interleave(protocols, [&](const ProtocolDecl *proto) { genericParamText << proto->getName(); }, [&] { genericParamText << " & "; }); genericParamText << "#>"; }; interleave(typeDecl->getInnermostGenericParamTypes(), printGenericParamSummary, [&]{ genericParamText << ", "; }); genericParamText << ">"; return true; } /// Diagnose an argument labeling issue, returning true if we successfully /// diagnosed the issue. bool swift::diagnoseArgumentLabelError(TypeChecker &TC, const Expr *expr, ArrayRef newNames, bool isSubscript, InFlightDiagnostic *existingDiag) { Optional diagOpt; auto getDiag = [&]() -> InFlightDiagnostic & { if (existingDiag) return *existingDiag; return *diagOpt; }; auto tuple = dyn_cast(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 // labeled tuples go away. if (auto tupleTy = expr->getType()->getRValueType()->getAs()) { 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(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 checkValueReferenceType = [&](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(); // ...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; if (normalizedBaseTy->isAny()) { const ProtocolDecl *anyObjectProto = TC.Context.getProtocol(KnownProtocolKind::AnyObject); bridged = anyObjectProto->getDeclaredType(); } else { bridged = TC.Context.getBridgedToObjC(DC, normalizedBaseTy); } 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; }; // Check if overriding fails because we lack @escaping attribute on the function // type repr. auto checkTypeMissingEscaping = [&](Type overrideTy, Type baseTy, SourceRange typeRange) -> bool { // Fix-it needs position to apply. if (typeRange.isInvalid()) return false; auto overrideFnTy = overrideTy->getAs(); auto baseFnTy = baseTy->getAs(); // Both types should be function. if (overrideFnTy && baseFnTy && // The overriding function type should be no escaping. overrideFnTy->getExtInfo().isNoEscape() && // The overridden function type should be escaping. !baseFnTy->getExtInfo().isNoEscape()) { diag.fixItInsert(typeRange.Start, "@escaping "); return true; } return false; }; auto checkType = [&](Type overrideTy, Type baseTy, SourceRange typeRange) -> bool { return checkValueReferenceType(overrideTy, baseTy, typeRange) || checkTypeMissingEscaping(overrideTy, baseTy, typeRange); }; if (auto *var = dyn_cast(decl)) { SourceRange typeRange = var->getTypeSourceRangeForDiagnostics(); auto *baseVar = cast(base); return checkType(var->getInterfaceType(), baseVar->getInterfaceType(), typeRange); } if (auto *fn = dyn_cast(decl)) { auto *baseFn = cast(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(decl)) { auto resultType = ArchetypeBuilder::mapTypeIntoContext( method, method->getResultInterfaceType()); auto *baseMethod = cast(base); auto baseResultType = ArchetypeBuilder::mapTypeIntoContext( baseMethod, baseMethod->getResultInterfaceType()); fixedAny |= checkType(resultType, baseResultType, method->getBodyResultTypeLoc().getSourceRange()); } return fixedAny; } if (auto *subscript = dyn_cast(decl)) { auto *baseSubscript = cast(base); bool fixedAny = false; for_each(*subscript->getIndices(), *baseSubscript->getIndices(), [&](ParamDecl *param, const ParamDecl *baseParam) { fixedAny |= fixItOverrideDeclarationTypes(TC, diag, param, baseParam); }); auto resultType = ArchetypeBuilder::mapTypeIntoContext( subscript->getDeclContext(), subscript->getElementInterfaceType()); auto baseResultType = ArchetypeBuilder::mapTypeIntoContext( baseSubscript->getDeclContext(), baseSubscript->getElementInterfaceType()); fixedAny |= checkType(resultType, baseResultType, subscript->getElementTypeLoc().getSourceRange()); return fixedAny; } llvm_unreachable("unknown overridable member"); } //===----------------------------------------------------------------------===// // Diagnose availability. //===----------------------------------------------------------------------===// void swift::fixItAvailableAttrRename(TypeChecker &TC, InFlightDiagnostic &diag, SourceRange referenceRange, const ValueDecl *renamedDecl, const AvailableAttr *attr, const ApplyExpr *call) { ParsedDeclName parsed = swift::parseDeclName(attr->Rename); if (!parsed) return; bool originallyWasKnownOperatorExpr = false; if (call) { originallyWasKnownOperatorExpr = isa(call) || isa(call) || isa(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(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(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(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(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(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 if (auto *dotCall = dyn_cast(call->getFn())) { SourceLoc removeLoc = dotCall->getDotLoc(); if (removeLoc.isInvalid()) return; diag.fixItRemove(SourceRange(removeLoc, dotCall->getFn()->getEndLoc())); } else if (!isa(call->getFn())) { return; } // Continue on to diagnose any constructor argument label renames. } else { // Just replace the base name. SmallString<64> baseReplace; if (!parsed.ContextName.empty()) { baseReplace += parsed.ContextName; baseReplace += '.'; } baseReplace += parsed.BaseName; if (parsed.IsFunctionName && parsed.ArgumentLabels.empty() && isa(renamedDecl)) { // If we're going from a var to a function with no arguments, emit an // empty parameter list. baseReplace += "()"; } diag.fixItReplace(referenceRange, baseReplace); } if (!dyn_cast_or_null(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(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(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 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(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(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(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 describeRename(ASTContext &ctx, const AvailableAttr *attr, const ValueDecl *D, SmallVectorImpl &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(D)))) { return None; } llvm::raw_svector_ostream name(nameBuf); if (!parsed.ContextName.empty()) name << parsed.ContextName << '.'; if (parsed.IsFunctionName) { // FIXME: duplicated from above. SmallVector 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::diagnoseIfDeprecated(SourceRange ReferenceRange, const DeclContext *ReferenceDC, const ValueDecl *DeprecatedDecl, const ApplyExpr *Call) { const AvailableAttr *Attr = TypeChecker::getDeprecated(DeprecatedDecl); if (!Attr) return; // 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; } } DeclName Name = DeprecatedDecl->getFullName(); 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 = 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( 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, DeprecatedDecl, 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(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, D, AvailableAttr::isUnavailable(D), call); }); } bool TypeChecker::diagnoseExplicitUnavailability( const ValueDecl *D, SourceRange R, const DeclContext *DC, llvm::function_ref 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->getPlatformAgnosticAvailability()) { case PlatformAgnosticAvailabilityKind::Deprecated: break; case PlatformAgnosticAvailabilityKind::None: case PlatformAgnosticAvailabilityKind::Unavailable: case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific: case PlatformAgnosticAvailabilityKind::UnavailableInSwift: { bool inSwift = (Attr->getPlatformAgnosticAvailability() == PlatformAgnosticAvailabilityKind::UnavailableInSwift); if (!Attr->Rename.empty()) { SmallString<32> newNameBuf; Optional replaceKind = describeRename(Context, Attr, D, newNameBuf); unsigned rawReplaceKind = static_cast( 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 { EncodedDiagnosticMessage EncodedMessage(Attr->Message); auto diag = diagnose(Loc, diag::availability_decl_unavailable_rename_msg, Name, replaceKind.hasValue(), rawReplaceKind, newName, EncodedMessage.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; } } switch (Attr->getVersionAvailability(Context)) { case AvailableVersionComparison::Available: case AvailableVersionComparison::PotentiallyUnavailable: llvm_unreachable("These aren't considered unavailable"); case AvailableVersionComparison::Unavailable: if (Attr->isLanguageVersionSpecific() && Attr->Introduced.hasValue()) diagnose(D, diag::availability_introduced_in_swift, Name, *Attr->Introduced).highlight(Attr->getRange()); else diagnose(D, diag::availability_marked_unavailable, Name) .highlight(Attr->getRange()); break; case AvailableVersionComparison::Obsoleted: // FIXME: Use of the platformString here is non-awesome for application // extensions. diagnose(D, diag::availability_obsoleted, Name, (Attr->isLanguageVersionSpecific() ? "Swift" : 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 ExprStack; public: AvailabilityWalker( TypeChecker &TC, DeclContext *DC) : TC(TC), DC(DC) {} virtual std::pair 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(E)) diagAvailability(DR->getDecl(), DR->getSourceRange(), getEnclosingApplyExpr()); if (auto MR = dyn_cast(E)) { walkMemberRef(MR); return skipChildren(); } if (auto OCDR = dyn_cast(E)) diagAvailability(OCDR->getDecl(), OCDR->getConstructorLoc().getSourceRange(), getEnclosingApplyExpr()); if (auto DMR = dyn_cast(E)) diagAvailability(DMR->getMember().getDecl(), DMR->getNameLoc().getSourceRange(), getEnclosingApplyExpr()); if (auto DS = dyn_cast(E)) diagAvailability(DS->getMember().getDecl(), DS->getSourceRange()); if (auto S = dyn_cast(E)) { if (S->hasDecl()) diagAvailability(S->getDecl().getDecl(), S->getSourceRange()); } if (auto A = dyn_cast(E)) { walkAssignExpr(A); return skipChildren(); } if (auto IO = dyn_cast(E)) { walkInOutExpr(IO); return skipChildren(); } return visitChildren(); } virtual Expr *walkToExprPost(Expr *E) override { assert(ExprStack.back() == E); ExprStack.pop_back(); return E; } bool diagAvailability(const ValueDecl *D, SourceRange R, const ApplyExpr *call = nullptr, bool AllowPotentiallyUnavailableProtocol = false, bool SignalOnPotentialUnavailability = true); private: 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 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(parents[idx]) || // Mod.f(a) isa(parents[idx]) || // obj.f(a) isa(parents[idx]) || // (f)(a) isa(parents[idx]) || // f!(a) isa(parents[idx])); // f?(a) auto *call = dyn_cast(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(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 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, bool AllowPotentiallyUnavailableProtocol, bool SignalOnPotentialUnavailability) { 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 TC.diagnoseIfDeprecated(R, DC, D, call); if (AllowPotentiallyUnavailableProtocol && isa(D)) return false; // Diagnose (and possibly signal) for potential unavailability auto maybeUnavail = TC.checkDeclarationAvailability(D, R.Start, DC); if (maybeUnavail.hasValue()) { TC.diagnosePotentialUnavailability(D, R, DC, maybeUnavail.getValue()); if (SignalOnPotentialUnavailability) 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(ExprStack[1]) || !(isa(ExprStack[0]) || isa(ExprStack[0]))) return false; auto call = cast(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(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 function, 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; StringRef Property = llvm::StringSwitch(D->getNameStr()) .Case("sizeof", "size") .Case("alignof", "alignment") .Case("strideof", "stride") .Default(StringRef()); if (Property.empty()) return false; auto args = dyn_cast(call->getArg()); if (!args) return false; EncodedDiagnosticMessage EncodedMessage(Attr->Message); auto diag = TC.diagnose(R.Start, diag::availability_decl_unavailable_msg, D->getFullName(), EncodedMessage.Message); diag.highlight(R); auto subject = args->getSubExpr(); StringRef Prefix = "MemoryLayout<"; StringRef Suffix = ">."; if (auto DTE = dyn_cast(subject)) { // Replace `sizeof(type(of: x))` with `MemoryLayout.size`, where `X` is // the static type of `x`. The previous spelling misleadingly hinted that // `sizeof(_:)` might return the size of the *dynamic* type of `x`, when // it is not the case. auto valueType = DTE->getBase()->getType()->getRValueType(); if (!valueType || valueType->hasError()) { // If we don't have a suitable argument, we can't emit a fixit. return true; } // Note that in rare circumstances we may be destructively replacing the // source text. For example, we'd replace `sizeof(type(of: doSomething()))` // with `MemoryLayout.size`, if T is the return type of `doSomething()`. diag.fixItReplace(call->getSourceRange(), (Prefix + valueType->getString() + Suffix + Property).str()); } else { SourceRange PrefixRange(call->getStartLoc(), args->getLParenLoc()); SourceRange SuffixRange(args->getRParenLoc()); // We must remove `.self`. if (auto *DSE = dyn_cast(subject)) SuffixRange.Start = DSE->getDotLoc(); diag .fixItReplace(PrefixRange, Prefix) .fixItReplace(SuffixRange, (Suffix + Property).str()); } return true; } /// Diagnose uses of unavailable declarations. static void diagAvailability(TypeChecker &TC, const Expr *E, DeclContext *DC) { AvailabilityWalker walker(TC, DC); const_cast(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 VarDecls; /// This is a mapping from an OpaqueValue to the expression that initialized /// it. llvm::SmallDenseMap 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 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(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(D)) return false; // If this is a VarDecl, then add it to our list of things to track. if (auto *vd = dyn_cast(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(Parent.getAsExpr())) defaultFlags = RK_CaptureList; VarDecls[vd] |= defaultFlags; } if (auto *afd = dyn_cast(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 walkToExprPre(Expr *E) override; /// handle #if directives. void handleIfConfig(IfConfigStmt *ICS); /// Custom handling for statements. std::pair walkToStmtPre(Stmt *S) override { // The body of #if statements are not walked into, we need custom processing // for them. if (auto *ICS = dyn_cast(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(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()) 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()) 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(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 && !isa(pbd->getPatternList()[0].getPattern())) { unsigned varKind = var->isLet(); SourceRange replaceRange( pbd->getStartLoc(), pbd->getPatternList()[0].getPattern()->getEndLoc()); TC.diagnose(var->getLoc(), diag::pbd_never_used, var->getName(), varKind) .fixItReplace(replaceRange, "_"); 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 = { // // we prefer to rewrite it to: // // if != 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(pattern)) if (auto LP = dyn_cast(OSP->getSubPattern())) if (isa(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(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(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(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(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(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())) { 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(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(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()->hasError()) { 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(E)) { addMark(DRE->getDecl(), Flags); return; } if (auto *TE = dyn_cast(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(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(E)) return markStoredOrInOutExpr(ioe->getSubExpr(), RK_Written|RK_Read); if (auto *MRE = dyn_cast(E)) { markBaseOfAbstractStorageDeclStore(MRE->getBase(), MRE->getMember()); return; } if (auto *TEE = dyn_cast(E)) return markStoredOrInOutExpr(TEE->getBase(), Flags); if (auto *FVE = dyn_cast(E)) return markStoredOrInOutExpr(FVE->getSubExpr(), Flags); if (auto *BOE = dyn_cast(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(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 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()->hasError()) { 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(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(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(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(E)) OpaqueValueMap[oee->getOpaqueValue()] = oee->getExistentialValue(); // If we saw an ErrorExpr, take note of this. if (isa(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(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(Cond); if (!callExpr) return nullptr; auto dotSyntaxExpr = dyn_cast(callExpr->getFn()); if (!dotSyntaxExpr) return nullptr; auto binaryExpr = dyn_cast(dotSyntaxExpr->getBase()); if (!binaryExpr) return nullptr; auto binaryFuncExpr = dyn_cast(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(args[0]); if (!loadExpr) return nullptr; auto declRefExpr = dyn_cast(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(Increment); if (!unaryExpr) unaryExpr = dyn_cast(Increment); if (!unaryExpr) return false; auto inoutExpr = dyn_cast(unaryExpr->getArg()); if (!inoutExpr) return false; auto incrementDeclRefExpr = dyn_cast(inoutExpr->getSubExpr()); if (!incrementDeclRefExpr) return false; auto unaryFuncExpr = dyn_cast(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(Increment); if (!binaryExpr) return false; auto binaryFuncExpr = dyn_cast(binaryExpr->getFn()); if (!binaryFuncExpr) return false; if (binaryFuncExpr->getDecl()->getNameStr() != OpName) return false; auto argTupleExpr = dyn_cast(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(argTupleExpr->getElement(0)); if (!inoutExpr) return false; auto declRefExpr = dyn_cast(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(initializers[0]) : nullptr; if (!loopVarDecl || loopVarDecl->getNumPatternEntries() != 1) return; VarDecl *loopVar = dyn_cast(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()) { // We forgot to do this in Swift 3 if (!TC.Context.isSwiftVersion3()) TC.checkUnsupportedProtocolType(cs); // 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()); } } } void swift::fixItEncloseTrailingClosure(TypeChecker &TC, InFlightDiagnostic &diag, const CallExpr *call, Identifier closureLabel) { auto argsExpr = call->getArg(); if (auto TSE = dyn_cast(argsExpr)) argsExpr = TSE->getSubExpr(); SmallString<32> replacement; SourceLoc lastLoc; SourceRange closureRange; if (auto PE = dyn_cast(argsExpr)) { assert(PE->hasTrailingClosure() && "must have trailing closure"); closureRange = PE->getSubExpr()->getSourceRange(); lastLoc = PE->getLParenLoc(); // e.g funcName() { 1 } if (!lastLoc.isValid()) { // Bare trailing closure: e.g. funcName { 1 } replacement = "("; lastLoc = call->getFn()->getEndLoc(); } } else if (auto TE = dyn_cast(argsExpr)) { // Tuple + trailing closure: e.g. funcName(x: 1) { 1 } assert(TE->hasTrailingClosure() && "must have trailing closure"); 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. if (!closureLabel.empty()) { replacement += closureLabel.str(); replacement += ": "; } lastLoc = Lexer::getLocForEndOfToken(TC.Context.SourceMgr, lastLoc); diag .fixItReplaceChars(lastLoc, closureRange.Start, replacement) .fixItInsertAfter(closureRange.End, ")"); } // Perform checkStmtConditionTrailingClosure for single expression. static void checkStmtConditionTrailingClosure(TypeChecker &TC, const Expr *E) { if (E == nullptr || isa(E)) return; // Shallow walker. just dig into implicit expression. class DiagnoseWalker : public ASTWalker { TypeChecker &TC; void diagnoseIt(const CallExpr *E) { if (!E->hasTrailingClosure()) return; auto argsExpr = E->getArg(); auto argsTy = argsExpr->getType(); // Ignore invalid argument type. Some diagnostics are already emitted. if (!argsTy || argsTy->hasError()) return; if (auto TSE = dyn_cast(argsExpr)) argsExpr = TSE->getSubExpr(); SourceLoc closureLoc; if (auto PE = dyn_cast(argsExpr)) closureLoc = PE->getSubExpr()->getStartLoc(); else if (auto TE = dyn_cast(argsExpr)) closureLoc = TE->getElements().back()->getStartLoc(); Identifier closureLabel; if (auto TT = argsTy->getAs()) { assert(TT->getNumElements() != 0 && "Unexpected empty TupleType"); closureLabel = TT->getElement(TT->getNumElements() - 1).getName(); } auto diag = TC.diagnose(closureLoc, diag::trailing_closure_requires_parens); fixItEncloseTrailingClosure(TC, diag, E, closureLabel); } public: DiagnoseWalker(TypeChecker &tc) : TC(tc) { } virtual std::pair walkToExprPre(Expr *E) override { // Dig into implicit expression. if (E->isImplicit()) return { true, E }; // Diagnose call expression. if (auto CE = dyn_cast(E)) diagnoseIt(CE); // Don't dig any further. return { false, E }; } }; DiagnoseWalker Walker(TC); const_cast(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(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(S)) { checkStmtConditionTrailingClosure(TC, SS->getSubjectExpr()); } else if (auto FES = dyn_cast(S)) { checkStmtConditionTrailingClosure(TC, FES->getSequence()); checkStmtConditionTrailingClosure(TC, FES->getWhere()); } else if (auto DCS = dyn_cast(S)) { for (auto CS : DCS->getCatches()) checkStmtConditionTrailingClosure(TC, CS->getGuardExpr()); } } static Optional 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 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(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(result[0].Decl); auto secondMethod = dyn_cast(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 walkToExprPre(Expr *expr) override { StringLiteralExpr *stringLiteral = dyn_cast(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(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(call->getFn())) { if (auto ctorRef = dyn_cast(ctorRefCall->getFn())) ctor = dyn_cast(ctorRef->getDecl()); else if (auto otherCtorRef = dyn_cast(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(arg)) arg = paren->getSubExpr(); else if (auto tuple = dyn_cast(arg)) arg = tuple->getElement(0); else return { true, expr }; // Track whether we had parentheses around the string literal. if (auto paren = dyn_cast(arg)) { hadParens = true; arg = paren->getSubExpr(); } // Check whether we have a string literal. stringLiteral = dyn_cast(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(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 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(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()) { // For static/class members, drop the metatype argument. if (bestMethod->isStatic()) fnType = fnType->getResult()->getAs(); // 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(dc)); if (!selectorTy) return; const_cast(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(p)) // Check for 'if let' to produce a tuned diagnostic. if (isa(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()); } } static void diagnoseUnintendedOptionalBehavior(TypeChecker &TC, const Expr *E, const DeclContext *DC) { if (!E || isa(E) || !E->getType()) return; class UnintendedOptionalBehaviorWalker : public ASTWalker { TypeChecker &TC; SmallPtrSet ErasureCoercedToAny; virtual std::pair walkToExprPre(Expr *E) { if (!E || isa(E) || !E->getType()) return { false, E }; if (auto *coercion = dyn_cast(E)) { if (E->getType()->isAny() && isa(coercion->getSubExpr())) ErasureCoercedToAny.insert(coercion->getSubExpr()); } else if (isa(E) && !ErasureCoercedToAny.count(E) && E->getType()->isAny()) { auto subExpr = cast(E)->getSubExpr(); auto erasedTy = subExpr->getType(); if (erasedTy->getOptionalObjectType()) { TC.diagnose(subExpr->getStartLoc(), diag::optional_to_any_coercion, erasedTy) .highlight(subExpr->getSourceRange()); TC.diagnose(subExpr->getLoc(), diag::default_optional_to_any) .highlight(subExpr->getSourceRange()) .fixItInsertAfter(subExpr->getEndLoc(), " ?? <#default value#>"); TC.diagnose(subExpr->getLoc(), diag::force_optional_to_any) .highlight(subExpr->getSourceRange()) .fixItInsertAfter(subExpr->getEndLoc(), "!"); TC.diagnose(subExpr->getLoc(), diag::silence_optional_to_any) .highlight(subExpr->getSourceRange()) .fixItInsertAfter(subExpr->getEndLoc(), " as Any"); } } else if (auto *literal = dyn_cast(E)) { // Warn about interpolated segments that contain optionals. for (auto &segment : literal->getSegments()) { // Allow explicit casts. if (auto paren = dyn_cast(segment)) { if (isa(paren->getSubExpr())) { continue; } } // Bail out if we don't have an optional. if (!segment->getType()->getRValueType()->getOptionalObjectType()) { continue; } TC.diagnose(segment->getStartLoc(), diag::optional_in_string_interpolation_segment) .highlight(segment->getSourceRange()); // Suggest 'String(describing: )'. auto segmentStart = segment->getStartLoc().getAdvancedLoc(1); TC.diagnose(segment->getLoc(), diag::silence_optional_in_interpolation_segment_call) .highlight(segment->getSourceRange()) .fixItInsert(segmentStart, "String(describing: ") .fixItInsert(segment->getEndLoc(), ")"); // Suggest inserting a default value. TC.diagnose(segment->getLoc(), diag::default_optional_to_any) .highlight(segment->getSourceRange()) .fixItInsert(segment->getEndLoc(), " ?? <#default value#>"); } } return { true, E }; } public: UnintendedOptionalBehaviorWalker(TypeChecker &tc) : TC(tc) { } }; UnintendedOptionalBehaviorWalker Walker(TC); const_cast(E)->walk(Walker); } //===----------------------------------------------------------------------===// // 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); diagnoseUnintendedOptionalBehavior(TC, E, DC); if (!TC.getLangOpts().DisableAvailabilityChecking) diagAvailability(TC, E, const_cast(DC)); if (TC.Context.LangOpts.EnableObjCInterop) diagDeprecatedObjCSelectors(TC, DC, E); } void swift::performStmtDiagnostics(TypeChecker &TC, const Stmt *S) { TC.checkUnsupportedProtocolType(const_cast(S)); if (auto forStmt = dyn_cast(S)) checkCStyleForLoop(TC, forStmt); if (auto switchStmt = dyn_cast(S)) checkSwitch(TC, switchStmt); checkStmtConditionTrailingClosure(TC, S); // Check for implicit optional promotions in stmt-condition patterns. if (auto *lcs = dyn_cast(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(); AbstractAccessibilityAttr *attr; if (isForSetter) { attr = attrs.getAttribute(); cast(VD)->overwriteSetterAccessibility(desiredAccess); } else { attr = attrs.getAttribute(); VD->overwriteAccessibility(desiredAccess); if (auto *ASD = dyn_cast(VD)) { if (auto *getter = ASD->getGetter()) getter->overwriteAccessibility(desiredAccess); if (auto *setterAttr = attrs.getAttribute()) { 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) { // If the formal access already matches the desired access, the problem // must be in a parent scope. Don't emit a fix-it. // FIXME: It's also possible for access to already be /broader/ than what's // desired, in which case the problem is also in a parent scope. However, // this function is sometimes called to make access narrower, so assuming // that a broader scope is acceptable breaks some diagnostics. if (attr->getAccess() != desiredAccess) { // 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(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(type.getPointer())) { type = aliasTy->getSinglyDesugaredType(); 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. type = type->getWithoutParens(); // 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()) { 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()) { if (auto genericParam = genericParamTy->getDecl()) return genericParam->getName().str(); return ""; } // Dependent members. if (auto dependentMemberTy = type->getAs()) { return dependentMemberTy->getName().str(); } // Archetypes. if (auto archetypeTy = type->getAs()) { return archetypeTy->getName().str(); } // Function types. if (auto funcTy = type->getAs()) { if (funcTy->getRepresentation() == AnyFunctionType::Representation::Block) return "Block"; return "Function"; } return ""; } Optional TypeChecker::omitNeedlessWords(AbstractFunctionDecl *afd) { auto &Context = afd->getASTContext(); if (!afd->hasInterfaceType()) validateDecl(afd); if (afd->isInvalid() || isa(afd)) return None; DeclName name = afd->getFullName(); if (!name) return None; // String'ify the arguments. StringRef baseNameStr = name.getBaseName().str(); SmallVector argNameStrs; for (auto arg : name.getArgumentNames()) { if (arg.empty()) argNameStrs.push_back(""); else argNameStrs.push_back(arg.str()); } // String'ify the parameter types. SmallVector 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(afd)) { resultType = func->getResultInterfaceType(); resultType = ArchetypeBuilder::mapTypeIntoContext(func, resultType); returnsSelf = func->hasDynamicSelf(); } else if (isa(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 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 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; } /// Run the Availability-diagnostics algorithm otherwise used in an expr /// context, but for non-expr contexts such as TypeDecls referenced from /// TypeReprs. bool swift::diagnoseDeclAvailability(const ValueDecl *Decl, TypeChecker &TC, DeclContext *DC, SourceRange R, bool AllowPotentiallyUnavailableProtocol, bool SignalOnPotentialUnavailability) { AvailabilityWalker AW(TC, DC); return AW.diagAvailability(Decl, R, nullptr, AllowPotentiallyUnavailableProtocol, SignalOnPotentialUnavailability); }