From 6fa088ab4d63f074b2e03152ed8afe3daa64917f Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 13 Feb 2024 22:20:46 +0100 Subject: [PATCH 1/5] Remove code duplication --- Sources/Core/AST/AST+Walk.swift | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/Sources/Core/AST/AST+Walk.swift b/Sources/Core/AST/AST+Walk.swift index 442ebe0ec..fd88489a1 100644 --- a/Sources/Core/AST/AST+Walk.swift +++ b/Sources/Core/AST/AST+Walk.swift @@ -236,16 +236,6 @@ extension AST { } } - /// Visits `b` and its children in pre-order, notifying `o` when a node is entered or left. - public func walk(functionBody b: FunctionBody, notifying o: inout O) { - switch b { - case .expr(let e): - walk(e, notifying: &o) - case .block(let s): - walk(s, notifying: &o) - } - } - // MARK: Declarations /// Visits the children of `n` in pre-order, notifying `o` when a node is entered or left. @@ -301,7 +291,7 @@ extension AST { walk(roots: n.parameters, notifying: &o) walk(n.receiver, notifying: &o) walk(n.output, notifying: &o) - n.body.map({ walk(functionBody: $0, notifying: &o) }) + n.body.map({ (b) in walk(b.base, notifying: &o) }) } /// Visits the children of `n` in pre-order, notifying `o` when a node is entered or left. @@ -342,7 +332,7 @@ extension AST { _ n: MethodImpl, notifying o: inout O ) { walk(n.receiver, notifying: &o) - n.body.map({ walk(functionBody: $0, notifying: &o) }) + n.body.map({ (b) in walk(b.base, notifying: &o) }) } /// Visits the children of `n` in pre-order, notifying `o` when a node is entered or left. @@ -397,7 +387,7 @@ extension AST { _ n: SubscriptImpl, notifying o: inout O ) { walk(n.receiver, notifying: &o) - n.body.map({ walk(functionBody: $0, notifying: &o) }) + n.body.map({ (b) in walk(b.base, notifying: &o) }) } /// Visits the children of `n` in pre-order, notifying `o` when a node is entered or left. From 66dfafb53384723a72e91997017bb6a225602040 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 13 Feb 2024 22:22:38 +0100 Subject: [PATCH 2/5] Add a property to project parts containing implicit captures --- Sources/Core/AST/Decl/CapturingDecl.swift | 3 +++ Sources/Core/AST/Decl/FunctionDecl.swift | 3 +++ Sources/Core/AST/Decl/SubscriptDecl.swift | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Sources/Core/AST/Decl/CapturingDecl.swift b/Sources/Core/AST/Decl/CapturingDecl.swift index 9fb423e84..3d2bf835b 100644 --- a/Sources/Core/AST/Decl/CapturingDecl.swift +++ b/Sources/Core/AST/Decl/CapturingDecl.swift @@ -4,4 +4,7 @@ public protocol CapturingDecl: Decl, LexicalScope { /// The explicit capture declarations of the entity. var explicitCaptures: [BindingDecl.ID] { get } + /// The part of the declaration that may have implicit captures. + var sourcesOfImplicitCaptures: [AnyNodeID] { get } + } diff --git a/Sources/Core/AST/Decl/FunctionDecl.swift b/Sources/Core/AST/Decl/FunctionDecl.swift index 1f0114275..bbbe07329 100644 --- a/Sources/Core/AST/Decl/FunctionDecl.swift +++ b/Sources/Core/AST/Decl/FunctionDecl.swift @@ -130,6 +130,9 @@ public struct FunctionDecl: CapturingDecl, ExposableDecl, GenericDecl, GenericSc } } + /// The part of the declaration that may have implicit captures. + public var sourcesOfImplicitCaptures: [AnyNodeID] { body.map({ (b) in [b.base] }) ?? [] } + public func validateForm(in ast: AST, reportingDiagnosticsTo log: inout DiagnosticSet) { if !isInExprContext { // Parameter declarations must have a type annotation. diff --git a/Sources/Core/AST/Decl/SubscriptDecl.swift b/Sources/Core/AST/Decl/SubscriptDecl.swift index 0a0e37e21..44a72cff9 100644 --- a/Sources/Core/AST/Decl/SubscriptDecl.swift +++ b/Sources/Core/AST/Decl/SubscriptDecl.swift @@ -84,6 +84,9 @@ public struct SubscriptDecl: BundleDecl, CapturingDecl, GenericDecl, GenericScop /// Returns whether the declaration denotes a static subscript. public var isStatic: Bool { memberModifier?.value == .static } + /// The part of the declaration that may have implicit captures. + public var sourcesOfImplicitCaptures: [AnyNodeID] { impls.map(AnyNodeID.init) } + public func validateForm(in ast: AST, reportingDiagnosticsTo log: inout DiagnosticSet) { // Parameter declarations must have a type annotation. for p in parameters { From 69ca1df0edfe4236b45952811d5ab7c2a900dc44 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 13 Feb 2024 22:24:22 +0100 Subject: [PATCH 3/5] Tighten the API of AST.uses(in:) --- Sources/FrontEnd/AST+UseCollection.swift | 4 ++-- Sources/FrontEnd/TypeChecking/TypeChecker.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/FrontEnd/AST+UseCollection.swift b/Sources/FrontEnd/AST+UseCollection.swift index f31ed5ea0..e8ea354ac 100644 --- a/Sources/FrontEnd/AST+UseCollection.swift +++ b/Sources/FrontEnd/AST+UseCollection.swift @@ -7,7 +7,7 @@ extension AST { /// /// This method collects all name expressions that occurs in `d`, visiting its children in /// pre-order. Nested type and extension declarations are not visited. - func uses(in d: AnyDeclID) -> [(NameExpr.ID, AccessEffect)] { + func uses(in d: T.ID) -> [(NameExpr.ID, AccessEffect)] { var v = UseVisitor() walk(d, notifying: &v) return v.uses @@ -16,7 +16,7 @@ extension AST { } /// The state of the visitor gathering uses. -private struct UseVisitor: ASTWalkObserver { +struct UseVisitor: ASTWalkObserver { /// The names being used with their visibility. private(set) var uses: [(NameExpr.ID, AccessEffect)] = [] diff --git a/Sources/FrontEnd/TypeChecking/TypeChecker.swift b/Sources/FrontEnd/TypeChecking/TypeChecker.swift index 1a6d242cb..dc07b0923 100644 --- a/Sources/FrontEnd/TypeChecking/TypeChecker.swift +++ b/Sources/FrontEnd/TypeChecking/TypeChecker.swift @@ -2602,7 +2602,7 @@ struct TypeChecker { } // Look at uses to update conventions where we could only guess `let` from the context. - for (n, m) in program.ast.uses(in: AnyDeclID(program[e].decl)) { + for (n, m) in program.ast.uses(in: program[e].decl) { let candidates = lookup(unqualified: program[n].name.value.stem, in: program[n].scope) guard let pick = candidates.unique(ParameterDecl.self), @@ -2680,7 +2680,7 @@ struct TypeChecker { } /// Returns the implicit captures found in the body of `d`. - private mutating func implicitCaptures( + private mutating func implicitCaptures( of d: T.ID, ignoring explicitCaptures: Set ) -> [TupleType.Element] { // Only local declarations have captures. @@ -2690,7 +2690,7 @@ struct TypeChecker { } var captureToStemAndEffect: [AnyDeclID: (stem: String, effect: AccessEffect)] = [:] - for (name, mutability) in program.ast.uses(in: AnyDeclID(d)) { + for (name, mutability) in program.ast.uses(in: d) { guard let (stem, pick) = lookupImplicitCapture(name, occurringIn: d), !explicitCaptures.contains(stem) From 1a3c30f573c1f9642742550c6f661d5c4e7941a0 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 13 Feb 2024 22:25:29 +0100 Subject: [PATCH 4/5] Avoid revisiting explicit captures to detect implicit ones --- Sources/FrontEnd/AST+UseCollection.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/FrontEnd/AST+UseCollection.swift b/Sources/FrontEnd/AST+UseCollection.swift index e8ea354ac..c4929a493 100644 --- a/Sources/FrontEnd/AST+UseCollection.swift +++ b/Sources/FrontEnd/AST+UseCollection.swift @@ -9,14 +9,16 @@ extension AST { /// pre-order. Nested type and extension declarations are not visited. func uses(in d: T.ID) -> [(NameExpr.ID, AccessEffect)] { var v = UseVisitor() - walk(d, notifying: &v) + for s in self[d].sourcesOfImplicitCaptures { + walk(s, notifying: &v) + } return v.uses } } /// The state of the visitor gathering uses. -struct UseVisitor: ASTWalkObserver { +private struct UseVisitor: ASTWalkObserver { /// The names being used with their visibility. private(set) var uses: [(NameExpr.ID, AccessEffect)] = [] From f1da9820cd25b113a770d1d962c7a01bb22be902 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 13 Feb 2024 22:27:43 +0100 Subject: [PATCH 5/5] Test explicit captures --- .../EndToEndTests/TestCases/ExplicitCaptures.hylo | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Tests/EndToEndTests/TestCases/ExplicitCaptures.hylo diff --git a/Tests/EndToEndTests/TestCases/ExplicitCaptures.hylo b/Tests/EndToEndTests/TestCases/ExplicitCaptures.hylo new file mode 100644 index 000000000..35d28e3b7 --- /dev/null +++ b/Tests/EndToEndTests/TestCases/ExplicitCaptures.hylo @@ -0,0 +1,15 @@ +//- compileAndRun expecting: success + +fun apply(_ f: [E]() -> Int) -> Int { + f() +} + +public fun main() { + var local_variable = 0 + let p = mutable_pointer[to: &local_variable] + let n = apply(fun[sink let q = p.copy()]() -> Int { + &(q.unsafe[]) = 19 + return 19 + }) + precondition(n == local_variable) +}