Skip to content

Commit

Permalink
Recognize imported enum case aliases in TypeCheckPattern.cpp.
Browse files Browse the repository at this point in the history
...so that they can still be used with exhaustive switches.

This is a hack---groveling through the AST to see if it's in the particular
form of an imported enum case alias---but at least it's limited to imported
properties.

More rdar://problem/18662118

Swift SVN r28326
  • Loading branch information
jrose-apple committed May 8, 2015
1 parent 21b8312 commit 46b5257
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 35 deletions.
42 changes: 40 additions & 2 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1675,8 +1675,12 @@ namespace {

// Did we already import an enum constant for this enum with the
// same value? If so, import it as a standalone constant.
if (!Impl.EnumConstantValues.insert({clangEnum, rawValue}).second)
return importOptionConstant(decl, clangEnum, theEnum);

auto insertResult =
Impl.EnumConstantValues.insert({{clangEnum, rawValue}, nullptr});
if (!insertResult.second)
return importEnumCaseAlias(decl, insertResult.first->second,
clangEnum, theEnum);

if (clangEnum->getIntegerType()->isSignedIntegerOrEnumerationType()
&& rawValue.slt(0)) {
Expand All @@ -1698,6 +1702,7 @@ namespace {
name, TypeLoc(),
SourceLoc(), rawValueExpr,
theEnum);
insertResult.first->second = element;

// Give the enum element the appropriate type.
auto argTy = MetatypeType::get(theEnum->getDeclaredType());
Expand Down Expand Up @@ -1731,6 +1736,39 @@ namespace {
return CD;
}

/// Import \p alias as an alias for the imported constant \p original.
///
/// This builds the getter in a way that's compatible with switch
/// statements. Changing the body here may require changing
/// TypeCheckPattern.cpp as well.
Decl *importEnumCaseAlias(const clang::EnumConstantDecl *alias,
EnumElementDecl *original,
const clang::EnumDecl *clangEnum,
NominalTypeDecl *importedEnum) {
auto name = getEnumConstantName(alias, clangEnum);
if (name.empty())
return nullptr;

// Construct the original constant. Enum constants witbout payloads look
// like simple values, but actually have type 'MyEnum.Type -> MyEnum'.
auto constantRef = new (Impl.SwiftContext) DeclRefExpr(original,
SourceLoc(),
/*implicit*/true);
Type importedEnumTy = importedEnum->getDeclaredTypeInContext();
auto typeRef = TypeExpr::createImplicit(importedEnumTy,
Impl.SwiftContext);
auto instantiate = new (Impl.SwiftContext) DotSyntaxCallExpr(constantRef,
SourceLoc(),
typeRef);
instantiate->setType(importedEnumTy);

Decl *CD = Impl.createConstant(name, importedEnum, importedEnumTy,
instantiate, ConstantConvertKind::None,
/*isStatic*/ true, alias);
Impl.importAttributes(alias, CD);
return CD;
}

NominalTypeDecl *importAsOptionSetType(DeclContext *dc,
Identifier name,
const clang::EnumDecl *decl) {
Expand Down
7 changes: 4 additions & 3 deletions lib/ClangImporter/ImporterImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
llvm::DenseMap<const clang::EnumDecl *, StringRef> EnumConstantNamePrefixes;

private:
class EnumConstantDenseSetInfo {
class EnumConstantDenseMapInfo {
public:
using PairTy = std::pair<const clang::EnumDecl *, llvm::APSInt>;
using PointerInfo = llvm::DenseMapInfo<const clang::EnumDecl *>;
Expand All @@ -496,8 +496,9 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation

public:
/// \brief Keep track of enum constant values that have been imported.
llvm::DenseSet<std::pair<const clang::EnumDecl *, llvm::APSInt>,
EnumConstantDenseSetInfo>
llvm::DenseMap<std::pair<const clang::EnumDecl *, llvm::APSInt>,
EnumElementDecl *,
EnumConstantDenseMapInfo>
EnumConstantValues;

/// \brief Keep track of initializer declarations that correspond to
Expand Down
93 changes: 64 additions & 29 deletions lib/Sema/TypeCheckPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,78 @@
#include <utility>
using namespace swift;

/// Find an unqualified enum element.
/// If the given VarDecl is a computed property whose getter always returns a
/// particular enum element, return that element.
///
/// This requires the getter's body to have a certain syntactic form. It should
/// be kept in sync with importEnumCaseAlias in the ClangImporter library.
static EnumElementDecl *
lookupUnqualifiedEnumMemberElement(TypeChecker &TC, DeclContext *DC,
Identifier name) {
auto lookupOptions = defaultUnqualifiedLookupOptions;
lookupOptions |= NameLookupFlags::KnownPrivate;
auto lookup = TC.lookupUnqualified(DC, name, SourceLoc(), lookupOptions);
if (!lookup)
extractEnumElement(const VarDecl *constant) {
const FuncDecl *getter = constant->getGetter();
if (!getter)
return nullptr;

const BraceStmt *body = getter->getBody();
if (!body || body->getNumElements() != 1)
return nullptr;

// See if there is any enum element in there.
auto *retStmtRaw = body->getElement(0).dyn_cast<Stmt *>();
auto *retStmt = dyn_cast_or_null<ReturnStmt>(retStmtRaw);
if (!retStmt)
return nullptr;

auto *resultExpr = dyn_cast_or_null<ApplyExpr>(retStmt->getResult());
if (!resultExpr)
return nullptr;

auto *ctorExpr = dyn_cast<DeclRefExpr>(resultExpr->getFn());
if (!ctorExpr)
return nullptr;

return dyn_cast<EnumElementDecl>(ctorExpr->getDecl());
}

/// Find the first enum element in \p foundElements.
///
/// If there are no enum elements but there are properties, attepmts to map
/// an arbitrary property to an enum element using extractEnumElement.
static EnumElementDecl *
filterForEnumElement(LookupResult foundElements) {
EnumElementDecl *foundElement = nullptr;
for (auto result : lookup) {
auto *oe = dyn_cast<EnumElementDecl>(result.Decl);
if (!oe)
VarDecl *foundConstant = nullptr;

for (ValueDecl *e : foundElements) {
assert(e);

if (auto *oe = dyn_cast<EnumElementDecl>(e)) {
// Ambiguities should be ruled out by parsing.
assert(!foundElement && "ambiguity in enum case name lookup?!");
foundElement = oe;
continue;
// Ambiguities should be ruled out by parsing.
assert(!foundElement && "ambiguity in enum case name lookup?!");
foundElement = oe;
}

if (auto *var = dyn_cast<VarDecl>(e)) {
foundConstant = var;
continue;
}
}

if (!foundElement && foundConstant && foundConstant->hasClangNode())
foundElement = extractEnumElement(foundConstant);

return foundElement;
}

/// Find an unqualified enum element.
static EnumElementDecl *
lookupUnqualifiedEnumMemberElement(TypeChecker &TC, DeclContext *DC,
Identifier name) {
auto lookupOptions = defaultUnqualifiedLookupOptions;
lookupOptions |= NameLookupFlags::KnownPrivate;
auto lookup = TC.lookupUnqualified(DC, name, SourceLoc(), lookupOptions);
return filterForEnumElement(lookup);
}

/// Find an enum element in an enum type.
static EnumElementDecl *
lookupEnumMemberElement(TypeChecker &TC, DeclContext *DC, Type ty,
Expand All @@ -57,21 +106,7 @@ lookupEnumMemberElement(TypeChecker &TC, DeclContext *DC, Type ty,
NameLookupOptions lookupOptions
= defaultMemberLookupOptions - NameLookupFlags::DynamicLookup;
LookupResult foundElements = TC.lookupMember(DC, ty, name, lookupOptions);
if (!foundElements)
return nullptr;

// See if there is any enum element in there.
EnumElementDecl *foundElement = nullptr;
for (ValueDecl *e : foundElements) {
auto *oe = dyn_cast<EnumElementDecl>(e);
if (!oe)
continue;
// Ambiguities should be ruled out by parsing.
assert(!foundElement && "ambiguity in enum case name lookup?!");
foundElement = oe;
}

return foundElement;
return filterForEnumElement(foundElements);
}

namespace {
Expand Down
18 changes: 18 additions & 0 deletions test/ClangModules/enum-dataflow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-sil %s -verify

// REQUIRES: objc_interop

import Foundation
import user_objc

let aliasOriginal = NSAliasesEnum.ByName

switch aliasOriginal {
case .Original:
break
} // expected-error {{switch must be exhaustive, consider adding a default clause}}

switch aliasOriginal {
case .BySameValue:
break
} // expected-error {{switch must be exhaustive, consider adding a default clause}}
47 changes: 46 additions & 1 deletion test/ClangModules/enum.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -parse %s -verify
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-sil %s -verify
// -- Check that we can successfully round-trip.
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -D IRGEN -emit-ir %s >/dev/null

Expand Down Expand Up @@ -62,6 +62,51 @@ var alias2 = NSAliasesEnum.ByEquivalentValue
var alias3 = NSAliasesEnum.ByName
var aliasOriginal = NSAliasesEnum.Original

switch aliasOriginal {
case .Original:
break
case .DifferentValue:
break
}
switch aliasOriginal {
case .Original:
break
default:
break
}

switch aliasOriginal {
case .BySameValue:
break
case .DifferentValue:
break
}
switch aliasOriginal {
case .BySameValue:
break
default:
break
}

switch aliasOriginal {
case NSAliasesEnum.BySameValue:
break
case NSAliasesEnum.DifferentValue:
break
}

extension NSAliasesEnum {
func test() {
switch aliasOriginal {
case BySameValue:
break
case DifferentValue:
break
}
}
}


#if !IRGEN
var qualifiedName = NSRuncingMode.Mince
var topLevelCaseName = NSRuncingMince // expected-error{{}}
Expand Down
1 change: 1 addition & 0 deletions test/Inputs/clang-importer-sdk/usr/include/Foundation.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ typedef NS_ENUM(unsigned char, NSAliasesEnum) {
NSAliasesBySameValue = 129,
NSAliasesByEquivalentValue = -127,
NSAliasesByName = NSAliasesOriginal,
NSAliasesDifferentValue = 2
};

NS_ENUM(NSInteger, NSMalformedEnumMissingTypedef) {
Expand Down

0 comments on commit 46b5257

Please sign in to comment.