Skip to content

Add implicit conversions and casts from T:Hashable <-> AnyHashable. #4047

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
26 changes: 25 additions & 1 deletion include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -3010,12 +3010,36 @@ class ErasureExpr : public ImplicitConversionExpr {
ArrayRef<ProtocolConformanceRef> getConformances() const {
return Conformances;
}

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::Erasure;
}
};

/// AnyHashableErasureExpr - Perform type erasure by converting a value
/// to AnyHashable type.
///
/// The type of the sub-expression should always be a type that implements
/// the Hashable protocol.
class AnyHashableErasureExpr : public ImplicitConversionExpr {
ProtocolConformanceRef Conformance;

public:
AnyHashableErasureExpr(Expr *subExpr, Type type,
ProtocolConformanceRef conformance)
: ImplicitConversionExpr(ExprKind::AnyHashableErasure, subExpr, type),
Conformance(conformance) {}

/// \brief Retrieve the mapping specifying how the type of the
/// subexpression conforms to the Hashable protocol.
ProtocolConformanceRef getConformance() const {
return Conformance;
}

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::AnyHashableErasure;
}
};
/// UnresolvedSpecializeExpr - Represents an explicit specialization using
/// a type parameter list (e.g. "Vector<Int>") that has not been resolved.
class UnresolvedSpecializeExpr : public Expr {
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/ExprNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ ABSTRACT_EXPR(ImplicitConversion, Expr)
EXPR(MetatypeConversion, ImplicitConversionExpr)
EXPR(CollectionUpcastConversion, ImplicitConversionExpr)
EXPR(Erasure, ImplicitConversionExpr)
EXPR(AnyHashableErasure, ImplicitConversionExpr)
EXPR(DerivedToBase, ImplicitConversionExpr)
EXPR(ArchetypeToSuper, ImplicitConversionExpr)
EXPR(InjectIntoOptional, ImplicitConversionExpr)
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/KnownDecls.def
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ FUNC_DECL(ConditionallyBridgeFromObjectiveCBridgeable,
FUNC_DECL(BridgeAnythingToObjectiveC,
"_bridgeAnythingToObjectiveC")

FUNC_DECL(ConvertToAnyHashable, "_convertToAnyHashable")

FUNC_DECL(DidEnterMain, "_stdlib_didEnterMain")
FUNC_DECL(DiagnoseUnexpectedNilOptional, "_diagnoseUnexpectedNilOptional")

Expand Down
4 changes: 4 additions & 0 deletions include/swift/Runtime/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -1980,6 +1980,10 @@ struct TargetValueMetadata : public TargetMetadata<Runtime> {
return (asWords + Description->GenericParams.Offset);
}

const TargetNominalTypeDescriptor<Runtime> *getDescription() const {
return Description.get();
}

StoredPointer offsetToDescriptorOffset() const {
return offsetof(TargetValueMetadata<Runtime>, Description);
}
Expand Down
7 changes: 7 additions & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1916,6 +1916,13 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
printRec(E->getSubExpr());
OS << ')';
}
void visitAnyHashableErasureExpr(AnyHashableErasureExpr *E) {
printCommon(E, "any_hashable_erasure_expr") << '\n';
printRec(E->getConformance());
OS << '\n';
printRec(E->getSubExpr());
OS << ')';
}
void visitLoadExpr(LoadExpr *E) {
printCommon(E, "load_expr") << '\n';
printRec(E->getSubExpr());
Expand Down
53 changes: 53 additions & 0 deletions lib/AST/ASTVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,33 @@ struct ASTNodeBase {};
verifyCheckedBase(E);
}

void verifyChecked(AnyHashableErasureExpr *E) {
auto anyHashableDecl = Ctx.getAnyHashableDecl();
if (!anyHashableDecl) {
Out << "AnyHashable declaration could not be found\n";
abort();
}

auto hashableDecl = Ctx.getProtocol(KnownProtocolKind::Hashable);
if (!hashableDecl) {
Out << "Hashable declaration could not be found\n";
abort();
}

checkSameType(E->getType(), anyHashableDecl->getDeclaredType(),
"AnyHashableErasureExpr and the standard AnyHashable type");

if (E->getConformance().getRequirement() != hashableDecl) {
Out << "conformance on AnyHashableErasureExpr was not for Hashable\n";
E->getConformance().dump();
abort();
}

verifyConformance(E->getSubExpr()->getType(), E->getConformance());

verifyCheckedBase(E);
}

void verifyChecked(TupleElementExpr *E) {
PrettyStackTraceExpr debugStack(Ctx, "verifying TupleElementExpr", E);

Expand Down Expand Up @@ -1795,6 +1822,32 @@ struct ASTNodeBase {};
}
}

