Repository: hylo-lang/hylo (swift)

Issue:
Invalid detection of trivially deinitializable types
on the requirements-machine branch,

```
type X: Deinitializable {
  public var a: Int
  public memberwise init
  public fun deinit() sink { print("mort") }
}

fun eat(_ x: sink X) {
//  x.deinit()
}

public fun main() {
  let y = X(a: 0)
  eat(y)
}
```

This prints nothing.  I thought that `deinit()` would be called implicitly on an unconsumed `sink` parameter, but clearly is not.  I think it _should_ be called implicitly, but if you don't do that we have to force the user to explicitly consume all `sink` parameters.

Pull request description:
Fixes #1500 and one of the bugs reported by #1499.

Patch:
diff --git a/Sources/FrontEnd/AST/Decl/FunctionDecl.swift b/Sources/FrontEnd/AST/Decl/FunctionDecl.swift
index aa2bd686f..0e50cb0fb 100644
--- a/Sources/FrontEnd/AST/Decl/FunctionDecl.swift
+++ b/Sources/FrontEnd/AST/Decl/FunctionDecl.swift
@@ -104,6 +104,9 @@ public struct FunctionDecl: CapturingDecl, ExposableDecl, GenericDecl {
   /// `true` iff `self` denotes a `sink` member function.
   public var isSink: Bool { receiverEffect?.value == .sink }
 
+  /// `true` iff `self` denotes a deinitializer.
+  public var isDeinit: Bool { (identifier?.value == "deinit") && isSink && parameters.isEmpty }
+
   /// `true` iff `self` is a foreign function interface.
   public var isForeignInterface: Bool {
     api.contains(.isForeignInterface)
diff --git a/Sources/FrontEnd/BuiltinFunction.swift b/Sources/FrontEnd/BuiltinFunction.swift
index cf5896ecc..7ca0db7f9 100644
--- a/Sources/FrontEnd/BuiltinFunction.swift
+++ b/Sources/FrontEnd/BuiltinFunction.swift
@@ -14,6 +14,10 @@ public struct BuiltinFunction: Hashable {
       let p = ParameterType(.let, ^freshVariable())
       return .init(inputs: [.init(label: "of", type: ^p)], output: .builtin(.ptr))
 
+    case .markUninitialized:
+      let p = ParameterType(.let, ^freshVariable())
+      return .init(inputs: [.init(label: nil, type: ^p)], output: .void)
+
     case .llvm(let s):
       return s.type
     }
@@ -37,6 +41,11 @@ extension BuiltinFunction {
     /// measures may be needed to keep the argument alive during the pointer's use.
     case addressOf
 
+    /// `Builtin.mark_uninitialized<T>(_ v: sink T) -> Void`
+    ///
+    /// Marks `v` as being uninitialized.
+    case markUninitialized
+
   }
 
 }
@@ -49,6 +58,8 @@ extension BuiltinFunction.Name: CustomStringConvertible {
       return n.description
     case .addressOf:
       return "address(of:)"
+    case .markUninitialized:
+      return "mark_uninitialized(_:)"
     }
   }
 
@@ -63,6 +74,8 @@ extension BuiltinFunction {
     switch n {
     case "address":
       self.init(name: .addressOf)
+    case "mark_uninitialized":
+      self.init(name: .markUninitialized)
     default:
       self.init(native: n)
     }
diff --git a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift
index e7f6b917f..2a9d418d8 100644
--- a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift
+++ b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift
@@ -129,7 +129,8 @@ extension Module {
         if p.isEmpty { break }
 
         insertDeinit(
-          s.source, at: p, anchoredTo: s.site, before: i, reportingDiagnosticsTo: &diagnostics)
+          s.source, at: p, before: i,
+          anchoringInstructionsTo: s.site, reportingDiagnosticsTo: &diagnostics)
         o.value = .full(.uninitialized)
         context.forEachObject(at: s.source, { $0 = o })
 
@@ -251,8 +252,8 @@ extension Module {
 
       case .sink:
         insertDeinit(
-          s.start, at: projection.value.initializedSubfields, anchoredTo: s.site, before: i,
-          reportingDiagnosticsTo: &diagnostics)
+          s.start, at: projection.value.initializedSubfields, before: i,
+          anchoringInstructionsTo: s.site, reportingDiagnosticsTo: &diagnostics)
         context.withObject(at: l, { $0.value = .full(.uninitialized) })
 
       case .yielded:
@@ -305,8 +306,8 @@ extension Module {
       // erasing the deallocated memory from the context.
       let p = context.withObject(at: l, \.value.initializedSubfields)
       insertDeinit(
-        s.location, at: p, anchoredTo: s.site, before: i,
-        reportingDiagnosticsTo: &diagnostics)
+        s.location, at: p, before: i,
+        anchoringInstructionsTo: s.site, reportingDiagnosticsTo: &diagnostics)
       context.memory[l] = nil
       return successor(of: i)
     }
