// RUN: %target-swiftc_driver -O -Rpass-missed=sil-opt-remark-gen -Xllvm -sil-disable-pass=FunctionSignatureOpts -emit-sil %s -o /dev/null -Xfrontend -verify
// REQUIRES: optimized_stdlib,swift_stdlib_no_asserts

public class Klass {}

// TODO: Change global related code to be implicit/autogenerated (as
// appropriate) so we don't emit this remark.
public var global = Klass() // expected-remark {{heap allocated ref of type 'Klass'}}

@inline(never)
public func getGlobal() -> Klass {
    return global // expected-remark @:5 {{retain of type 'Klass'}}
                  // expected-note @-5:12 {{of 'global'}}
}

// Make sure that the retain msg is at the beginning of the print and the
// releases are the end of the print.
//
// The heap allocated ref is for the temporary array and the release that is
// unannotated is for that array as well. We make sure that the heap allocated
// ref is on the argument that necessitated its creation.
public func useGlobal() {
    let x = getGlobal()
    print(x) // expected-remark @:11 {{heap allocated ref of type}}
             // expected-remark @-1:5 {{retain of type 'Klass'}}
             // expected-note @-3:9 {{of 'x'}}
             // expected-remark @-3:12 {{release of type}}
             // expected-remark @-4:12 {{release of type 'Klass'}}
             // expected-note @-6:9 {{of 'x'}}
}

public enum TrivialState {
case first
case second
case third
}

struct StructWithOwner {
    var owner = Klass()
    var state = TrivialState.first
}

func printStructWithOwner(x : StructWithOwner) {
    print(x) // expected-remark @:11 {{heap allocated ref of type}}
             // expected-remark @-1 {{retain of type 'Klass'}}
             // expected-note @-3:27 {{of 'x.owner'}}
             // expected-remark @-3:12 {{release of type}}
}

func printStructWithOwnerOwner(x : StructWithOwner) {
    print(x.owner) // expected-remark @:11 {{heap allocated ref of type}}
                   // expected-remark @-1 {{retain of type 'Klass'}}
                   // expected-note @-3:32 {{of 'x.owner'}}
                   // expected-remark @-3:18 {{release of type}}
}

func returnStructWithOwnerOwner(x: StructWithOwner) -> Klass {
    return x.owner // expected-remark {{retain of type 'Klass'}}
                   // expected-note @-2:33 {{of 'x.owner'}}
}

func callingAnInitializerStructWithOwner(x: Klass) -> StructWithOwner {
    return StructWithOwner(owner: x) // expected-remark {{retain of type 'Klass'}}
                                     // expected-note @-2:42 {{of 'x'}}
}

struct KlassPair {
    var lhs: Klass
    var rhs: Klass
}

func printKlassPair(x : KlassPair) {
    // We pattern match columns to ensure we get retain on the p and release on
    // the end ')'
    print(x) // expected-remark @:11 {{heap allocated ref of type}}
             // expected-remark @-1:5 {{retain of type 'Klass'}}
             // expected-note @-5:21 {{of 'x.lhs'}}
             // expected-remark @-3:5 {{retain of type 'Klass'}}
             // expected-note @-7:21 {{of 'x.rhs'}}
             // expected-remark @-5:12 {{release of type}}
}

func printKlassPairLHS(x : KlassPair) {
    // We print the remarks at the 'p' and at the ending ')'.
    print(x.lhs) // expected-remark @:11 {{heap allocated ref of type}}
                 // expected-remark @-1:5 {{retain of type 'Klass'}}
                 // expected-note @-4:24 {{of 'x.lhs'}}
                 // expected-remark @-3:16 {{release of type}}
}

// We put the retain on the return here since it is part of the result
// convention.
func returnKlassPairLHS(x: KlassPair) -> Klass {
    return x.lhs // expected-remark @:5 {{retain of type 'Klass'}}
                 // expected-note @-2:25 {{of 'x.lhs'}}
}

func callingAnInitializerKlassPair(x: Klass, y: Klass) -> KlassPair {
    return KlassPair(lhs: x, rhs: y) // expected-remark {{retain of type 'Klass'}}
                                     // expected-note @-2:36 {{of 'x'}}
                                     // expected-remark @-2:5 {{retain of type 'Klass'}}
                                     // expected-note @-4:46 {{of 'y'}}
}

