diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 2fb3d000e0f3f..8b1d48772d6af 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8702,6 +8702,14 @@ ERROR(inherit_actor_context_only_on_async_or_isolation_erased_params,none, "asynchronous function types", (DeclAttribute)) +ERROR(inherit_actor_context_with_concurrent,none, + "'@_inheritActorContext' attribute cannot be used together with %0", + (const TypeAttribute *)) + +ERROR(inherit_actor_context_with_nonisolated_nonsending,none, + "'@_inheritActorContext' attribute cannot be used together with %0", + (TypeRepr *)) + //===----------------------------------------------------------------------===// // MARK: @concurrent and nonisolated(nonsending) attributes //===----------------------------------------------------------------------===// diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 148cf3514c80f..0dcda6d0fbf7f 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -497,6 +497,10 @@ namespace { RValue emitFunctionCvtFromExecutionCallerToGlobalActor(FunctionConversionExpr *E, SGFContext C); + + RValue emitFunctionCvtForNonisolatedNonsendingClosureExpr( + FunctionConversionExpr *E, SGFContext C); + RValue visitActorIsolationErasureExpr(ActorIsolationErasureExpr *E, SGFContext C); RValue visitExtractFunctionIsolationExpr(ExtractFunctionIsolationExpr *E, @@ -2031,6 +2035,44 @@ RValueEmitter::emitFunctionCvtToExecutionCaller(FunctionConversionExpr *e, return RValue(SGF, e, destType, result); } +RValue RValueEmitter::emitFunctionCvtForNonisolatedNonsendingClosureExpr( + FunctionConversionExpr *E, SGFContext C) { + // The specific AST pattern for this looks as follows: + // + // (function_conversion_expr type="nonisolated(nonsending) () async -> Void" + // (closure_expr type="() async -> ()" isolated_to_caller_isolation)) + CanAnyFunctionType destType = + cast(E->getType()->getCanonicalType()); + auto subExpr = E->getSubExpr()->getSemanticsProvidingExpr(); + + // If we do not have a closure or if that closure is not caller isolation + // inheriting, bail. + auto *closureExpr = dyn_cast(subExpr); + if (!closureExpr || + !closureExpr->getActorIsolation().isCallerIsolationInheriting()) + return RValue(); + + // Then grab our closure type... make sure it is non isolated and then make + // sure it is the same as our destType but with nonisolated. + CanAnyFunctionType closureType = + cast(closureExpr->getType()->getCanonicalType()); + if (!closureType->getIsolation().isNonIsolated() || + closureType != + destType->withIsolation(FunctionTypeIsolation::forNonIsolated()) + ->getCanonicalType()) + return RValue(); + + // NOTE: This is a partial inline of getClosureTypeInfo. We do this so we have + // more control and make this change less viral in the compiler for 6.2. + auto newExtInfo = closureType->getExtInfo().withIsolation( + FunctionTypeIsolation::forNonIsolatedCaller()); + closureType = closureType.withExtInfo(newExtInfo); + auto info = SGF.getFunctionTypeInfo(closureType); + + auto closure = emitClosureReference(closureExpr, info); + return RValue(SGF, closureExpr, destType, closure); +} + RValue RValueEmitter::emitFunctionCvtFromExecutionCallerToGlobalActor( FunctionConversionExpr *e, SGFContext C) { // We are pattern matching a conversion sequence like the following: @@ -2142,6 +2184,28 @@ RValue RValueEmitter::visitFunctionConversionExpr(FunctionConversionExpr *e, // TODO: Move this up when we can emit closures directly with C calling // convention. auto subExpr = e->getSubExpr()->getSemanticsProvidingExpr(); + + // Before we go any further into emitting the convert function expr, see if + // our SubExpr is a ClosureExpr with the exact same type as our + // FunctionConversionExpr except with the FunctionConversionExpr adding + // nonisolated(nonsending). Then see if the ClosureExpr itself (even though it + // is not nonisolated(nonsending) typed is considered to have + // nonisolated(nonsending) isolation. In such a case, emit the closure + // directly. We are going to handle it especially in closure emission to work + // around the missing information in the type. + // + // DISCUSSION: We need to do this here since in the Expression TypeChecker we + // do not have access to capture information when we would normally want to + // mark the closure type as being nonisolated(nonsending). As a result, we + // cannot know if the nonisolated(nonsending) should be overridden by for + // example an actor that is captured by the closure. So to work around this in + // Sema, we still mark the ClosureExpr as having the appropriate isolation + // even though its type does not have it... and then we work around this here + // and also in getClosureTypeInfo. + if (destType->getIsolation().isNonIsolatedCaller()) + if (auto rv = emitFunctionCvtForNonisolatedNonsendingClosureExpr(e, C)) + return rv; + // Look through `as` type ascriptions that don't induce bridging too. while (auto subCoerce = dyn_cast(subExpr)) { // Coercions that introduce bridging aren't simple type ascriptions. diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 077785c16b62a..a6d86969ff3f7 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -7849,23 +7849,6 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType, } } - // If we have a ClosureExpr, then we can safely propagate the - // 'nonisolated(nonsending)' isolation if it's not explicitly - // marked as `@concurrent`. - if (toEI.getIsolation().isNonIsolatedCaller() && - (fromEI.getIsolation().isNonIsolated() && - !isClosureMarkedAsConcurrent(expr))) { - auto newFromFuncType = fromFunc->withIsolation( - FunctionTypeIsolation::forNonIsolatedCaller()); - if (applyTypeToClosureExpr(cs, expr, newFromFuncType)) { - fromFunc = newFromFuncType->castTo(); - // Propagating 'nonisolated(nonsending)' might have satisfied the entire - // conversion. If so, we're done, otherwise keep converting. - if (fromFunc->isEqual(toType)) - return expr; - } - } - if (ctx.LangOpts.isDynamicActorIsolationCheckingEnabled()) { // Passing a synchronous global actor-isolated function value and // parameter that expects a synchronous non-isolated function type could diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 48f3242d61b79..8d475ec630ea6 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -7871,6 +7871,9 @@ void AttributeChecker::visitInheritActorContextAttr( return; auto paramTy = P->getInterfaceType(); + if (paramTy->hasError()) + return; + auto *funcTy = paramTy->lookThroughAllOptionalTypes()->getAs(); if (!funcTy) { diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index af6f0f4d9d7ba..028cd7f7ac262 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -4964,8 +4964,33 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation( closure->getParent(), getClosureActorIsolation); preconcurrency |= parentIsolation.preconcurrency(); - return computeClosureIsolationFromParent(closure, parentIsolation, - checkIsolatedCapture); + auto normalIsolation = computeClosureIsolationFromParent( + closure, parentIsolation, checkIsolatedCapture); + + // The solver has to be conservative and produce a conversion to + // `nonisolated(nonsending)` because at solution application time + // we don't yet know whether there are any captures which would + // make closure isolated. + // + // At this point we know that closure is not explicitly annotated with + // global actor, nonisolated/@concurrent attributes and doesn't have + // isolated parameters. If our closure is nonisolated and we have a + // conversion to nonisolated(nonsending), then we should respect that. + if (auto *explicitClosure = dyn_cast(closure); + !normalIsolation.isGlobalActor()) { + if (auto *fce = + dyn_cast_or_null(Parent.getAsExpr())) { + auto expectedIsolation = + fce->getType()->castTo()->getIsolation(); + if (expectedIsolation.isNonIsolatedCaller()) { + auto captureInfo = explicitClosure->getCaptureInfo(); + if (!captureInfo.getIsolatedParamCapture()) + return ActorIsolation::forCallerIsolationInheriting(); + } + } + } + + return normalIsolation; }(); // Apply computed preconcurrency. diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index 2540469873b4f..aff024004c3f1 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -2337,6 +2337,9 @@ static Type validateParameterType(ParamDecl *decl) { if (dc->isInSpecializeExtensionContext()) options |= TypeResolutionFlags::AllowUsableFromInline; + if (decl->getAttrs().hasAttribute()) + options |= TypeResolutionFlags::InheritsActorContext; + Type Ty; auto *nestedRepr = decl->getTypeRepr(); diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 1fc7ccd5e1d97..a9f4d968a92f0 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -4219,6 +4219,11 @@ NeverNullType TypeResolver::resolveASTFunctionType( attr->getAttrName()); } + if (options.contains(TypeResolutionFlags::InheritsActorContext)) { + diagnoseInvalid(repr, attr->getAttrLoc(), + diag::inherit_actor_context_with_concurrent, attr); + } + switch (isolation.getKind()) { case FunctionTypeIsolation::Kind::NonIsolated: break; @@ -4265,18 +4270,20 @@ NeverNullType TypeResolver::resolveASTFunctionType( if (!repr->isInvalid()) isolation = FunctionTypeIsolation::forNonIsolated(); } else if (!getWithoutClaiming(attrs)) { - // Infer async function type as `nonisolated(nonsending)` if there is - // no `@concurrent` or `nonisolated(nonsending)` attribute and isolation - // is nonisolated. - if (ctx.LangOpts.hasFeature(Feature::NonisolatedNonsendingByDefault) && - repr->isAsync() && isolation.isNonIsolated()) { - isolation = FunctionTypeIsolation::forNonIsolatedCaller(); - } else if (ctx.LangOpts - .getFeatureState(Feature::NonisolatedNonsendingByDefault) - .isEnabledForMigration()) { - // Diagnose only in the interface stage, which is run once. - if (inStage(TypeResolutionStage::Interface)) { - warnAboutNewNonisolatedAsyncExecutionBehavior(ctx, repr, isolation); + if (!options.contains(TypeResolutionFlags::InheritsActorContext)) { + // Infer async function type as `nonisolated(nonsending)` if there is + // no `@concurrent` or `nonisolated(nonsending)` attribute and isolation + // is nonisolated. + if (ctx.LangOpts.hasFeature(Feature::NonisolatedNonsendingByDefault) && + repr->isAsync() && isolation.isNonIsolated()) { + isolation = FunctionTypeIsolation::forNonIsolatedCaller(); + } else if (ctx.LangOpts + .getFeatureState(Feature::NonisolatedNonsendingByDefault) + .isEnabledForMigration()) { + // Diagnose only in the interface stage, which is run once. + if (inStage(TypeResolutionStage::Interface)) { + warnAboutNewNonisolatedAsyncExecutionBehavior(ctx, repr, isolation); + } } } } @@ -5326,6 +5333,12 @@ TypeResolver::resolveCallerIsolatedTypeRepr(CallerIsolatedTypeRepr *repr, return ErrorType::get(getASTContext()); } + if (options.contains(TypeResolutionFlags::InheritsActorContext)) { + diagnoseInvalid(repr, repr->getLoc(), + diag::inherit_actor_context_with_nonisolated_nonsending, + repr); + } + if (!fnType->isAsync()) { diagnoseInvalid(repr, repr->getStartLoc(), diag::nonisolated_nonsending_only_on_async, repr); diff --git a/lib/Sema/TypeCheckType.h b/lib/Sema/TypeCheckType.h index ecf18d2f05e00..9e622246bde17 100644 --- a/lib/Sema/TypeCheckType.h +++ b/lib/Sema/TypeCheckType.h @@ -87,6 +87,9 @@ enum class TypeResolutionFlags : uint16_t { /// Whether the immediate context has an @escaping attribute. DirectEscaping = 1 << 14, + + /// We are in a `@_inheritActorContext` parameter declaration. + InheritsActorContext = 1 << 15, }; /// Type resolution contexts that require special handling. diff --git a/test/Concurrency/attr_execution/attr_execution.swift b/test/Concurrency/attr_execution/attr_execution.swift index 93702c1c8aacb..bc848de3c3889 100644 --- a/test/Concurrency/attr_execution/attr_execution.swift +++ b/test/Concurrency/attr_execution/attr_execution.swift @@ -18,7 +18,7 @@ func callerTest() async {} struct Test { // CHECK-LABEL: // closure #1 in variable initialization expression of Test.x // CHECK: // Isolation: caller_isolation_inheriting - // CHECK: sil private [ossa] @$s14attr_execution4TestV1xyyYaYCcvpfiyyYaYCcfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> () + // CHECK: sil private [ossa] @$s14attr_execution4TestV1xyyYaYCcvpfiyyYacfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> () var x: () async -> Void = {} // CHECK-LABEL: // Test.test() @@ -67,7 +67,7 @@ func takesClosure(fn: () async -> Void) { } // CHECK-LABEL: sil hidden [ossa] @$s14attr_execution11testClosureyyF : $@convention(thin) () -> () -// CHECK: [[CLOSURE:%.*]] = function_ref @$s14attr_execution11testClosureyyFyyYaYCXEfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> () +// CHECK: [[CLOSURE:%.*]] = function_ref @$s14attr_execution11testClosureyyFyyYaXEfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> () // CHECK: [[THUNKED_CLOSURE:%.*]] = thin_to_thick_function %0 to $@noescape @async @callee_guaranteed (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> () // CHECK: [[TAKES_CLOSURE:%.*]] = function_ref @$s14attr_execution12takesClosure2fnyyyYaYCXE_tF : $@convention(thin) (@guaranteed @noescape @async @callee_guaranteed (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> ()) -> () // CHECK: apply [[TAKES_CLOSURE]]([[THUNKED_CLOSURE]]) @@ -75,8 +75,21 @@ func takesClosure(fn: () async -> Void) { // CHECK-LABEL: // closure #1 in testClosure() // CHECK: // Isolation: caller_isolation_inheriting -// CHECK: sil private [ossa] @$s14attr_execution11testClosureyyFyyYaYCXEfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> () +// CHECK: sil private [ossa] @$s14attr_execution11testClosureyyFyyYaXEfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> () func testClosure() { takesClosure { } } + +// CHECK-LABEL: // testInheritsActor(fn:) +// CHECK: Isolation: global_actor. type: MainActor +// CHECK: sil hidden [ossa] @$s14attr_execution17testInheritsActor2fnyyyYaYbXE_tYaF : $@convention(thin) @async (@guaranteed @noescape @Sendable @async @callee_guaranteed () -> ()) -> () +// CHECK: bb0([[FN:%.*]] : @guaranteed $@noescape @Sendable @async @callee_guaranteed () -> ()): +// CHECK: [[FN_COPY:%.*]] = copy_value [[FN]] +// CHECK: [[BORROWED_FN_COPY:%.*]] = begin_borrow [[FN_COPY]] +// CHECK: apply [[BORROWED_FN_COPY]]() : $@noescape @Sendable @async @callee_guaranteed () -> () +// CHECK: } // end sil function '$s14attr_execution17testInheritsActor2fnyyyYaYbXE_tYaF' +@MainActor +func testInheritsActor(@_inheritActorContext(always) fn: @Sendable () async -> Void) async { + await fn() +} diff --git a/test/Concurrency/attr_execution/conversions_silgen.swift b/test/Concurrency/attr_execution/conversions_silgen.swift index 7b97a59fc57e5..2271c686ed172 100644 --- a/test/Concurrency/attr_execution/conversions_silgen.swift +++ b/test/Concurrency/attr_execution/conversions_silgen.swift @@ -421,7 +421,7 @@ func conversionsFromSyncToAsync(_ x: @escaping @Sendable (NonSendableKlass) -> V } func testThatClosuresAssumeIsolation(fn: inout nonisolated(nonsending) (Int) async -> Void) { - // CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFyyYaYCcfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> () + // CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFyyYacfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> () { // CHECK: bb0([[EXECUTOR:%.*]] : @guaranteed $Optional): // CHECK: hop_to_executor [[EXECUTOR]] let _: nonisolated(nonsending) () async -> Void = { @@ -430,7 +430,7 @@ func testThatClosuresAssumeIsolation(fn: inout nonisolated(nonsending) (Int) asy func testParam(_: nonisolated(nonsending) () async throws -> Void) {} - // CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFyyYaYCXEfU0_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> @error any Error + // CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFyyYaXEfU0_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional) -> () { // CHECK: bb0([[EXECUTOR:%.*]] : @guaranteed $Optional): // CHECK: hop_to_executor [[EXECUTOR]] testParam { 42 } @@ -440,7 +440,7 @@ func testThatClosuresAssumeIsolation(fn: inout nonisolated(nonsending) (Int) asy // CHECK: hop_to_executor [[GENERIC_EXECUTOR]] testParam { @concurrent in 42 } - // CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFySiYaYCcfU2_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional, Int) -> () + // CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFySiYacfU2_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional, Int) -> () { // CHECK: bb0([[EXECUTOR:%.*]] : @guaranteed $Optional, %1 : $Int): // CHECK: hop_to_executor [[EXECUTOR]] fn = { _ in } diff --git a/test/Concurrency/attr_execution/migration_mode.swift b/test/Concurrency/attr_execution/migration_mode.swift index 08929d2186651..25d08906ac06e 100644 --- a/test/Concurrency/attr_execution/migration_mode.swift +++ b/test/Concurrency/attr_execution/migration_mode.swift @@ -393,3 +393,9 @@ do { } } } + +// @_inheritActorContext prevents `nonisolated(nonsending)` inference. +do { + func testInherit1(@_inheritActorContext _: @Sendable () async -> Void) {} + func testInherit2(@_inheritActorContext(always) _: (@Sendable () async -> Void)?) {} +} diff --git a/test/Concurrency/attr_execution/nonisolated_nonsending_by_default.swift b/test/Concurrency/attr_execution/nonisolated_nonsending_by_default.swift index 6c240ec88f364..2a94b64d30819 100644 --- a/test/Concurrency/attr_execution/nonisolated_nonsending_by_default.swift +++ b/test/Concurrency/attr_execution/nonisolated_nonsending_by_default.swift @@ -8,3 +8,8 @@ func testCasts() { // expected-error@-1 {{cannot convert value of type '(nonisolated(nonsending) () async -> ()).Type' to type '(() async -> ()).Type' in coercion}} _ = defaultedType as (nonisolated(nonsending) () async -> ()).Type // Ok } + +func test(@_inheritActorContext fn: @Sendable () async -> Void) { + let _: Int = fn + // expected-error@-1 {{cannot convert value of type '@Sendable () async -> Void' to specified type 'Int'}} +} diff --git a/test/Concurrency/transfernonsendable_closureliterals_isolationinference.swift b/test/Concurrency/transfernonsendable_closureliterals_isolationinference.swift index 150c8c16ad680..fb3669656dfd4 100644 --- a/test/Concurrency/transfernonsendable_closureliterals_isolationinference.swift +++ b/test/Concurrency/transfernonsendable_closureliterals_isolationinference.swift @@ -1,14 +1,12 @@ -// RUN: %target-swift-frontend -emit-sil -parse-as-library -target %target-swift-5.1-abi-triple -swift-version 5 -strict-concurrency=complete %s -o - | %FileCheck %s +// RUN: %target-swift-frontend -emit-sil -parse-as-library -target %target-swift-5.1-abi-triple -swift-version 5 -strict-concurrency=complete %s -o - 2>/dev/null | %FileCheck %s // RUN: %target-swift-frontend -emit-sil -parse-as-library -target %target-swift-5.1-abi-triple -swift-version 6 -verify %s -o /dev/null -verify-additional-prefix ni- -// RUN: %target-swift-frontend -emit-sil -parse-as-library -target %target-swift-5.1-abi-triple -swift-version 5 -strict-concurrency=complete %s -o - -enable-upcoming-feature NonisolatedNonsendingByDefault | %FileCheck %s +// RUN: %target-swift-frontend -emit-sil -parse-as-library -target %target-swift-5.1-abi-triple -swift-version 5 -strict-concurrency=complete %s -o - -enable-upcoming-feature NonisolatedNonsendingByDefault 2>/dev/null | %FileCheck %s // RUN: %target-swift-frontend -emit-sil -parse-as-library -target %target-swift-5.1-abi-triple -swift-version 6 -verify %s -o /dev/null -enable-upcoming-feature NonisolatedNonsendingByDefault -verify-additional-prefix ni-ns- // REQUIRES: concurrency // REQUIRES: asserts // REQUIRES: swift_feature_NonisolatedNonsendingByDefault -// REQUIRES: rdar154969621 - // This test validates the behavior of transfernonsendable around // closure literals diff --git a/test/Parse/execution_behavior_attrs.swift b/test/Parse/execution_behavior_attrs.swift index 6f0a1035ff6c0..2ad748d1f289d 100644 --- a/test/Parse/execution_behavior_attrs.swift +++ b/test/Parse/execution_behavior_attrs.swift @@ -84,3 +84,10 @@ do { nonisolated(0) // expected-warning {{result of call to 'nonisolated' is unused}} print("hello") } + +do { + func testActorInheriting1(@_inheritActorContext _: @concurrent @Sendable () async -> Void) {} + // expected-error@-1 {{'@_inheritActorContext' attribute cannot be used together with '@concurrent'}} + func testActorInheriting2(@_inheritActorContext _: nonisolated(nonsending) @Sendable () async -> Void) {} + // expected-error@-1 {{'@_inheritActorContext' attribute cannot be used together with 'nonisolated(nonsending)'}} +}