Skip to content

Commit

Permalink
Implement variance for Objective-C type parameters.
Browse files Browse the repository at this point in the history
Introduce co- and contra-variance for Objective-C type parameters,
which allows us to express that (for example) an NSArray is covariant
in its type parameter. This means that NSArray<NSMutableString *> * is
a subtype of NSArray<NSString *> *, which is expected of the immutable
Foundation collections.

Type parameters can be annotated with __covariant or __contravariant
to make them co- or contra-variant, respectively. This feature can be
detected by __has_feature(objc_generics_variance). Implements
rdar://problem/20217490.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@241549 91177308-0d34-0410-b5e6-96231b3b80d8
  • Loading branch information
DougGregor committed Jul 7, 2015
1 parent ffad82b commit a34fa68
Show file tree
Hide file tree
Showing 19 changed files with 366 additions and 31 deletions.
46 changes: 41 additions & 5 deletions include/clang/AST/DeclObjC.h
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,18 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext {
friend class ASTDeclWriter;
};

/// Describes the variance of a given generic parameter.
enum class ObjCTypeParamVariance : uint8_t {
/// The parameter is invariant: must match exactly.
Invariant,
/// The parameter is covariant, e.g., X<T> is a subtype of X<U> when
/// the type parameter is covariant and T is a subtype of U.
Covariant,
/// The parameter is contravariant, e.g., X<T> is a subtype of X<U>
/// when the type parameter is covariant and U is a subtype of T.
Contravariant,
};