func printKlassTuplePair(x : (Klass, Klass)) {
    // We pattern match columns to ensure we get retain on the p and release on
    // the end ')'
    print(x) // expected-remark @:11 {{heap allocated ref of type}}
             // expected-remark @-1:5 {{retain of type 'Klass'}}
             // expected-note @-5:26 {{of 'x'}}
             // expected-remark @-3:5 {{retain of type 'Klass'}}
             // expected-note @-7:26 {{of 'x'}}
             // expected-remark @-5:12 {{release of type}}
}

func printKlassTupleLHS(x : (Klass, Klass)) {
    // We print the remarks at the 'p' and at the ending ')'.
    print(x.0) // expected-remark @:11 {{heap allocated ref of type}}
               // expected-remark @-1:5 {{retain of type 'Klass'}}
               // expected-note @-4:25 {{of 'x'}}
               // Release on Array<Any> for print.
               // expected-remark @-4:14 {{release of type}}
}

func returnKlassTupleLHS(x: (Klass, Klass)) -> Klass {
    return x.0 // expected-remark @:5 {{retain of type 'Klass'}}
               // expected-note @-2:26 {{of 'x'}}
}

func callingAnInitializerKlassTuplePair(x: Klass, y: Klass) -> (Klass, Klass) {
    return (x, y) // expected-remark {{retain of type 'Klass'}}
                  // expected-note @-2:41 {{of 'x'}}
                  // expected-remark @-2:5 {{retain of type 'Klass'}}
                  // expected-note @-4:51 {{of 'y'}}
}

public class SubKlass : Klass {
    @inline(never)
    final func doSomething() {}
}

func lookThroughCast(x: SubKlass) -> Klass {
    return x as Klass // expected-remark {{retain of type 'SubKlass'}}
                      // expected-note @-2:22 {{of 'x'}}
}

func lookThroughRefCast(x: Klass) -> SubKlass {
    return x as! SubKlass // expected-remark {{retain of type 'Klass'}}
                          // expected-note @-2:25 {{of 'x'}}
}

func lookThroughEnum(x: Klass?) -> Klass {
    return x! // expected-remark {{retain of type 'Klass'}}
              // expected-note @-2:22 {{of 'x.some'}}
}

func castAsQuestion(x: Klass) -> SubKlass? {
    x as? SubKlass // expected-remark {{retain of type 'Klass'}}
                   // expected-note @-2:21 {{of 'x'}}
}

func castAsQuestionDiamond(x: Klass) -> SubKlass? {
    guard let y = x as? SubKlass else {
        return nil
    }

    y.doSomething()
    return y // expected-remark {{retain of type 'Klass'}}
             // expected-note @-7:28 {{of 'x'}}
}

func castAsQuestionDiamondGEP(x: KlassPair) -> SubKlass? {
    guard let y = x.lhs as? SubKlass else {
        return nil
    }

    y.doSomething()
    // We eliminate the rhs retain/release.
    return y // expected-remark {{retain of type 'Klass'}}
             // expected-note @-8:31 {{of 'x.lhs'}}
}

// We don't handle this test case as well.
func castAsQuestionDiamondGEP2(x: KlassPair) {
    switch (x.lhs as? SubKlass, x.rhs as? SubKlass) { // expected-remark @:19 {{retain of type 'Klass'}}
                                                      // expected-note @-2 {{of 'x.lhs'}}
                                                      // expected-remark @-2:39 {{retain of type 'Klass'}}
                                                      // expected-note @-4 {{of 'x.rhs'}}
    case let (.some(x1), .some(x2)):
        print(x1, x2) // expected-remark @:15 {{heap allocated ref of type}}
                      // expected-remark @-1 {{retain of type 'Optional<SubKlass>'}}
                      // expected-remark @-2 {{retain of type 'Optional<SubKlass>'}}
                      // expected-remark @-3 {{release of type}}
                      // expected-remark @-4 {{release of type 'Optional<SubKlass>'}}
                      // expected-remark @-5 {{release of type 'Optional<SubKlass>'}}
    case let (.some(x1), nil):
        print(x1) // expected-remark @:15 {{heap allocated ref of type}}
                  // expected-remark @-1 {{retain of type 'SubKlass'}}
                  // expected-remark @-2 {{release of type}}
                  // expected-remark @-3 {{release of type 'Optional<SubKlass>'}}
    case let (nil, .some(x2)):
        print(x2) // expected-remark @:15 {{heap allocated ref of type}}
                  // expected-remark @-1 {{retain of type 'SubKlass'}}
                  // expected-remark @-2 {{release of type}}
                  // expected-remark @-3 {{release of type 'Optional<SubKlass>'}}
    case (nil, nil):
        break
    }
}