/// Verify that the given conformance makes sense for the given
/// type.
void verifyConformance(Type type, ProtocolConformanceRef conformance) {
if (conformance.isAbstract()) {
if (!type->is<ArchetypeType>() && !type->isAnyExistentialType()) {
Out << "type " << type
<< " should not have an abstract conformance to "
<< conformance.getRequirement()->getName();
abort();
}

return;
}

if (type->getCanonicalType() !=
conformance.getConcrete()->getType()->getCanonicalType()) {
Out << "conforming type does not match conformance\n";
Out << "conforming type:\n";
type.dump(Out, 2);
Out << "\nconformance:\n";
conformance.getConcrete()->dump(Out, 2);
Out << "\n";
abort();
}
}

/// Check the given explicit protocol conformance.
void verifyConformance(Decl *decl, ProtocolConformance *conformance) {
PrettyStackTraceDecl debugStack("verifying protocol conformance", decl);
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ ConcreteDeclRef Expr::getReferencedDecl() const {
PASS_THROUGH_REFERENCE(MetatypeConversion, getSubExpr);
PASS_THROUGH_REFERENCE(CollectionUpcastConversion, getSubExpr);
PASS_THROUGH_REFERENCE(Erasure, getSubExpr);
PASS_THROUGH_REFERENCE(AnyHashableErasure, getSubExpr);
PASS_THROUGH_REFERENCE(DerivedToBase, getSubExpr);
PASS_THROUGH_REFERENCE(ArchetypeToSuper, getSubExpr);
PASS_THROUGH_REFERENCE(InjectIntoOptional, getSubExpr);
Expand Down Expand Up @@ -754,6 +755,7 @@ bool Expr::canAppendCallParentheses() const {
case ExprKind::MetatypeConversion:
case ExprKind::CollectionUpcastConversion:
case ExprKind::Erasure:
case ExprKind::AnyHashableErasure:
case ExprKind::DerivedToBase:
case ExprKind::ArchetypeToSuper:
case ExprKind::InjectIntoOptional:
Expand Down
84 changes: 68 additions & 16 deletions lib/SIL/DynamicCasts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,45 @@ classifyDynamicCastToProtocol(CanType source,
return DynamicCastFeasibility::MaySucceed;
}

static DynamicCastFeasibility
classifyDynamicCastFromProtocol(ModuleDecl *M, CanType source, CanType target,
bool isWholeModuleOpts) {
assert(source.isExistentialType() &&
"source should be an existential type");

if (source == target)
return DynamicCastFeasibility::WillSucceed;

// Casts from class existential into a non-class can never succeed.
if (source->isClassExistentialType() &&
!target.isAnyExistentialType() &&
!target.getClassOrBoundGenericClass() &&
!isa<ArchetypeType>(target) &&
!mayBridgeToObjectiveC(M, target)) {
assert((target.getEnumOrBoundGenericEnum() ||
target.getStructOrBoundGenericStruct() ||
isa<TupleType>(target) ||
isa<SILFunctionType>(target) ||
isa<FunctionType>(target) ||
isa<MetatypeType>(target)) &&
"Target should be an enum, struct, tuple, metatype or function type");
return DynamicCastFeasibility::WillFail;
}

// TODO: maybe prove that certain conformances are impossible?

return DynamicCastFeasibility::MaySucceed;
}

/// Returns the existential type associated with the Hashable
/// protocol, if it can be found.
static CanType getHashableExistentialType(Module *M) {
auto hashable =
M->getASTContext().getProtocol(KnownProtocolKind::Hashable);
if (!hashable) return CanType();
return hashable->getDeclaredType()->getCanonicalType();
}

/// Check if a given type conforms to _BridgedToObjectiveC protocol.
bool swift::isObjectiveCBridgeable(Module *M, CanType Ty) {
// Retrieve the _BridgedToObjectiveC protocol.
Expand Down Expand Up @@ -263,21 +302,11 @@ swift::classifyDynamicCast(Module *M,
target.isExistentialType())
return classifyDynamicCastToProtocol(source, target, isWholeModuleOpts);

// Casts from class existential into a non-class can never succeed.
if (source->isClassExistentialType() &&
!target.isAnyExistentialType() &&
!target.getClassOrBoundGenericClass() &&
!isa<ArchetypeType>(target) &&
!mayBridgeToObjectiveC(M, target)) {
assert((target.getEnumOrBoundGenericEnum() ||
target.getStructOrBoundGenericStruct() ||
isa<TupleType>(target) ||
isa<SILFunctionType>(target) ||
isa<FunctionType>(target) ||
isa<MetatypeType>(target)) &&
"Target should be an enum, struct, tuple, metatype or function type");
return DynamicCastFeasibility::WillFail;
}
// Check conversions from protocol types to non-protocol types.
if (source.isExistentialType() &&
!target.isExistentialType())
return classifyDynamicCastFromProtocol(M, source, target,
isWholeModuleOpts);

return DynamicCastFeasibility::MaySucceed;
}
Expand Down Expand Up @@ -540,7 +569,6 @@ swift::classifyDynamicCast(Module *M,
// Check for a viable collection cast.
if (auto sourceStruct = dyn_cast<BoundGenericStructType>(source)) {
if (auto targetStruct = dyn_cast<BoundGenericStructType>(target)) {

// Both types have to be the same kind of collection.
auto typeDecl = sourceStruct->getDecl();
if (typeDecl == targetStruct->getDecl()) {
Expand Down Expand Up @@ -571,6 +599,30 @@ swift::classifyDynamicCast(Module *M,
}
}

// Casts from AnyHashable.
if (auto sourceStruct = dyn_cast<StructType>(source)) {
if (sourceStruct->getDecl() == M->getASTContext().getAnyHashableDecl()) {
if (auto hashable = getHashableExistentialType(M)) {
// Succeeds if Hashable can be cast to the target type.
return classifyDynamicCastFromProtocol(M, hashable, target,
isWholeModuleOpts);
}
}
}

// Casts to AnyHashable.
if (auto targetStruct = dyn_cast<StructType>(target)) {
if (targetStruct->getDecl() == M->getASTContext().getAnyHashableDecl()) {
// Succeeds if the source type can be dynamically cast to Hashable.
// Hashable is not actually a legal existential type right now, but
// the check doesn't care about that.
if (auto hashable = getHashableExistentialType(M)) {
return classifyDynamicCastToProtocol(source, hashable,
isWholeModuleOpts);
}
}
}

return DynamicCastFeasibility::WillFail;
}

Expand Down
20 changes: 20 additions & 0 deletions lib/SILGen/SILGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ namespace {
CovariantReturnConversionExpr *E,
SGFContext C);
RValue visitErasureExpr(ErasureExpr *E, SGFContext C);
RValue visitAnyHashableErasureExpr(AnyHashableErasureExpr *E, SGFContext C);
RValue visitForcedCheckedCastExpr(ForcedCheckedCastExpr *E,
SGFContext C);
RValue visitConditionalCheckedCastExpr(ConditionalCheckedCastExpr *E,
Expand Down Expand Up @@ -1326,6 +1327,25 @@ RValue RValueEmitter::visitErasureExpr(ErasureExpr *E, SGFContext C) {
return RValue(SGF, E, mv);
}

RValue RValueEmitter::visitAnyHashableErasureExpr(AnyHashableErasureExpr *E,
SGFContext C) {
// Ensure that the intrinsic function exists.
auto convertFn = SGF.SGM.getConvertToAnyHashable(E);
if (!convertFn) return SGF.emitUndefRValue(E, E->getType());

// Construct the substitution for T: Hashable.
ProtocolConformanceRef conformances[] = { E->getConformance() };
Substitution sub(E->getSubExpr()->getType(),
SGF.getASTContext().AllocateCopy(conformances));

// Emit the source value into a temporary.
auto sourceOrigType = AbstractionPattern::getOpaque();
auto source =
SGF.emitMaterializedRValueAsOrig(E->getSubExpr(), sourceOrigType);

return SGF.emitApplyOfLibraryIntrinsic(E, convertFn, sub, source, C);
}

/// Treating this as a successful operation, turn a CMV into a +1 MV.
ManagedValue SILGenFunction::getManagedValue(SILLocation loc,
ConsumableManagedValue value) {
Expand Down
4 changes: 4 additions & 0 deletions lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
ManagedValue emitRValueAsOrig(Expr *E, AbstractionPattern origPattern,
const TypeLowering &origTL,
SGFContext C = SGFContext());

/// Emit an r-value into temporary memory and return the managed address.
ManagedValue
emitMaterializedRValueAsOrig(Expr *E, AbstractionPattern origPattern);

/// Emit the given expression, ignoring its result.
void emitIgnoredExpr(Expr *E);
Expand Down
20 changes: 20 additions & 0 deletions lib/SILGen/SILGenPoly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2587,6 +2587,26 @@ RValue SILGenFunction::emitSubstToOrigValue(SILLocation loc, RValue &&v,
ctxt);
}

ManagedValue
SILGenFunction::emitMaterializedRValueAsOrig(Expr *expr,
AbstractionPattern origType) {
// Create a temporary.
auto &origTL = getTypeLowering(origType, expr->getType());
auto temporary = emitTemporary(expr, origTL);

// Emit the reabstracted r-value.
auto result =
emitRValueAsOrig(expr, origType, origTL, SGFContext(temporary.get()));

// Force the result into the temporary.
if (!result.isInContext()) {
temporary->copyOrInitValueInto(*this, expr, result, /*init*/ true);
temporary->finishInitialization(*this);
}

return temporary->getManagedAddress();
}

ManagedValue
SILGenFunction::emitRValueAsOrig(Expr *expr, AbstractionPattern origPattern,
const TypeLowering &origTL, SGFContext ctxt) {
Expand Down
21 changes: 21 additions & 0 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5304,6 +5304,27 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
isBridged);
}

case ConversionRestrictionKind::HashableToAnyHashable: {
// Look through implicitly unwrapped optionals.
if (auto objTy
= cs.lookThroughImplicitlyUnwrappedOptionalType(expr->getType())) {
expr = coerceImplicitlyUnwrappedOptionalToValue(expr, objTy, locator);
}

// Find the conformance of the source type to Hashable.
auto hashable = tc.Context.getProtocol(KnownProtocolKind::Hashable);
ProtocolConformance *conformance;
bool conforms = tc.conformsToProtocol(expr->getType(), hashable, cs.DC,
ConformanceCheckFlags::InExpression,
&conformance);
assert(conforms && "must conform to Hashable");
(void)conforms;
ProtocolConformanceRef conformanceRef(hashable, conformance);

return new (tc.Context) AnyHashableErasureExpr(expr, toType,
conformanceRef);
}

case ConversionRestrictionKind::DictionaryUpcast: {
// Look through implicitly unwrapped optionals.
if (auto objTy
Expand Down
Loading