Skip to content

Commit 43d2904

Browse files
authored
Merge pull request #27089 from Vercantez/warn-on-empty-optionset-static-constants
warn on empty OptionSet static constants
2 parents 1da224d + 3dba00f commit 43d2904

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,12 @@ ERROR(corresponding_param_not_defaulted,none,
14211421
NOTE(inherited_default_param_here,none,
14221422
"corresponding parameter declared here", ())
14231423

1424+
WARNING(option_set_zero_constant,none,
1425+
"static property %0 produces an empty option set",
1426+
(Identifier))
1427+
NOTE(option_set_empty_set_init,none,
1428+
"use [] to silence this warning", ())
1429+
14241430
// Alignment attribute
14251431
ERROR(alignment_not_power_of_two,none,
14261432
"alignment value must be a power of two", ())

lib/Sema/TypeCheckDecl.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,68 @@ static void checkInheritanceClause(
484484
}
485485
}
486486

487+
// Check for static properties that produce empty option sets
488+
// using a rawValue initializer with a value of '0'
489+
static void checkForEmptyOptionSet(const VarDecl *VD, TypeChecker &tc) {
490+
// Check if property is a 'static let'
491+
if (!VD->isStatic() || !VD->isLet())
492+
return;
493+
494+
auto DC = VD->getDeclContext();
495+
496+
// Make sure property is of same type as the type it is declared in
497+
if (!VD->getType()->isEqual(DC->getSelfTypeInContext()))
498+
return;
499+
500+
// Make sure this type conforms to OptionSet
501+
auto *optionSetProto = tc.Context.getProtocol(KnownProtocolKind::OptionSet);
502+
bool conformsToOptionSet = (bool)tc.containsProtocol(
503+
DC->getSelfTypeInContext(),
504+
optionSetProto,
505+
DC,
506+
/*Flags*/None);
507+
508+
if (!conformsToOptionSet)
509+
return;
510+
511+
auto PBD = VD->getParentPatternBinding();
512+
if (!PBD)
513+
return;
514+
515+
auto initIndex = PBD->getPatternEntryIndexForVarDecl(VD);
516+
auto init = PBD->getInit(initIndex);
517+
518+
// Make sure property is being set with a constructor
519+
auto ctor = dyn_cast_or_null<CallExpr>(init);
520+
if (!ctor)
521+
return;
522+
auto ctorCalledVal = ctor->getCalledValue();
523+
if (!ctorCalledVal)
524+
return;
525+
if (!isa<ConstructorDecl>(ctorCalledVal))
526+
return;
527+
528+
// Make sure it is calling the rawValue constructor
529+
if (ctor->getNumArguments() != 1)
530+
return;
531+
if (ctor->getArgumentLabels().front() != tc.Context.Id_rawValue)
532+
return;
533+
534+
// Make sure the rawValue parameter is a '0' integer literal
535+
auto *args = cast<TupleExpr>(ctor->getArg());
536+
auto intArg = dyn_cast<IntegerLiteralExpr>(args->getElement(0));
537+
if (!intArg)
538+
return;
539+
if (intArg->getValue() != 0)
540+
return;
541+
542+
auto loc = VD->getLoc();
543+
tc.diagnose(loc, diag::option_set_zero_constant, VD->getName());
544+
tc.diagnose(loc, diag::option_set_empty_set_init)
545+
.fixItReplace(args->getSourceRange(), "([])");
546+
}
547+
548+
487549
/// Check the inheritance clauses generic parameters along with any
488550
/// requirements stored within the generic parameter list.
489551
static void checkGenericParams(GenericParamList *genericParams,
@@ -2387,6 +2449,8 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
23872449
VD->diagnose(diag::dynamic_self_in_mutable_property);
23882450
}
23892451
}
2452+
2453+
checkForEmptyOptionSet(VD, TC);
23902454

23912455
// Under the Swift 3 inference rules, if we have @IBInspectable or
23922456
// @GKInspectable but did not infer @objc, warn that the attribute is

test/Sema/option-set-empty.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
struct SomeOptions: OptionSet {
4+
var rawValue: Int
5+
6+
static let some = MyOptions(rawValue: 4)
7+
static let empty = SomeOptions(rawValue: 0) // expected-warning {{static property 'empty' produces an empty option set}} expected-note {{use [] to silence this warning}}{{35-48=([])}}
8+
static var otherVal = SomeOptions(rawValue: 0)
9+
10+
let someVal = MyOptions(rawValue: 6)
11+
let option = MyOptions(float: Float.infinity)
12+
let none = SomeOptions(rawValue: 0) // expected-error {{value type 'SomeOptions' cannot have a stored property that recursively contains it}}
13+
}
14+
15+
struct MyOptions: OptionSet {
16+
let rawValue: Int
17+
18+
init(rawValue: Int) {
19+
self.rawValue = rawValue
20+
}
21+
22+
init(float: Float) {
23+
self.rawValue = float.exponent
24+
}
25+
26+
static let none = MyOptions(rawValue: 0) // expected-warning {{static property 'none' produces an empty option set}} expected-note {{use [] to silence this warning}}{{32-45=([])}}
27+
static var nothing = MyOptions(rawValue: 0)
28+
static let nope = MyOptions()
29+
static let other = SomeOptions(rawValue: 8)
30+
static let piVal = MyOptions(float: Float.pi)
31+
static let zero = MyOptions(float: 0.0)
32+
}

0 commit comments

Comments
 (0)