Skip to content

Parsing and type checking for the definition of isolated conformances #79624

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 2 commits into from
Feb 27, 2025
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
5 changes: 5 additions & 0 deletions include/swift/AST/ConformanceAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ struct ConformanceAttributes {

/// The location of the "unsafe" attribute if present.
SourceLoc unsafeLoc;

/// The location of the "@isolated" attribute if present.
SourceLoc isolatedLoc;

/// Merge other conformance attributes into this set.
ConformanceAttributes &
Expand All @@ -37,6 +40,8 @@ struct ConformanceAttributes {
preconcurrencyLoc = other.preconcurrencyLoc;
if (other.unsafeLoc.isValid())
unsafeLoc = other.unsafeLoc;
if (other.isolatedLoc.isValid())
isolatedLoc = other.isolatedLoc;
return *this;
}
};
Expand Down
10 changes: 4 additions & 6 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1836,12 +1836,13 @@ struct InheritedEntry : public TypeLoc {
bool isPreconcurrency() const {
return getOptions().contains(ProtocolConformanceFlags::Preconcurrency);
}
bool isIsolated() const {
return getOptions().contains(ProtocolConformanceFlags::Isolated);
}

ExplicitSafety getExplicitSafety() const {
if (getOptions().contains(ProtocolConformanceFlags::Unsafe))
return ExplicitSafety::Unsafe;
if (getOptions().contains(ProtocolConformanceFlags::Safe))
return ExplicitSafety::Safe;
return ExplicitSafety::Unspecified;
}

Expand All @@ -1852,13 +1853,10 @@ struct InheritedEntry : public TypeLoc {
}

void setOption(ExplicitSafety safety) {
RawOptions = (getOptions() - ProtocolConformanceFlags::Unsafe
- ProtocolConformanceFlags::Safe).toRaw();
RawOptions = (getOptions() - ProtocolConformanceFlags::Unsafe).toRaw();
switch (safety) {
case ExplicitSafety::Unspecified:
break;
case ExplicitSafety::Safe:
RawOptions = (getOptions() | ProtocolConformanceFlags::Safe).toRaw();
break;
case ExplicitSafety::Unsafe:
RawOptions = (getOptions() | ProtocolConformanceFlags::Unsafe).toRaw();
Expand Down
15 changes: 15 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2722,8 +2722,23 @@ WARNING(add_predates_concurrency_import,none,
GROUPED_WARNING(remove_predates_concurrency_import,PreconcurrencyImport,
DefaultIgnore,
"'@preconcurrency' attribute on module %0 has no effect", (Identifier))
NOTE(add_isolated_to_conformance,none,
"add 'isolated' to the %0 conformance to restrict it to %1 code",
(DeclName, ActorIsolation))
NOTE(add_preconcurrency_to_conformance,none,
"add '@preconcurrency' to the %0 conformance to defer isolation checking to run time", (DeclName))
ERROR(isolated_conformance_not_global_actor_isolated,none,
"isolated conformance is only permitted on global-actor-isolated types",
())
ERROR(isolated_conformance_experimental_feature,none,
"isolated conformances require experimental feature "
" 'IsolatedConformances'", ())
ERROR(nonisolated_conformance_depends_on_isolated_conformance,none,
"conformance of %0 to %1 depends on %2 conformance of %3 to %4; mark it as 'isolated'",
(Type, DeclName, ActorIsolation, Type, DeclName))
ERROR(isolated_conformance_mismatch_with_associated_isolation,none,
"%0 conformance of %1 to %2 cannot depend on %3 conformance of %4 to %5",
(ActorIsolation, Type, DeclName, ActorIsolation, Type, DeclName))
WARNING(remove_public_import,none,
"public import of %0 was not used in public declarations or inlinable code",
(Identifier))
Expand Down
7 changes: 5 additions & 2 deletions include/swift/AST/ProtocolConformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,11 @@ class NormalProtocolConformance : public RootProtocolConformance,
return getOptions().contains(ProtocolConformanceFlags::Preconcurrency);
}

/// Whether this is an isolated conformance.
bool isIsolated() const {
return getOptions().contains(ProtocolConformanceFlags::Isolated);
}

/// Retrieve the location of `@preconcurrency`, if there is one and it is
/// known.
SourceLoc getPreconcurrencyLoc() const { return PreconcurrencyLoc; }
Expand All @@ -678,8 +683,6 @@ class NormalProtocolConformance : public RootProtocolConformance,
ExplicitSafety getExplicitSafety() const {
if (getOptions().contains(ProtocolConformanceFlags::Unsafe))
return ExplicitSafety::Unsafe;
if (getOptions().contains(ProtocolConformanceFlags::Safe))
return ExplicitSafety::Safe;
return ExplicitSafety::Unspecified;
}

Expand Down
4 changes: 2 additions & 2 deletions include/swift/AST/ProtocolConformanceOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ enum class ProtocolConformanceFlags {
/// @retroactive conformance
Retroactive = 0x08,

/// @safe conformance
Safe = 0x10,
/// @isolated conformance
Isolated = 0x10,

// Note: whenever you add a bit here, update
// NumProtocolConformanceOptions below.
Expand Down
4 changes: 4 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -450,11 +450,15 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(CustomAvailability, true)
/// Be strict about the Sendable conformance of metatypes.
EXPERIMENTAL_FEATURE(StrictSendableMetatypes, true)


/// Allow public enumerations to be extensible by default
/// regardless of whether the module they are declared in
/// is resilient or not.
EXPERIMENTAL_FEATURE(ExtensibleEnums, true)

/// Allow isolated conformances.
EXPERIMENTAL_FEATURE(IsolatedConformances, true)

#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE
Expand Down
7 changes: 7 additions & 0 deletions lib/AST/ConformanceLookupTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
options |= ProtocolConformanceFlags::Preconcurrency;
if (getUnsafeLoc().isValid())
options |= ProtocolConformanceFlags::Unsafe;
if (getIsolatedLoc().isValid())
options |= ProtocolConformanceFlags::Isolated;
return options;
}

Expand Down Expand Up @@ -209,6 +211,11 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
return attributes.unsafeLoc;
}

/// The location of the @isolated attribute, if any.
SourceLoc getIsolatedLoc() const {
return attributes.isolatedLoc;
}

/// For an inherited conformance, retrieve the class declaration
/// for the inheriting class.
ClassDecl *getInheritingClass() const {
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1780,6 +1780,8 @@ InheritedEntry::InheritedEntry(const TypeLoc &typeLoc)
setOption(ProtocolConformanceFlags::Unsafe);
if (typeRepr->findAttrLoc(TypeAttrKind::Preconcurrency).isValid())
setOption(ProtocolConformanceFlags::Preconcurrency);
if (typeRepr->findAttrLoc(TypeAttrKind::Isolated).isValid())
setOption(ProtocolConformanceFlags::Isolated);
}
}

Expand Down
5 changes: 5 additions & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,11 @@ static bool usesFeatureABIAttribute(Decl *decl) {
return getABIAttr(decl) != nullptr;
}

static bool usesFeatureIsolatedConformances(Decl *decl) {
// FIXME: Check conformances associated with this decl?
return false;
}

UNINTERESTING_FEATURE(WarnUnsafe)
UNINTERESTING_FEATURE(SafeInteropWrappers)
UNINTERESTING_FEATURE(AssumeResilientCxxTypes)
Expand Down
25 changes: 24 additions & 1 deletion lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3144,6 +3144,12 @@ directReferencesForTypeRepr(Evaluator &evaluator, ASTContext &ctx,
attributed->getTypeRepr(), dc, options);
}

