Skip to content

Commit 095a68b

Browse files
committed
[Sema] Disallow accessing enum elements as instance members
This implements SE-0036.
1 parent 232fe59 commit 095a68b

File tree

10 files changed

+205
-6
lines changed

10 files changed

+205
-6
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,9 @@ Swift 3.0
246246
`NS[Mutable]Dictionary` are still imported as nongeneric classes for
247247
the time being.
248248

249+
* [SE-0036](https://github.com/apple/swift-evolution/blob/master/proposals/0036-enum-dot.md):
250+
Enum elements can no longer be accessed as instance members in instance methods.
251+
249252
* As part of the changes for SE-0055 (see below), the *pointee* types of
250253
imported pointers (e.g. the `id` in `id *`) are no longer assumed to always
251254
be `_Nullable` even if annotated otherwise. However, an implicit or explicit

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ ERROR(could_not_use_type_member,none,
9393
ERROR(could_not_use_type_member_on_instance,none,
9494
"static member %1 cannot be used on instance of type %0",
9595
(Type, DeclName))
96+
ERROR(could_not_use_enum_element_on_instance,none,
97+
"enum element %0 cannot be referenced as an instance member",
98+
(DeclName))
9699
ERROR(could_not_use_type_member_on_existential,none,
97100
"static member %1 cannot be used on protocol metatype %0",
98101
(Type, DeclName))

lib/AST/NameLookup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
563563
if (FD->isStatic() && !isMetatypeType)
564564
continue;
565565
} else if (isa<EnumElementDecl>(Result)) {
566-
Results.push_back(UnqualifiedLookupResult(MetaBaseDecl, Result));
566+
Results.push_back(UnqualifiedLookupResult(BaseDecl, Result));
567567
continue;
568568
}
569569

lib/Sema/CSDiag.cpp

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1859,6 +1859,11 @@ class FailureDiagnosis :public ASTVisitor<FailureDiagnosis, /*exprresult*/bool>{
18591859
/// the exact expression kind).
18601860
bool diagnoseGeneralMemberFailure(Constraint *constraint);
18611861

1862+
/// Diagnose the lookup of an enum element as instance member where only a
1863+
/// static member is allowed
1864+
void diagnoseEnumInstanceMemberLookup(EnumElementDecl *enumElementDecl,
1865+
SourceLoc loc);
1866+
18621867
/// Given a result of name lookup that had no viable results, diagnose the
18631868
/// unviable ones.
18641869
void diagnoseUnviableLookupResults(MemberLookupResult &lookupResults,
@@ -2228,6 +2233,70 @@ bool FailureDiagnosis::diagnoseGeneralMemberFailure(Constraint *constraint) {
22282233
return false;
22292234
}
22302235

2236+
void FailureDiagnosis::
2237+
diagnoseEnumInstanceMemberLookup(EnumElementDecl *enumElementDecl,
2238+
SourceLoc loc) {
2239+
auto diag = diagnose(loc, diag::could_not_use_enum_element_on_instance,
2240+
enumElementDecl->getName());
2241+
auto parentEnum = enumElementDecl->getParentEnum();
2242+
auto enumMetatype = parentEnum->getType()->castTo<AnyMetatypeType>();
2243+
2244+
// Determine the contextual type of the expression
2245+
Type contextualType;
2246+
for (auto iterateCS = CS;
2247+
contextualType.isNull() && iterateCS;
2248+
iterateCS = iterateCS->baseCS) {
2249+
contextualType = iterateCS->getContextualType();
2250+
}
2251+
2252+
// Try to provide a fix-it that only contains a '.'
2253+
if (contextualType) {
2254+
if (enumMetatype->getInstanceType()->isEqual(contextualType)) {
2255+
diag.fixItInsert(loc, ".");
2256+
return;
2257+
}
2258+
}
2259+
2260+
// Check if the expression is the matching operator ~=, most often used in
2261+
// case statements. If so, try to provide a single dot fix-it
2262+
const Expr *contextualTypeNode;
2263+
for (auto iterateCS = CS; iterateCS; iterateCS = iterateCS->baseCS) {
2264+
contextualTypeNode = iterateCS->getContextualTypeNode();
2265+
}
2266+
2267+
// The '~=' operator is an overloaded decl ref inside a binaryExpr
2268+
if (auto binaryExpr = dyn_cast<BinaryExpr>(contextualTypeNode)) {
2269+
if (auto overloadedFn
2270+
= dyn_cast<OverloadedDeclRefExpr>(binaryExpr->getFn())) {
2271+
if (overloadedFn->getDecls().size() > 0) {
2272+
// Fetch any declaration to check if the name is '~='
2273+
ValueDecl *decl0 = overloadedFn->getDecls()[0];
2274+
2275+
if (decl0->getName() == decl0->getASTContext().Id_MatchOperator) {
2276+
assert(binaryExpr->getArg()->getElements().size() == 2);
2277+
2278+
// If the rhs of '~=' is the enum type, a single dot suffices
2279+
// since the type can be inferred
2280+
Type secondArgType = binaryExpr->getArg()->getElement(1)->getType();
2281+
if (secondArgType->isEqual(enumMetatype->getInstanceType())) {
2282+
diag.fixItInsert(loc, ".");
2283+
return;
2284+
}
2285+
}
2286+
}
2287+
}
2288+
}
2289+
2290+
// Fall back to a fix-it with a full type qualifier
2291+
SmallString<32> enumTypeName;
2292+
llvm::raw_svector_ostream typeNameStream(enumTypeName);
2293+
typeNameStream << parentEnum->getName();
2294+
typeNameStream << ".";
2295+
2296+
diag.fixItInsert(loc, typeNameStream.str());
2297+
return;
2298+
}
2299+
22312300

22322301
/// Given a result of name lookup that had no viable results, diagnose the
22332302
/// unviable ones.
@@ -2365,6 +2434,21 @@ diagnoseUnviableLookupResults(MemberLookupResult &result, Type baseObjTy,
23652434
} else {
23662435
// Otherwise the static member lookup was invalid because it was
23672436
// called on an instance
2437+
2438+
// Handle enum element lookup on instance type
2439+
auto lookThroughBaseObjTy = baseObjTy->lookThroughAllAnyOptionalTypes();
2440+
if (lookThroughBaseObjTy->is<EnumType>()
2441+
|| lookThroughBaseObjTy->is<BoundGenericEnumType>()) {
2442+
for (auto cand : result.UnviableCandidates) {
2443+
ValueDecl *decl = cand.first;
2444+
if (auto enumElementDecl = dyn_cast<EnumElementDecl>(decl)) {
2445+
diagnoseEnumInstanceMemberLookup(enumElementDecl, loc);
2446+
return;
2447+
}
2448+
}
2449+
}
2450+
2451+
// Provide diagnostic other static member lookups on instance type
23682452
diagnose(loc, diag::could_not_use_type_member_on_instance,
23692453
baseObjTy, memberName)
23702454
.highlight(baseRange).highlight(nameLoc.getSourceRange());
@@ -3035,7 +3119,7 @@ typeCheckChildIndependently(Expr *subExpr, Type convertType,
30353119
bool hadError = CS->TC.typeCheckExpression(subExpr, CS->DC,
30363120
TypeLoc::withoutLoc(convertType),
30373121
convertTypePurpose, TCEOptions,
3038-
listener);
3122+
listener, CS);
30393123

30403124
// This is a terrible hack to get around the fact that typeCheckExpression()
30413125
// might change subExpr to point to a new OpenExistentialExpr. In that case,

lib/Sema/ConstraintSystem.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,9 @@ class ConstraintSystem {
899899
/// Note: this is only used to support ObjCSelectorExpr at the moment.
900900
llvm::SmallPtrSet<Expr *, 2> UnevaluatedRootExprs;
901901

902+
/// The original CS if this CS was created as a simplification of another CS
903+
ConstraintSystem *baseCS = nullptr;
904+
902905
private:
903906

904907
/// \brief Allocator used for all of the related constraint systems.
@@ -1248,6 +1251,10 @@ class ConstraintSystem {
12481251
return contextualType;
12491252
}
12501253

1254+
const Expr *getContextualTypeNode() const {
1255+
return contextualTypeNode;
1256+
}
1257+
12511258
ContextualTypePurpose getContextualTypePurpose() const {
12521259
return contextualTypePurpose;
12531260
}

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1416,14 +1416,16 @@ bool TypeChecker::typeCheckExpression(Expr *&expr, DeclContext *dc,
14161416
TypeLoc convertType,
14171417
ContextualTypePurpose convertTypePurpose,
14181418
TypeCheckExprOptions options,
1419-
ExprTypeCheckListener *listener) {
1419+
ExprTypeCheckListener *listener,
1420+
ConstraintSystem *baseCS) {
14201421
PrettyStackTraceExpr stackTrace(Context, "type-checking", expr);
14211422

14221423
// Construct a constraint system from this expression.
14231424
ConstraintSystemOptions csOptions = ConstraintSystemFlags::AllowFixes;
14241425
if (options.contains(TypeCheckExprFlags::PreferForceUnwrapToOptional))
14251426
csOptions |= ConstraintSystemFlags::PreferForceUnwrapToOptional;
14261427
ConstraintSystem cs(*this, dc, csOptions);
1428+
cs.baseCS = baseCS;
14271429
CleanupIllFormedExpressionRAII cleanup(Context, expr);
14281430
ExprCleanser cleanup2(expr);
14291431

lib/Sema/TypeCheckPattern.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,16 @@ filterForEnumElement(TypeChecker &TC, DeclContext *DC, SourceLoc UseLoc,
7878
EnumElementDecl *foundElement = nullptr;
7979
VarDecl *foundConstant = nullptr;
8080

81-
for (ValueDecl *e : foundElements) {
81+
for (LookupResult::Result result : foundElements) {
82+
ValueDecl *e = result.Decl;
8283
assert(e);
8384
if (e->isInvalid()) {
8485
continue;
8586
}
87+
// Skip if the enum element was referenced as an instance member
88+
if (!result.Base || !result.Base->getType()->is<MetatypeType>()) {
89+
continue;
90+
}
8691

8792
if (auto *oe = dyn_cast<EnumElementDecl>(e)) {
8893
// Ambiguities should be ruled out by parsing.

lib/Sema/TypeChecker.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,12 +1197,17 @@ class TypeChecker final : public LazyResolver {
11971197
/// events in the type checking of this expression, and which can introduce
11981198
/// additional constraints.
11991199
///
1200+
/// \param baseCS If this type checking process is the simplification of
1201+
/// another constraint system, set the original constraint system. \c null
1202+
/// otherwise
1203+
///
12001204
/// \returns true if an error occurred, false otherwise.
12011205
bool typeCheckExpression(Expr *&expr, DeclContext *dc,
12021206
TypeLoc convertType = TypeLoc(),
12031207
ContextualTypePurpose convertTypePurpose =CTP_Unused,
12041208
TypeCheckExprOptions options =TypeCheckExprOptions(),
1205-
ExprTypeCheckListener *listener = nullptr);
1209+
ExprTypeCheckListener *listener = nullptr,
1210+
constraints::ConstraintSystem *baseCS = nullptr);
12061211

12071212
bool typeCheckExpression(Expr *&expr, DeclContext *dc,
12081213
ExprTypeCheckListener *listener) {

test/Parse/enum.swift

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,3 +433,93 @@ enum RawValueBTest: Double, RawValueB {
433433
enum foo : String {
434434
case bar = nil // expected-error {{cannot convert nil to raw type 'String'}}
435435
}
436+
437+
// Static member lookup from instance methods
438+
439+
struct EmptyStruct {}
440+
441+
enum EnumWithStaticMember {
442+
static let staticVar = EmptyStruct()
443+
444+
func foo() {
445+
let _ = staticVar // expected-error {{static member 'staticVar' cannot be used on instance of type 'EnumWithStaticMember'}}
446+
}
447+
}
448+
449+
// SE-0036:
450+
451+
struct SE0036_Auxiliary {}
452+
453+
enum SE0036 {
454+
case A
455+
case B(SE0036_Auxiliary)
456+
457+
static func staticReference() {
458+
_ = A
459+
_ = SE0036.A
460+
}
461+
462+
func staticReferencInInstanceMethod() {
463+
_ = A // expected-error {{enum element 'A' cannot be referenced as an instance member}} {{9-9=SE0036.}}
464+
_ = SE0036.A
465+
}
466+
467+
static func staticReferencInSwitchInStaticMethod() {
468+
switch SE0036.A {
469+
case A: break
470+
case B(_): break
471+
}
472+
}
473+
474+
func staticReferencInSwitchInInstanceMethod() {
475+
switch self {
476+
case A: break // expected-error {{enum element 'A' cannot be referenced as an instance member}} {{10-10=.}}
477+
case B(_): break // expected-error {{enum element 'B' cannot be referenced as an instance member}} {{10-10=.}}
478+
}
479+
}
480+
481+
func explicitReferencInSwitch() {
482+
switch SE0036.A {
483+
case SE0036.A: break
484+
case SE0036.B(_): break
485+
}
486+
}
487+
488+
func dotReferencInSwitchInInstanceMethod() {
489+
switch self {
490+
case .A: break
491+
case .B(_): break
492+
}
493+
}
494+
495+
static func dotReferencInSwitchInStaticMethod() {
496+
switch SE0036.A {
497+
case .A: break
498+
case .B(_): break
499+
}
500+
}
501+
502+
init() {
503+
self = .A
504+
self = A // expected-error {{enum element 'A' cannot be referenced as an instance member}} {{12-12=.}}
505+
self = SE0036.A
506+
self = .B(SE0036_Auxiliary())
507+
self = B(SE0036_Auxiliary()) // expected-error {{enum element 'B' cannot be referenced as an instance member}} {{12-12=.}}
508+
self = SE0036.B(SE0036_Auxiliary())
509+
}
510+
}
511+
512+
enum SE0036_Generic<T> {
513+
case A(x: T)
514+
515+
func foo() {
516+
switch self {
517+
case A(_): break // expected-error {{enum element 'A' cannot be referenced as an instance member}} {{10-10=SE0036_Generic.}}
518+
}
519+
520+
switch self {
521+
case SE0036_Generic.A(let a): print(a)
522+
}
523+
}
524+
}
525+

validation-test/compiler_crashers/28363-swift-expr-walk.swift renamed to validation-test/compiler_crashers_fixed/28363-swift-expr-walk.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// See http://swift.org/LICENSE.txt for license information
66
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
77

8-
// RUN: not --crash %target-swift-frontend %s -parse
8+
// RUN: not %target-swift-frontend %s -parse
99
// REQUIRES: asserts
1010
class A
1111
enum A<A{

0 commit comments

Comments
 (0)