/// Represents the declaration of an Objective-C type parameter.
///
/// \code
Expand All @@ -521,21 +533,32 @@ class ObjCTypeParamDecl : public TypedefNameDecl {
void anchor() override;

/// Index of this type parameter in the type parameter list.
unsigned Index : 16;
unsigned Index : 14;

/// The variance of the type parameter.
unsigned Variance : 2;

// The location of the ':', which will be valid when the bound was
// explicitly specified.
/// The location of the variance, if any.
SourceLocation VarianceLoc;

/// The location of the ':', which will be valid when the bound was
/// explicitly specified.
SourceLocation ColonLoc;

ObjCTypeParamDecl(ASTContext &ctx, DeclContext *dc, unsigned index,
ObjCTypeParamDecl(ASTContext &ctx, DeclContext *dc,
ObjCTypeParamVariance variance, SourceLocation varianceLoc,
unsigned index,
SourceLocation nameLoc, IdentifierInfo *name,
SourceLocation colonLoc, TypeSourceInfo *boundInfo)
: TypedefNameDecl(ObjCTypeParam, ctx, dc, nameLoc, nameLoc, name,
boundInfo),
Index(index), ColonLoc(colonLoc) { }
Index(index), Variance(static_cast<unsigned>(variance)),
VarianceLoc(varianceLoc), ColonLoc(colonLoc) { }

public:
static ObjCTypeParamDecl *Create(ASTContext &ctx, DeclContext *dc,
ObjCTypeParamVariance variance,
SourceLocation varianceLoc,
unsigned index,
SourceLocation nameLoc,
IdentifierInfo *name,
Expand All @@ -545,6 +568,19 @@ class ObjCTypeParamDecl : public TypedefNameDecl {

SourceRange getSourceRange() const override LLVM_READONLY;

/// Determine the variance of this type parameter.
ObjCTypeParamVariance getVariance() const {
return static_cast<ObjCTypeParamVariance>(Variance);
}

/// Set the variance of this type parameter.
void setVariance(ObjCTypeParamVariance variance) {
Variance = static_cast<unsigned>(variance);
}

/// Retrieve the location of the variance keyword.
SourceLocation getVarianceLoc() const { return VarianceLoc; }

/// Retrieve the index into its type parameter list.
unsigned getIndex() const { return Index; }

Expand Down
4 changes: 4 additions & 0 deletions include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -7782,6 +7782,10 @@ def err_objc_type_param_bound_conflict : Error<
"type bound %0 for type parameter %1 conflicts with "
"%select{implicit|previous}2 bound %3%select{for type parameter %5|}4">;

def err_objc_type_param_variance_conflict : Error<
"%select{in|co|contra}0variant type parameter %1 conflicts with previous "
"%select{in|co|contra}2variant type parameter %3">;

def note_objc_type_param_here : Note<"type parameter %0 declared here">;

def err_objc_type_param_bound_missing : Error<
Expand Down
4 changes: 3 additions & 1 deletion include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,9 @@ KEYWORD(__bridge_retained , KEYARC)
KEYWORD(__bridge_retain , KEYARC)

// Objective-C keywords.
KEYWORD(__kindof , KEYOBJC2)
KEYWORD(__covariant , KEYOBJC2)
KEYWORD(__contravariant , KEYOBJC2)
KEYWORD(__kindof , KEYOBJC2)

// Alternate spelling for various tokens. There are GCC extensions in all
// languages, but should not be disabled in strict conformance mode.
Expand Down
5 changes: 4 additions & 1 deletion include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -7089,7 +7089,10 @@ class Sema {
};
ObjCContainerKind getObjCContainerKind() const;

DeclResult actOnObjCTypeParam(Scope *S, unsigned index,
DeclResult actOnObjCTypeParam(Scope *S,
ObjCTypeParamVariance variance,
SourceLocation varianceLoc,
unsigned index,
IdentifierInfo *paramName,
SourceLocation paramLoc,
SourceLocation colonLoc,
Expand Down
55 changes: 50 additions & 5 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6943,20 +6943,62 @@ void getIntersectionOfProtocols(ASTContext &Context,
compareObjCProtocolsByName);
}

/// Determine whether the first type is a subtype of the second.
static bool canAssignObjCObjectTypes(ASTContext &ctx, QualType lhs,
QualType rhs) {
// Common case: two object pointers.
const ObjCObjectPointerType *lhsOPT = lhs->getAs<ObjCObjectPointerType>();
const ObjCObjectPointerType *rhsOPT = rhs->getAs<ObjCObjectPointerType>();
if (lhsOPT && rhsOPT)
return ctx.canAssignObjCInterfaces(lhsOPT, rhsOPT);

// Two block pointers.
const BlockPointerType *lhsBlock = lhs->getAs<BlockPointerType>();
const BlockPointerType *rhsBlock = rhs->getAs<BlockPointerType>();
if (lhsBlock && rhsBlock)
return ctx.typesAreBlockPointerCompatible(lhs, rhs);

// If either is an unqualified 'id' and the other is a block, it's
// acceptable.
if ((lhsOPT && lhsOPT->isObjCIdType() && rhsBlock) ||
(rhsOPT && rhsOPT->isObjCIdType() && lhsBlock))
return true;

return false;
}

// Check that the given Objective-C type argument lists are equivalent.
static bool sameObjCTypeArgs(const ASTContext &ctx, ArrayRef<QualType> lhsArgs,
static bool sameObjCTypeArgs(ASTContext &ctx,
const ObjCInterfaceDecl *iface,
ArrayRef<QualType> lhsArgs,
ArrayRef<QualType> rhsArgs,
bool stripKindOf) {
if (lhsArgs.size() != rhsArgs.size())
return false;

ObjCTypeParamList *typeParams = iface->getTypeParamList();
for (unsigned i = 0, n = lhsArgs.size(); i != n; ++i) {
if (!ctx.hasSameType(lhsArgs[i], rhsArgs[i])) {
if (ctx.hasSameType(lhsArgs[i], rhsArgs[i]))
continue;

switch (typeParams->begin()[i]->getVariance()) {
case ObjCTypeParamVariance::Invariant:
if (!stripKindOf ||
!ctx.hasSameType(lhsArgs[i].stripObjCKindOfType(ctx),
rhsArgs[i].stripObjCKindOfType(ctx))) {
return false;
}
break;

case ObjCTypeParamVariance::Covariant:
if (!canAssignObjCObjectTypes(ctx, lhsArgs[i], rhsArgs[i]))
return false;
break;

case ObjCTypeParamVariance::Contravariant:
if (!canAssignObjCObjectTypes(ctx, rhsArgs[i], lhsArgs[i]))
return false;
break;
}
}

Expand Down Expand Up @@ -6989,7 +7031,8 @@ QualType ASTContext::areCommonBaseCompatible(
bool anyChanges = false;
if (LHS->isSpecialized() && RHS->isSpecialized()) {
// Both have type arguments, compare them.
if (!sameObjCTypeArgs(*this, LHS->getTypeArgs(), RHS->getTypeArgs(),
if (!sameObjCTypeArgs(*this, LHS->getInterface(),
LHS->getTypeArgs(), RHS->getTypeArgs(),
/*stripKindOf=*/true))
return QualType();
} else if (LHS->isSpecialized() != RHS->isSpecialized()) {
Expand Down Expand Up @@ -7037,7 +7080,8 @@ QualType ASTContext::areCommonBaseCompatible(
bool anyChanges = false;
if (LHS->isSpecialized() && RHS->isSpecialized()) {
// Both have type arguments, compare them.
if (!sameObjCTypeArgs(*this, LHS->getTypeArgs(), RHS->getTypeArgs(),
if (!sameObjCTypeArgs(*this, LHS->getInterface(),
LHS->getTypeArgs(), RHS->getTypeArgs(),
/*stripKindOf=*/true))
return QualType();
} else if (LHS->isSpecialized() != RHS->isSpecialized()) {
Expand Down Expand Up @@ -7127,7 +7171,8 @@ bool ASTContext::canAssignObjCInterfaces(const ObjCObjectType *LHS,

// If the RHS is specializd, compare type arguments.
if (RHSSuper->isSpecialized() &&
!sameObjCTypeArgs(*this, LHS->getTypeArgs(), RHSSuper->getTypeArgs(),
!sameObjCTypeArgs(*this, LHS->getInterface(),
LHS->getTypeArgs(), RHSSuper->getTypeArgs(),
/*stripKindOf=*/true)) {
return false;
}
Expand Down
13 changes: 13 additions & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1475,6 +1475,19 @@ void ASTDumper::VisitObjCMethodDecl(const ObjCMethodDecl *D) {

void ASTDumper::VisitObjCTypeParamDecl(const ObjCTypeParamDecl *D) {
dumpName(D);
switch (D->getVariance()) {
case ObjCTypeParamVariance::Invariant:
break;

case ObjCTypeParamVariance::Covariant:
OS << " covariant";
break;

case ObjCTypeParamVariance::Contravariant:
OS << " contravariant";
break;
}

if (D->hasExplicitBound())
OS << " bounded";
dumpType(D->getUnderlyingType());
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/ASTImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3452,6 +3452,8 @@ Decl *ASTNodeImporter::VisitObjCTypeParamDecl(ObjCTypeParamDecl *D) {

ObjCTypeParamDecl *Result = ObjCTypeParamDecl::Create(
Importer.getToContext(), DC,
D->getVariance(),
Importer.Import(D->getVarianceLoc()),
D->getIndex(),
Importer.Import(D->getLocation()),
Name.getAsIdentifierInfo(),
Expand Down
18 changes: 13 additions & 5 deletions lib/AST/DeclObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1198,28 +1198,36 @@ ObjCMethodDecl::findPropertyDecl(bool CheckOverrides) const {
void ObjCTypeParamDecl::anchor() { }

ObjCTypeParamDecl *ObjCTypeParamDecl::Create(ASTContext &ctx, DeclContext *dc,
ObjCTypeParamVariance variance,
SourceLocation varianceLoc,
unsigned index,
SourceLocation nameLoc,
IdentifierInfo *name,
SourceLocation colonLoc,
TypeSourceInfo *boundInfo) {
return new (ctx, dc) ObjCTypeParamDecl(ctx, dc, index, nameLoc, name, colonLoc,
boundInfo);
return new (ctx, dc) ObjCTypeParamDecl(ctx, dc, variance, varianceLoc, index,
nameLoc, name, colonLoc, boundInfo);
}

ObjCTypeParamDecl *ObjCTypeParamDecl::CreateDeserialized(ASTContext &ctx,
unsigned ID) {
return new (ctx, ID) ObjCTypeParamDecl(ctx, nullptr, 0, SourceLocation(),
return new (ctx, ID) ObjCTypeParamDecl(ctx, nullptr,
ObjCTypeParamVariance::Invariant,
SourceLocation(), 0, SourceLocation(),
nullptr, SourceLocation(), nullptr);
}

SourceRange ObjCTypeParamDecl::getSourceRange() const {
SourceLocation startLoc = VarianceLoc;
if (startLoc.isInvalid())
startLoc = getLocation();

if (hasExplicitBound()) {
return SourceRange(getLocation(),
return SourceRange(startLoc,
getTypeSourceInfo()->getTypeLoc().getEndLoc());
}

return SourceRange(getLocation());
return SourceRange(startLoc);
}

//===----------------------------------------------------------------------===//
Expand Down
13 changes: 13 additions & 0 deletions lib/AST/DeclPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,19 @@ void DeclPrinter::PrintObjCTypeParams(ObjCTypeParamList *Params) {
Out << ", ";
}

switch (Param->getVariance()) {
case ObjCTypeParamVariance::Invariant:
break;

case ObjCTypeParamVariance::Covariant:
Out << "__covariant ";
break;

case ObjCTypeParamVariance::Contravariant:
Out << "__contravariant ";
break;
}

Out << Param->getDeclName().getAsString();

if (Param->hasExplicitBound()) {
Expand Down
1 change: 1 addition & 0 deletions lib/Lex/PPMacroExpansion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,7 @@ static bool HasFeature(const Preprocessor &PP, const IdentifierInfo *II) {
.Case("objc_bridge_id", true)
.Case("objc_bridge_id_on_typedefs", true)
.Case("objc_generics", LangOpts.ObjC2)
.Case("objc_generics_variance", LangOpts.ObjC2)
// C11 features
.Case("c_alignas", LangOpts.C11)
.Case("c_alignof", LangOpts.C11)
Expand Down
42 changes: 35 additions & 7 deletions lib/Parse/ParseObjc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,11 +416,15 @@ static void addContextSensitiveTypeNullability(Parser &P,
/// '<' objc-type-parameter (',' objc-type-parameter)* '>'
///
/// objc-type-parameter:
/// identifier objc-type-parameter-bound[opt]
/// objc-type-parameter-variance? identifier objc-type-parameter-bound[opt]
///
/// objc-type-parameter-bound:
/// ':' type-name
///
/// objc-type-parameter-variance:
/// '__covariant'
/// '__contravariant'
///
/// \param lAngleLoc The location of the starting '<'.
///
/// \param protocolIdents Will capture the list of identifiers, if the
Expand All @@ -444,12 +448,15 @@ ObjCTypeParamList *Parser::parseObjCTypeParamListOrProtocolRefs(
auto makeProtocolIdentsIntoTypeParameters = [&]() {
unsigned index = 0;
for (const auto &pair : protocolIdents) {
DeclResult typeParam = Actions.actOnObjCTypeParam(getCurScope(),
index++,
pair.first,
pair.second,
SourceLocation(),
ParsedType());
DeclResult typeParam = Actions.actOnObjCTypeParam(
getCurScope(),
ObjCTypeParamVariance::Invariant,
SourceLocation(),
index++,
pair.first,
pair.second,
SourceLocation(),
ParsedType());
if (typeParam.isUsable())
typeParams.push_back(typeParam.get());
}
Expand All @@ -460,7 +467,26 @@ ObjCTypeParamList *Parser::parseObjCTypeParamListOrProtocolRefs(

bool invalid = false;
lAngleLoc = ConsumeToken();

do {
// Parse the variance, if any.
SourceLocation varianceLoc;
ObjCTypeParamVariance variance = ObjCTypeParamVariance::Invariant;
if (Tok.is(tok::kw___covariant) || Tok.is(tok::kw___contravariant)) {
variance = Tok.is(tok::kw___covariant)
? ObjCTypeParamVariance::Covariant
: ObjCTypeParamVariance::Contravariant;
varianceLoc = ConsumeToken();

// Once we've seen a variance specific , we know this is not a
// list of protocol references.
if (mayBeProtocolList) {
// Up until now, we have been queuing up parameters because they
// might be protocol references. Turn them into parameters now.
makeProtocolIdentsIntoTypeParameters();
}
}

// Parse the identifier.
if (!Tok.is(tok::identifier)) {
// Code completion.
Expand Down Expand Up @@ -508,6 +534,8 @@ ObjCTypeParamList *Parser::parseObjCTypeParamListOrProtocolRefs(

// Create the type parameter.
DeclResult typeParam = Actions.actOnObjCTypeParam(getCurScope(),
variance,
varianceLoc,
typeParams.size(),
paramName,
paramLoc,
Expand Down
Loading

0 comments on commit a34fa68

Please sign in to comment.