Skip to content

Commit ee20fec

Browse files
[Sema] Warn about dictionary literal of dictionary type about duplicated keys
1 parent 9c4972f commit ee20fec

File tree

6 files changed

+410
-2
lines changed

6 files changed

+410
-2
lines changed

Diff for: include/swift/AST/DiagnosticsSema.def

+5-1
Original file line numberDiff line numberDiff line change
@@ -3768,7 +3768,11 @@ ERROR(should_use_empty_dictionary_literal,none,
37683768
// Dictionary literals
37693769
ERROR(type_is_not_dictionary,none,
37703770
"contextual type %0 cannot be used with dictionary literal", (Type))
3771-
3771+
WARNING(duplicated_literal_keys_in_dictionary_literal, none,
3772+
"dictionary literal of type %0 has duplicate entries for %1 literal key%select{ %3|}2",
3773+
(Type, StringRef, bool, StringRef))
3774+
NOTE(duplicated_key_declared_here, none,
3775+
"duplicate key declared here", ())
37723776

37733777
// Generic specializations
37743778
ERROR(cannot_explicitly_specialize_generic_function,none,

Diff for: include/swift/AST/Expr.h

+3
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,9 @@ class LiteralExpr : public Expr {
617617
void setInitializer(ConcreteDeclRef initializer) {
618618
Initializer = initializer;
619619
}
620+
621+
/// Get description string for the literal expression.
622+
StringRef getLiteralKindDescription() const;
620623
};
621624

622625
/// BuiltinLiteralExpr - Common base class between all literals

Diff for: lib/AST/Expr.cpp

+32
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,38 @@ bool Expr::isValidParentOfTypeExpr(Expr *typeExpr) const {
972972
// Support methods for Exprs.
973973
//===----------------------------------------------------------------------===//
974974

975+
StringRef LiteralExpr::getLiteralKindDescription() const {
976+
switch (getKind()) {
977+
case ExprKind::IntegerLiteral:
978+
return "integer";
979+
case ExprKind::FloatLiteral:
980+
return "floating-point";
981+
case ExprKind::BooleanLiteral:
982+
return "boolean";
983+
case ExprKind::StringLiteral:
984+
return "string";
985+
case ExprKind::InterpolatedStringLiteral:
986+
return "string";
987+
case ExprKind::RegexLiteral:
988+
return "regular expression";
989+
case ExprKind::MagicIdentifierLiteral:
990+
return MagicIdentifierLiteralExpr::getKindString(
991+
cast<MagicIdentifierLiteralExpr>(this)->getKind());
992+
case ExprKind::NilLiteral:
993+
return "nil";
994+
case ExprKind::ObjectLiteral:
995+
return "object";
996+
// Define an unreachable case for all non-literal expressions.
997+
// This way, if a new literal is added in the future, the compiler
998+
// will warn that a case is missing from this switch.
999+
#define LITERAL_EXPR(Id, Parent)
1000+
#define EXPR(Id, Parent) case ExprKind::Id:
1001+
#include "swift/AST/ExprNodes.def"
1002+
llvm_unreachable("Not a literal expression");
1003+
}
1004+
llvm_unreachable("Unhandled literal");
1005+
}
1006+
9751007
IntegerLiteralExpr * IntegerLiteralExpr::createFromUnsigned(ASTContext &C, unsigned value) {
9761008
llvm::SmallString<8> Scratch;
9771009
llvm::APInt(sizeof(unsigned)*8, value).toString(Scratch, 10, /*signed*/ false);

Diff for: lib/Sema/MiscDiagnostics.cpp

+158
Original file line numberDiff line numberDiff line change
@@ -5011,6 +5011,163 @@ static void diagUnqualifiedAccessToMethodNamedSelf(const Expr *E,
50115011
const_cast<Expr *>(E)->walk(Walker);
50125012
}
50135013

5014+
static void
5015+
diagnoseDictionaryLiteralDuplicateKeyEntries(const Expr *E,
5016+
const DeclContext *DC) {
5017+
class DiagnoseWalker : public ASTWalker {
5018+
ASTContext &Ctx;
5019+
5020+
private:
5021+
std::string getKeyStringValue(const LiteralExpr *keyExpr) {
5022+
if (isa<MagicIdentifierLiteralExpr>(keyExpr)) {
5023+
return keyExpr->getLiteralKindDescription().str();
5024+
}
5025+
std::string out;
5026+
llvm::raw_string_ostream OS(out);
5027+
keyExpr->printConstExprValue(&OS, /*additionalCheck=*/nullptr);
5028+
return out;
5029+
}
5030+
5031+
std::string getKeyStringValueForDiagnostic(const LiteralExpr *keyExpr) {
5032+
std::string out;
5033+
switch (keyExpr->getKind()) {
5034+
case ExprKind::NilLiteral:
5035+
case ExprKind::MagicIdentifierLiteral:
5036+
return out;
5037+
case ExprKind::StringLiteral: {
5038+
const auto *SL = cast<StringLiteralExpr>(keyExpr);
5039+
out = SL->getValue().str();
5040+
break;
5041+
}
5042+
default:
5043+
llvm::raw_string_ostream OS(out);
5044+
keyExpr->printConstExprValue(&OS, /*additionalCheck=*/nullptr);
5045+
break;
5046+
}
5047+
return "'" + out + "'";
5048+
}
5049+
5050+
bool shouldDiagnoseLiteral(const LiteralExpr *LE) {
5051+
switch (LE->getKind()) {
5052+
case ExprKind::IntegerLiteral:
5053+
case ExprKind::FloatLiteral:
5054+
case ExprKind::BooleanLiteral:
5055+
case ExprKind::StringLiteral:
5056+
case ExprKind::MagicIdentifierLiteral:
5057+
case ExprKind::NilLiteral:
5058+
return true;
5059+
// Skip interpolated literals because they
5060+
// can contain expressions that although equal
5061+
// maybe be evaluated to different values. e.g.
5062+
// "\(a) \(a)" where 'a' is a computed variable.
5063+
case ExprKind::InterpolatedStringLiteral:
5064+
// Also skip object literals as most of them takes paramenters that can
5065+
// contain expressions that altough equal may evaluate to different
5066+
// values e.g. #fileLiteral(resourceName: a) where 'a' is a computed
5067+
// property is valid.
5068+
case ExprKind::ObjectLiteral:
5069+
// Literal expressions produce Regex<Out> type result,
5070+
// which cannot be keys due to not conforming to hashable.
5071+
case ExprKind::RegexLiteral:
5072+
return false;
5073+
// If a new literal is added in the future, the compiler
5074+
// will warn that a case is missing from this switch.
5075+
#define LITERAL_EXPR(Id, Parent)
5076+
#define EXPR(Id, Parent) case ExprKind::Id:
5077+
#include "swift/AST/ExprNodes.def"
5078+
llvm_unreachable("Not a literal expression");
5079+
}
5080+
llvm_unreachable("Unhandled literal");
5081+
}
5082+
public:
5083+
DiagnoseWalker(const DeclContext *DC) : Ctx(DC->getASTContext()) {}
5084+
5085+
bool shouldWalkIntoSeparatelyCheckedClosure(ClosureExpr *expr) override {
5086+
return false;
5087+
}
5088+
5089+
bool shouldWalkIntoTapExpression() override { return false; }
5090+
5091+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
5092+
const auto *DLE = dyn_cast_or_null<DictionaryExpr>(E);
5093+
if (!DLE)
5094+
return {true, E};
5095+
5096+
auto type = DLE->getType();
5097+
// For other types conforming with `ExpressibleByDictionaryLiteral`
5098+
// protocol, duplicated keys may be allowed.
5099+
if (!(type && type->isDictionary())) {
5100+
return {true, E};
5101+
}
5102+
5103+
using LiteralKey = std::pair<std::string, ExprKind>;
5104+
using Element = std::pair<const TupleExpr *, size_t>;
5105+
5106+
std::map<LiteralKey, llvm::SmallVector<Element, 4>> groupedLiteralKeys;
5107+
5108+
for (size_t i = 0; i < DLE->getElements().size(); ++i) {
5109+
const auto *elt = DLE->getElement(i);
5110+
const auto *tupleElt = cast<TupleExpr>(elt);
5111+
const auto *keyExpr =
5112+
tupleElt->getElement(0)->getSemanticsProvidingExpr();
5113+
auto *LE = dyn_cast<LiteralExpr>(keyExpr);
5114+
if (!LE)
5115+
continue;
5116+
5117+
if (!shouldDiagnoseLiteral(LE))
5118+
continue;
5119+
5120+
auto keyStringValue = getKeyStringValue(LE);
5121+
auto literalKey = std::make_pair(keyStringValue, keyExpr->getKind());
5122+
groupedLiteralKeys[literalKey].push_back({tupleElt, i});
5123+
}
5124+
5125+
// All keys are unique.
5126+
if (groupedLiteralKeys.size() == DLE->getNumElements()) {
5127+
return {true, E};
5128+
}
5129+
5130+
auto &DE = Ctx.Diags;
5131+
auto emitNoteWithFixit = [&](const Element &duplicated) {
5132+
auto note = DE.diagnose(duplicated.first->getLoc(),
5133+
diag::duplicated_key_declared_here);
5134+
auto duplicatedEltIdx = duplicated.second;
5135+
const auto commanLocs = DLE->getCommaLocs();
5136+
note.fixItRemove(duplicated.first->getSourceRange());
5137+
if (duplicatedEltIdx < commanLocs.size()) {
5138+
note.fixItRemove(commanLocs[duplicatedEltIdx]);
5139+
} else {
5140+
// For the last element remove the previous comma.
5141+
note.fixItRemove(commanLocs[duplicatedEltIdx - 1]);
5142+
}
5143+
};
5144+
5145+
for (auto &entry : groupedLiteralKeys) {
5146+
auto &keyValuePairs = entry.second;
5147+
if (keyValuePairs.size() == 1) {
5148+
continue;
5149+
}
5150+
5151+
auto elt = keyValuePairs.front();
5152+
const auto keyValue = entry.first.first;
5153+
const auto keyExpr = cast<LiteralExpr>(
5154+
elt.first->getElement(0)->getSemanticsProvidingExpr());
5155+
const auto value = getKeyStringValueForDiagnostic(keyExpr);
5156+
DE.diagnose(elt.first->getLoc(),
5157+
diag::duplicated_literal_keys_in_dictionary_literal, type,
5158+
keyExpr->getLiteralKindDescription(), value.empty(), value);
5159+
for (auto &duplicated : keyValuePairs) {
5160+
emitNoteWithFixit(duplicated);
5161+
}
5162+
}
5163+
return {true, E};
5164+
}
5165+
};
5166+
5167+
DiagnoseWalker Walker(DC);
5168+
const_cast<Expr *>(E)->walk(Walker);
5169+
}
5170+
50145171
namespace {
50155172

50165173
class CompletionHandlerUsageChecker final : public ASTWalker {
@@ -5098,6 +5255,7 @@ void swift::performSyntacticExprDiagnostics(const Expr *E,
50985255
diagDeprecatedObjCSelectors(DC, E);
50995256
diagnoseConstantArgumentRequirement(E, DC);
51005257
diagUnqualifiedAccessToMethodNamedSelf(E, DC);
5258+
diagnoseDictionaryLiteralDuplicateKeyEntries(E, DC);
51015259
}
51025260

51035261
void swift::performStmtDiagnostics(const Stmt *S, DeclContext *DC) {

Diff for: test/Constraints/bridging.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ func rdar60501780() {
391391
func foo(_: [String: NSObject]) {}
392392

393393
func bar(_ v: String) {
394-
foo(["": "", "": v as NSString])
394+
foo(["a": "", "b": v as NSString])
395395
}
396396
}
397397

0 commit comments

Comments
 (0)