@@ -453,10 +454,17 @@ extension Module {
     func interpret(return i: InstructionID, in context: inout Context) -> PC? {
       // Make sure that all non-sink parameters are initialized on exit.
       let entry = entry(of: f)!
-      for (i, p) in self[f].inputs.enumerated() where p.type.access != .sink {
-        ensureInitializedOnExit(
-          .parameter(entry, i), passed: p.type.access, in: &context,
-          reportingDiagnosticsAt: diagnosticSite(for: p, in: f))
+      for (k, p) in self[f].inputs.enumerated() {
+        let source = Operand.parameter(entry, k)
+        if p.type.access == .sink {
+          ensureUninitializedOnExit(
+            source, in: &context, insertingDeinitializationBefore: i,
+            reportingDiagnosticsAt: self[i].site)
+        } else {
+          ensureInitializedOnExit(
+            source, passed: p.type.access, in: &context,
+            reportingDiagnosticsAt: diagnosticSite(for: p, in: f))
+        }
       }
 
       // Make sure that the return value is initialized on exit.
@@ -581,6 +589,30 @@ extension Module {
       }
     }
 
+    /// Checks that entry parameter `p` is deinitialized in `context`, inserting definitialization
+    /// before instruction `i` if it isn't, reporting diagnostics at `site`.
+    func ensureUninitializedOnExit(
+      _ p: Operand, in context: inout Context,
+      insertingDeinitializationBefore i: InstructionID,
+      reportingDiagnosticsAt site: SourceRange
+    ) {
+      context.withObject(at: .root(p), { (o) in
+        let s = o.value.initializedSubfields
+        if s == [[]] && isDeinit(i.function) {
+          // We cannot call `deinit` in `deinit` itself.
+          insertDeinitParts(
+            of: p, before: i,
+            anchoringInstructionsTo: site, reportingDiagnosticsTo: &diagnostics)
+        } else {
+          insertDeinit(
+            p, at: s, before: i,
+            anchoringInstructionsTo: site, reportingDiagnosticsTo: &diagnostics)
+        }
+
+        o.value = .full(.uninitialized)
+      })
+    }
+
     /// Checks that the return value is initialized in `context`.
     func ensureReturnValueIsInitialized(
       in context: inout Context, at site: SourceRange
@@ -637,8 +669,8 @@ extension Module {
 
       case .sink:
         insertDeinit(
-          start, at: projection.value.initializedSubfields, anchoredTo: self[exit].site,
-          before: exit, reportingDiagnosticsTo: &diagnostics)
+          start, at: projection.value.initializedSubfields, before: exit,
+          anchoringInstructionsTo: self[exit].site, reportingDiagnosticsTo: &diagnostics)
         context.withObject(at: l, { $0.value = .full(.uninitialized) })
 
       case .yielded:
@@ -720,8 +752,9 @@ extension Module {
   /// Inserts IR for the deinitialization of `root` at given `initializedSubfields` before
   /// instruction `i`, anchoring instructions to `site`.
   private mutating func insertDeinit(
-    _ root: Operand, at initializedSubfields: [RecordPath], anchoredTo site: SourceRange,
-    before i: InstructionID, reportingDiagnosticsTo log: inout DiagnosticSet
+    _ root: Operand, at initializedSubfields: [RecordPath], before i: InstructionID,
+    anchoringInstructionsTo site: SourceRange,
+    reportingDiagnosticsTo log: inout DiagnosticSet
   ) {
     for path in initializedSubfields {
       Emitter.withInstance(insertingIn: &self, reportingDiagnosticsTo: &log) { (e) in
@@ -732,6 +765,19 @@ extension Module {
     }
   }
 
+  /// Inserts ID for the deinitialization of `whole`'s parts before instruction `i`, anchoring
+  /// new instructions to `site`.
+  private mutating func insertDeinitParts(
+    of whole: Operand, before i: InstructionID,
+    anchoringInstructionsTo site: SourceRange,
+    reportingDiagnosticsTo log: inout DiagnosticSet
+  ) {
+    Emitter.withInstance(insertingIn: &self, reportingDiagnosticsTo: &log) { (e) in
+      e.insertionPoint = .before(i)
+      e.emitDeinitParts(of: whole, at: site)
+    }
+  }
+
   /// Returns the site at which diagnostics related to the parameter `p` should be reported in `f`.
   private func diagnosticSite(for p: Parameter, in f: Function.ID) -> SourceRange {
     guard let d = p.decl else { return .empty(at: self[f].site.start) }
diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift
index e50d53ee8..773f5753c 100644
--- a/Sources/IR/Emitter.swift
+++ b/Sources/IR/Emitter.swift
@@ -2112,6 +2112,11 @@ struct Emitter {
     case .addressOf:
       let source = emitLValue(arguments[0].value)
       return insert(module.makeAddressToPointer(source, at: site))!
+
+    case .markUninitialized:
+      let source = emitLValue(arguments[0].value)
+      insert(module.makeMarkState(source, initialized: false, at: site))
+      return .void
     }
   }
 
@@ -2994,7 +2999,7 @@ struct Emitter {
 
   /// If `storage` is deinitializable in `self.insertionScope`, inserts the IR for deinitializing
   /// it; reports a diagnostic for each part that isn't deinitializable otherwise.
-  private mutating func emitDeinitParts(of storage: Operand, at site: SourceRange) {
+  mutating func emitDeinitParts(of storage: Operand, at site: SourceRange) {
     let t = module.type(of: storage).ast
 
     if program.isTriviallyDeinitializable(t, in: insertionScope!) {
diff --git a/Sources/IR/Module.swift b/Sources/IR/Module.swift
index a19a0c216..358c65800 100644
--- a/Sources/IR/Module.swift
+++ b/Sources/IR/Module.swift
@@ -179,6 +179,20 @@ public struct Module {
     return d.dominates(lhs.block, rhs.block)
   }
 
+  /// Returns `true` if `i` is a deinitializer.
+  public func isDeinit(_ i: Function.ID) -> Bool {
+    switch i.value {
+    case .lowered(let d):
+      return FunctionDecl.ID(d).map({ (n) in program.ast[n].isDeinit }) ?? false
+    case .existentialized(let j):
+      return isDeinit(j)
+    case .monomorphized(let j, arguments: _):
+      return isDeinit(j)
+    case .synthesized(let d):
+      return d.kind == .deinitialize
+    }
+  }
+
   /// Returns whether the IR in `self` is well-formed.
   ///
   /// Use this method as a sanity check to verify the module's invariants.
@@ -507,12 +521,19 @@ public struct Module {
 
   /// Returns the lowered declarations of `d`'s parameters.
   private func loweredParameters(of d: FunctionDecl.ID) -> [Parameter] {
-    let captures = ArrowType(program[d].type)!.captures.lazy.map { (e) in
+    let declType = ArrowType(program[d].type)!
+    let captures = declType.captures.lazy.map { (e) in
       program.canonical(e.type, in: program[d].scope)
     }
+
     var result: [Parameter] = zip(program.captures(of: d), captures).map({ (c, e) in
-      .init(c, capturedAs: e)
+      if let t = RemoteType(e) {
+        return Parameter(decl: c, type: ParameterType(t))
+      } else {
+        return Parameter(decl: c, type: ParameterType(declType.receiverEffect, e))
+      }
     })
+
     result.append(contentsOf: program.ast[d].parameters.map(pairedWithLoweredType(parameter:)))
     return result
   }
@@ -537,11 +558,17 @@ public struct Module {
 
   /// Returns the lowered declarations of `d`'s parameters.
   private func loweredParameters(of d: SubscriptImpl.ID) -> [Parameter] {
-    let captures = SubscriptImplType(program[d].type)!.captures.lazy.map { (e) in
+    let declType = SubscriptImplType(program[d].type)!
+    let captures = declType.captures.lazy.map { (e) in
       program.canonical(e.type, in: program[d].scope)
     }
+
     var result: [Parameter] = zip(program.captures(of: d), captures).map({ (c, e) in
-      .init(c, capturedAs: e)
+      if let t = RemoteType(e) {
+        return Parameter(decl: c, type: ParameterType(t))
+      } else {
+        return Parameter(decl: c, type: ParameterType(declType.receiverEffect, e))
+      }
     })
 
     let bundle = SubscriptDecl.ID(program[d].scope)!
diff --git a/Sources/IR/Parameter.swift b/Sources/IR/Parameter.swift
index f33a0b549..b8bbd50d4 100644
--- a/Sources/IR/Parameter.swift
+++ b/Sources/IR/Parameter.swift
@@ -25,7 +25,7 @@ public struct Parameter {
     case let u as RemoteType:
       self.init(decl: d, type: ParameterType(u))
     default:
-      self.init(decl: d, type: ParameterType(.sink, t))
+      self.init(decl: d, type: ParameterType(.inout, t))
     }
   }
 
diff --git a/StandardLibrary/Sources/Concurrency/Future.hylo b/StandardLibrary/Sources/Concurrency/Future.hylo
index 900adca69..85b263868 100644
--- a/StandardLibrary/Sources/Concurrency/Future.hylo
+++ b/StandardLibrary/Sources/Concurrency/Future.hylo
@@ -1,5 +1,5 @@
 /// A future that cannot escape the local scope.
-public type Future<E: Movable & Deinitializable> {
+public type Future<E: Movable & Deinitializable>: Deinitializable {
 
   // TODO
   public typealias Result = Int
diff --git a/StandardLibrary/Sources/Core/Bitcast.hylo b/StandardLibrary/Sources/Core/Bitcast.hylo
index 1dd57d900..51909d734 100644
--- a/StandardLibrary/Sources/Core/Bitcast.hylo
+++ b/StandardLibrary/Sources/Core/Bitcast.hylo
@@ -14,5 +14,6 @@ public subscript unsafe_bitcast<T, U>(_ value: T): U {
 /// Returns `value` with its memory representation reinterpreted as a value of type `U`.
 public fun unsafe_bitcast<T, U: Movable>(consuming value: sink T) -> U {
   sink let p: PointerToMutable<U> = PointerToMutable(type_punning: mutable_pointer[to: &value])
+  Builtin.mark_uninitialized(value)
   return p.unsafe_pointee()
 }
diff --git a/StandardLibrary/Sources/Core/String/UTF8Array.hylo b/StandardLibrary/Sources/Core/String/UTF8Array.hylo
index 599eadb1d..f579b2067 100644
--- a/StandardLibrary/Sources/Core/String/UTF8Array.hylo
+++ b/StandardLibrary/Sources/Core/String/UTF8Array.hylo
@@ -168,7 +168,9 @@ public type UTF8Array: Regular {
     &self = with_extended_lifetime(
       nullterminated,
       do: fun[sink let c = count(), sink let k = new_capacity.copy()] (_ source) {
-        UTF8Array(unsafely_copying: PointerToBuffer(start: source.copy(), count: c), reserving: k)
+        UTF8Array(
+          unsafely_copying: PointerToBuffer(start: source.copy(), count: c.copy()),
+          reserving: k)
       })
   }
 


Test patch:
diff --git a/Tests/HyloTests/TestCases/Lowering/DI.hylo b/Tests/HyloTests/TestCases/Lowering/DI.hylo
index 156c1ec41..49638afb6 100644
--- a/Tests/HyloTests/TestCases/Lowering/DI.hylo
+++ b/Tests/HyloTests/TestCases/Lowering/DI.hylo
@@ -4,7 +4,7 @@ fun read<T>(_ x: T) {}
 
 fun modify<T>(_ x: inout T) {}
 
-fun consume<T>(_ x: sink T) {}
+fun consume<T: Deinitializable>(_ x: sink T) {}
 
 // fun assign<T>(_ x: set T, to y: sink T) { &x = y }
 
diff --git a/Tests/HyloTests/TestCases/Lowering/MissingDeinit.hylo b/Tests/HyloTests/TestCases/Lowering/MissingDeinit.hylo
new file mode 100644
index 000000000..dd539bffb
--- /dev/null
+++ b/Tests/HyloTests/TestCases/Lowering/MissingDeinit.hylo
@@ -0,0 +1,10 @@
+//- lowerToFinishedIR expecting: failure
+
+type A {
+  public memberwise init
+}
+
+//! @+1 diagnostic type 'A' does not conform to trait 'Deinitializable'
+fun f(_ a: sink A) {}
+
+public fun main() { f(A()) }