func inoutKlassPairArgument(x: inout KlassPair) -> Klass {
    return x.lhs // expected-remark {{retain of type 'Klass'}}
                 // expected-note @-2 {{of 'x.lhs'}}
}

func inoutKlassTuplePairArgument(x: inout (Klass, Klass)) -> Klass {
    return x.0 // expected-remark {{retain of type 'Klass'}}
               // expected-note @-2 {{of 'x.0'}}
}

func inoutKlassOptionalArgument(x: inout Klass?) -> Klass {
    return x! // expected-remark {{retain of type 'Klass'}}
              // expected-note @-2 {{of 'x.some'}}
}

func inoutKlassBangCastArgument(x: inout Klass) -> SubKlass {
    return x as! SubKlass // expected-remark {{retain of type 'Klass'}}
                          // expected-note @-2 {{of 'x'}}
}

func inoutKlassQuestionCastArgument(x: inout Klass) -> SubKlass? {
    return x as? SubKlass // expected-remark {{retain of type 'Klass'}}
                          // expected-note @-2 {{of 'x'}}
}

func inoutKlassBangCastArgument2(x: inout Klass?) -> SubKlass {
    return x as! SubKlass // expected-remark {{retain of type 'Klass'}}
                          // expected-note @-2 {{of 'x.some'}}
}

func inoutKlassQuestionCastArgument2(x: inout Klass?) -> SubKlass? {
    return x as? SubKlass // expected-remark {{retain of type 'Klass'}}
                          // expected-note @-2 {{of 'x.some'}}
}

// We should have 1x rr remark here on calleeX for storing it into the array to
// print. Release is from the array. We don't pattern match it due to the actual
// underlying Array type name changing under the hood in between platforms.
@inline(__always)
func alwaysInlineCallee(_ calleeX: Klass) {
    print(calleeX) // expected-remark @:11 {{heap allocated ref of type}}
                   // expected-remark @-1:5 {{retain of type 'Klass'}}
                   // expected-note @-3:27 {{of 'calleeX'}}
                   // expected-remark @-3:18 {{release of type}}
}

// We should have 3x rr remarks here on callerX and none on calleeX.  All of the
// releases are for the temporary array that we pass into print.
//
// TODO: Should we print out as notes the whole inlined call stack?
func alwaysInlineCaller(_ callerX: Klass) {
    alwaysInlineCallee(callerX) // expected-remark @:5 {{heap allocated ref of type}}
                                // expected-remark @-1:5 {{retain of type 'Klass'}}
                                // expected-note @-3:27 {{of 'callerX'}}
                                // expected-remark @-3:31 {{release of type}}
    print(callerX)              // expected-remark @:11 {{heap allocated ref of type}}
                                // expected-remark @-1:5 {{retain of type 'Klass'}}
                                // expected-note @-7:27 {{of 'callerX'}}
                                // expected-remark @-3:18 {{release of type}}
    alwaysInlineCallee(callerX) // expected-remark @:5 {{heap allocated ref of type}}
                                // expected-remark @-1:5 {{retain of type 'Klass'}}
                                // expected-note @-11:27 {{of 'callerX'}}
                                // expected-remark @-3:31 {{release of type}}
}

func allocateValue() {
    // Remark should be on Klass and note should be on k.
    let k = Klass() // expected-remark @:13 {{heap allocated ref of type 'Klass'}}
                    // expected-note @-1:9 {{of 'k'}}
    print(k)        // expected-remark @:11 {{heap allocated ref of type}}
                    // expected-remark @-1:5 {{retain of type 'Klass'}}
                    // expected-note @-4:9 {{of 'k'}}
                    // expected-remark @-3:12 {{release of type}}
                    // expected-remark @-4:12 {{release of type 'Klass'}}
                    // expected-note @-7:9 {{of 'k'}}
}
