Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3768,7 +3768,11 @@ ERROR(should_use_empty_dictionary_literal,none,
// Dictionary literals
ERROR(type_is_not_dictionary,none,
"contextual type %0 cannot be used with dictionary literal", (Type))

WARNING(duplicated_literal_keys_in_dictionary_literal, none,
"dictionary literal of type %0 has duplicate entries for %1 literal key%select{ %3|}2",
(Type, StringRef, bool, StringRef))
NOTE(duplicated_key_declared_here, none,
"duplicate key declared here", ())

// Generic specializations
ERROR(cannot_explicitly_specialize_generic_function,none,
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,9 @@ class LiteralExpr : public Expr {
void setInitializer(ConcreteDeclRef initializer) {
Initializer = initializer;
}

/// Get description string for the literal expression.
StringRef getLiteralKindDescription() const;
};

/// BuiltinLiteralExpr - Common base class between all literals
Expand Down
32 changes: 32 additions & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,38 @@ bool Expr::isValidParentOfTypeExpr(Expr *typeExpr) const {
// Support methods for Exprs.
//===----------------------------------------------------------------------===//

StringRef LiteralExpr::getLiteralKindDescription() const {
switch (getKind()) {
case ExprKind::IntegerLiteral:
return "integer";
case ExprKind::FloatLiteral:
return "floating-point";
case ExprKind::BooleanLiteral:
return "boolean";
case ExprKind::StringLiteral:
return "string";
case ExprKind::InterpolatedStringLiteral:
return "string";
case ExprKind::RegexLiteral:
return "regular expression";
case ExprKind::MagicIdentifierLiteral:
return MagicIdentifierLiteralExpr::getKindString(
cast<MagicIdentifierLiteralExpr>(this)->getKind());
case ExprKind::NilLiteral:
return "nil";
case ExprKind::ObjectLiteral:
return "object";
// Define an unreachable case for all non-literal expressions.
// This way, if a new literal is added in the future, the compiler
// will warn that a case is missing from this switch.
#define LITERAL_EXPR(Id, Parent)
#define EXPR(Id, Parent) case ExprKind::Id:
#include "swift/AST/ExprNodes.def"
llvm_unreachable("Not a literal expression");
}
llvm_unreachable("Unhandled literal");
}

IntegerLiteralExpr * IntegerLiteralExpr::createFromUnsigned(ASTContext &C, unsigned value) {
llvm::SmallString<8> Scratch;
llvm::APInt(sizeof(unsigned)*8, value).toString(Scratch, 10, /*signed*/ false);
Expand Down
158 changes: 158 additions & 0 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5011,6 +5011,163 @@ static void diagUnqualifiedAccessToMethodNamedSelf(const Expr *E,
const_cast<Expr *>(E)->walk(Walker);
}

static void
diagnoseDictionaryLiteralDuplicateKeyEntries(const Expr *E,
const DeclContext *DC) {
class DiagnoseWalker : public ASTWalker {
ASTContext &Ctx;

private:
std::string getKeyStringValue(const LiteralExpr *keyExpr) {
if (isa<MagicIdentifierLiteralExpr>(keyExpr)) {
return keyExpr->getLiteralKindDescription().str();
}
std::string out;
llvm::raw_string_ostream OS(out);
keyExpr->printConstExprValue(&OS, /*additionalCheck=*/nullptr);
return out;
}

std::string getKeyStringValueForDiagnostic(const LiteralExpr *keyExpr) {
std::string out;
switch (keyExpr->getKind()) {
case ExprKind::NilLiteral:
case ExprKind::MagicIdentifierLiteral:
return out;
case ExprKind::StringLiteral: {
const auto *SL = cast<StringLiteralExpr>(keyExpr);
out = SL->getValue().str();
break;
}
default:
llvm::raw_string_ostream OS(out);
keyExpr->printConstExprValue(&OS, /*additionalCheck=*/nullptr);
break;
}
return "'" + out + "'";
}

bool shouldDiagnoseLiteral(const LiteralExpr *LE) {
switch (LE->getKind()) {
case ExprKind::IntegerLiteral:
case ExprKind::FloatLiteral:
case ExprKind::BooleanLiteral:
case ExprKind::StringLiteral:
case ExprKind::MagicIdentifierLiteral:
case ExprKind::NilLiteral:
return true;
// Skip interpolated literals because they
// can contain expressions that although equal
// maybe be evaluated to different values. e.g.
// "\(a) \(a)" where 'a' is a computed variable.
case ExprKind::InterpolatedStringLiteral:
// Also skip object literals as most of them takes paramenters that can
// contain expressions that altough equal may evaluate to different
// values e.g. #fileLiteral(resourceName: a) where 'a' is a computed
// property is valid.
case ExprKind::ObjectLiteral:
// Literal expressions produce Regex<Out> type result,
// which cannot be keys due to not conforming to hashable.
case ExprKind::RegexLiteral:
return false;
// If a new literal is added in the future, the compiler
// will warn that a case is missing from this switch.
#define LITERAL_EXPR(Id, Parent)
#define EXPR(Id, Parent) case ExprKind::Id:
#include "swift/AST/ExprNodes.def"
llvm_unreachable("Not a literal expression");
}
llvm_unreachable("Unhandled literal");
}
public:
DiagnoseWalker(const DeclContext *DC) : Ctx(DC->getASTContext()) {}

bool shouldWalkIntoSeparatelyCheckedClosure(ClosureExpr *expr) override {
return false;
}

bool shouldWalkIntoTapExpression() override { return false; }

std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
const auto *DLE = dyn_cast_or_null<DictionaryExpr>(E);
if (!DLE)
return {true, E};

auto type = DLE->getType();
// For other types conforming with `ExpressibleByDictionaryLiteral`
// protocol, duplicated keys may be allowed.
if (!(type && type->isDictionary())) {
return {true, E};
}

using LiteralKey = std::pair<std::string, ExprKind>;
using Element = std::pair<const TupleExpr *, size_t>;

std::map<LiteralKey, llvm::SmallVector<Element, 4>> groupedLiteralKeys;

for (size_t i = 0; i < DLE->getElements().size(); ++i) {
const auto *elt = DLE->getElement(i);
const auto *tupleElt = cast<TupleExpr>(elt);
const auto *keyExpr =
tupleElt->getElement(0)->getSemanticsProvidingExpr();
auto *LE = dyn_cast<LiteralExpr>(keyExpr);
if (!LE)
continue;

if (!shouldDiagnoseLiteral(LE))
continue;

auto keyStringValue = getKeyStringValue(LE);
auto literalKey = std::make_pair(keyStringValue, keyExpr->getKind());
groupedLiteralKeys[literalKey].push_back({tupleElt, i});
}

// All keys are unique.
if (groupedLiteralKeys.size() == DLE->getNumElements()) {
return {true, E};
}

auto &DE = Ctx.Diags;
auto emitNoteWithFixit = [&](const Element &duplicated) {
auto note = DE.diagnose(duplicated.first->getLoc(),
diag::duplicated_key_declared_here);
auto duplicatedEltIdx = duplicated.second;
const auto commanLocs = DLE->getCommaLocs();
note.fixItRemove(duplicated.first->getSourceRange());
if (duplicatedEltIdx < commanLocs.size()) {
note.fixItRemove(commanLocs[duplicatedEltIdx]);
} else {
// For the last element remove the previous comma.
note.fixItRemove(commanLocs[duplicatedEltIdx - 1]);
}
};

for (auto &entry : groupedLiteralKeys) {
auto &keyValuePairs = entry.second;
if (keyValuePairs.size() == 1) {
continue;
}

auto elt = keyValuePairs.front();
const auto keyValue = entry.first.first;
const auto keyExpr = cast<LiteralExpr>(
elt.first->getElement(0)->getSemanticsProvidingExpr());
const auto value = getKeyStringValueForDiagnostic(keyExpr);
DE.diagnose(elt.first->getLoc(),
diag::duplicated_literal_keys_in_dictionary_literal, type,
keyExpr->getLiteralKindDescription(), value.empty(), value);
for (auto &duplicated : keyValuePairs) {
emitNoteWithFixit(duplicated);
}
}
return {true, E};
}
};

DiagnoseWalker Walker(DC);
const_cast<Expr *>(E)->walk(Walker);
}

namespace {

class CompletionHandlerUsageChecker final : public ASTWalker {
Expand Down Expand Up @@ -5098,6 +5255,7 @@ void swift::performSyntacticExprDiagnostics(const Expr *E,
diagDeprecatedObjCSelectors(DC, E);
diagnoseConstantArgumentRequirement(E, DC);
diagUnqualifiedAccessToMethodNamedSelf(E, DC);
diagnoseDictionaryLiteralDuplicateKeyEntries(E, DC);
}

void swift::performStmtDiagnostics(const Stmt *S, DeclContext *DC) {
Expand Down
28 changes: 1 addition & 27 deletions lib/Sema/TypeCheckStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1227,34 +1227,8 @@ static bool isDiscardableType(Type type) {
}

static void diagnoseIgnoredLiteral(ASTContext &Ctx, LiteralExpr *LE) {
const auto getLiteralDescription = [](LiteralExpr *LE) -> StringRef {
switch (LE->getKind()) {
case ExprKind::IntegerLiteral: return "integer";
case ExprKind::FloatLiteral: return "floating-point";
case ExprKind::BooleanLiteral: return "boolean";
case ExprKind::StringLiteral: return "string";
case ExprKind::InterpolatedStringLiteral: return "string";
case ExprKind::RegexLiteral: return "regular expression";
case ExprKind::MagicIdentifierLiteral:
return MagicIdentifierLiteralExpr::getKindString(
cast<MagicIdentifierLiteralExpr>(LE)->getKind());
case ExprKind::NilLiteral: return "nil";
case ExprKind::ObjectLiteral: return "object";

// Define an unreachable case for all non-literal expressions.
// This way, if a new literal is added in the future, the compiler
// will warn that a case is missing from this switch.
#define LITERAL_EXPR(Id, Parent)
#define EXPR(Id, Parent) case ExprKind::Id:
#include "swift/AST/ExprNodes.def"
llvm_unreachable("Not a literal expression");
}

llvm_unreachable("Unhandled ExprKind in switch.");
};

Ctx.Diags.diagnose(LE->getLoc(), diag::expression_unused_literal,
getLiteralDescription(LE))
LE->getLiteralKindDescription())
.highlight(LE->getSourceRange());
}

Expand Down
2 changes: 1 addition & 1 deletion test/Constraints/bridging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ func rdar60501780() {
func foo(_: [String: NSObject]) {}

func bar(_ v: String) {
foo(["": "", "": v as NSString])
foo(["a": "", "b": v as NSString])
}
}

Expand Down
Loading