case TypeReprKind::Isolated: {
auto isolated = cast<IsolatedTypeRepr>(typeRepr);
return directReferencesForTypeRepr(evaluator, ctx,
isolated->getBase(), dc, options);
}

case TypeReprKind::Composition: {
auto composition = cast<CompositionTypeRepr>(typeRepr);
for (auto component : composition->getTypes()) {
Expand Down Expand Up @@ -3217,7 +3223,6 @@ directReferencesForTypeRepr(Evaluator &evaluator, ASTContext &ctx,
case TypeReprKind::Error:
case TypeReprKind::Function:
case TypeReprKind::Ownership:
case TypeReprKind::Isolated:
case TypeReprKind::CompileTimeConst:
case TypeReprKind::Metatype:
case TypeReprKind::Protocol:
Expand Down Expand Up @@ -3933,6 +3938,21 @@ CustomAttrNominalRequest::evaluate(Evaluator &evaluator,
return nullptr;
}

/// Find the location of 'isolated' within this type representation.
static SourceLoc findIsolatedLoc(TypeRepr *typeRepr) {
do {
if (auto isolatedTypeRepr = dyn_cast<IsolatedTypeRepr>(typeRepr))
return isolatedTypeRepr->getLoc();

if (auto attrTypeRepr = dyn_cast<AttributedTypeRepr>(typeRepr)) {
typeRepr = attrTypeRepr->getTypeRepr();
continue;
}

return SourceLoc();
} while (true);
}

/// Decompose the ith inheritance clause entry to a list of type declarations,
/// inverses, and optional AnyObject member.
void swift::getDirectlyInheritedNominalTypeDecls(
Expand Down Expand Up @@ -3971,6 +3991,9 @@ void swift::getDirectlyInheritedNominalTypeDecls(
attributes.uncheckedLoc = typeRepr->findAttrLoc(TypeAttrKind::Unchecked);
attributes.preconcurrencyLoc = typeRepr->findAttrLoc(TypeAttrKind::Preconcurrency);
attributes.unsafeLoc = typeRepr->findAttrLoc(TypeAttrKind::Unsafe);

// Look for an IsolatedTypeRepr.
attributes.isolatedLoc = findIsolatedLoc(typeRepr);
}

// Form the result.
Expand Down
4 changes: 3 additions & 1 deletion lib/Parse/ParseType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ ParserResult<TypeRepr> Parser::parseTypeSimple(
Diag<> MessageID, ParseTypeReason reason) {
ParserResult<TypeRepr> ty;

if (isParameterSpecifier()) {
if (isParameterSpecifier() &&
!(!Context.LangOpts.hasFeature(Feature::IsolatedConformances) &&
Tok.isContextualKeyword("isolated"))) {
// Type specifier should already be parsed before here. This only happens
// for construct like 'P1 & inout P2'.
diagnose(Tok.getLoc(), diag::attr_only_on_parameters, Tok.getRawText());
Expand Down
54 changes: 54 additions & 0 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "swift/AST/ImportCache.h"
#include "swift/AST/Initializer.h"
#include "swift/AST/NameLookupRequests.h"
#include "swift/AST/PackConformance.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/TypeCheckRequests.h"
Expand Down Expand Up @@ -7669,3 +7670,56 @@ bool swift::diagnoseNonSendableFromDeinit(
diag::non_sendable_from_deinit,
var->getDescriptiveKind(), var->getName());
}

bool swift::forEachIsolatedConformance(
ProtocolConformanceRef conformance,
llvm::function_ref<bool(ProtocolConformance*)> body
) {
if (conformance.isInvalid() || conformance.isAbstract())
return false;

if (conformance.isPack()) {
auto pack = conformance.getPack()->getPatternConformances();
for (auto conformance : pack) {
if (forEachIsolatedConformance(conformance, body))
return true;
}

return false;
}

// Is this an isolated conformance?
auto concrete = conformance.getConcrete();
if (auto normal =
dyn_cast<NormalProtocolConformance>(concrete->getRootConformance())) {
if (normal->isIsolated()) {
if (body(concrete))
return true;
}
}

// Check conformances that are part of this conformance.
auto subMap = concrete->getSubstitutionMap();
for (auto conformance : subMap.getConformances()) {
if (forEachIsolatedConformance(conformance, body))
return true;
}

return false;
}

ActorIsolation swift::getConformanceIsolation(ProtocolConformance *conformance) {
auto rootNormal =
dyn_cast<NormalProtocolConformance>(conformance->getRootConformance());
if (!rootNormal)
return ActorIsolation::forNonisolated(false);

if (!rootNormal->isIsolated())
return ActorIsolation::forNonisolated(false);

auto nominal = rootNormal->getDeclContext()->getSelfNominalTypeDecl();
if (!nominal)
return ActorIsolation::forNonisolated(false);

return getActorIsolation(nominal);
}
13 changes: 13 additions & 0 deletions lib/Sema/TypeCheckConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,19 @@ void introduceUnsafeInheritExecutorReplacements(
void introduceUnsafeInheritExecutorReplacements(
const DeclContext *dc, Type base, SourceLoc loc, LookupResult &result);

/// Enumerate all of the isolated conformances in the given conformance.
///
/// The given `body` will be called on each isolated conformance. If it ever
/// returns `true`, this function will abort the search and return `true`.
bool forEachIsolatedConformance(
ProtocolConformanceRef conformance,
llvm::function_ref<bool(ProtocolConformance*)> body
);

/// Determine the isolation of the given conformance. This only applies to
/// the immediate conformance, not any conformances on which it depends.
ActorIsolation getConformanceIsolation(ProtocolConformance *conformance);

} // end namespace swift

namespace llvm {
Expand Down
Loading