Help with generating valid generic function in the AST level

I'm working on issue SR-12397.

Basically, when evaluating a value's type in a generic context, the substituted type is always used, instead of the generic one.

For example:

func f<T>(_ foo: T) {
    let test = foo != nil // breakpoint here
    print(test)
}
let x: Int? = nil
f(x)

For the code above, test evaluates to true, while evaluating the same expr in LLDB results in false. This seems to happen because the compiler injects T into an optional (T?), but by the time we evaluate the expression in LLDB, the type of foo is already resolved to Int? so this does not occur.

My first attempted solution was trying to create a new inner function and pass the arguments that were originally generic as generic once again. So for the example above, we'd have something more or less like:

func lldb_inner<T>(foo: T) -> Bool {
    return foo != nil
}

func lldb_expr() {
    // foo has type Int? here
    lldb_inner(foo: foo)
}

I managed to write code that more or less generates this in the AST, but now I'm getting this error when type-checking the expression:

error: <EXPR>:3:5: error: type of expression is ambiguous without more context
foo != nil
~~~~^~~~~~

Dumping SolutionApplicationTarget as an Expr prints:

(binary_expr type='<null>'
  (overloaded_decl_ref_expr type='<null>' name=!= number_of_decls=17 function_ref=unapplied decls=[
    Swift.(file).!=,
    Swift.(file).!=,
    Swift.(file).!=,
    Swift.(file).!=,
    Swift.(file).!=,
    Swift.(file).!=,
    Swift.(file).!=,
    Swift.(file).!=,
    Swift.(file).!=,
    Swift.(file).!=,
    Swift.(file).!=,
    Swift.(file).Equatable extension.!=,
    Swift.(file).BinaryInteger extension.!=,
    Swift.(file).Optional extension.!=,
    Swift.(file).Optional extension.!=,
    Swift.(file).BinaryInteger extension.!=,
    Swift.(file).StringProtocol extension.!=])
  (tuple_expr implicit type='<null>'
    (declref_expr implicit type='τ_0_0' location=<EXPR>:3:1 range=[<EXPR>:3:1 - line:3:1] decl=__lldb_expr_10.(file).$__lldb_inner_expr(test:foo:$τ_0_0:x:).foo function_ref=unapplied)
    (nil_literal_expr type='<null>' initializer=**NULL**)))

This seems to indicate that the constraint system can't figure out which version of != to apply.

Can someone help me understand what I'm doing wrong here? I'm posting the whole AST since I don't know which parts are relevant, so apologies for the long wall of text:

(source_file "<EXPR>"
  (func_decl range=[<EXPR>:1:1 - line:5:1] "$__lldb_inner_expr(test:foo:$τ_0_0:x:)" interface type='<τ_0_0> (test: Bool, foo: τ_0_0, $τ_0_0: Builtin.RawPointer, x: Optional<Int>) -> Bool' access=internal
    (parameter_list
      (parameter "test" apiName=test type='Bool' interface type='Bool')
      (parameter "foo" apiName=foo type='τ_0_0' interface type='τ_0_0')
      (parameter "$τ_0_0" apiName=$τ_0_0 type='Builtin.RawPointer' interface type='Builtin.RawPointer')
      (parameter "x" apiName=x type='Optional<Int>' interface type='Optional<Int>') range=[<EXPR>:1:82 - line:5:1])
    (result
      (type_ident
        (component id='Bool' bind=Swift.(file).Bool)))
    (sequence_expr type='<null>'
      (declref_expr implicit type='τ_0_0' location=<EXPR>:3:1 range=[<EXPR>:3:1 - line:3:1] decl=__lldb_expr_8.(file).$__lldb_inner_expr(test:foo:$τ_0_0:x:).foo function_ref=unapplied)
      (unresolved_decl_ref_expr type='<null>' name=!= function_ref=unapplied)
      (nil_literal_expr type='<null>' initializer=**NULL**)))
  (func_decl range=[<EXPR>:7:1 - line:15:1] "$__lldb_expr(_:)" access=internal
    (parameter_list
      (parameter "$__lldb_arg" type='UnsafeMutablePointer<Any>' interface type='UnsafeMutablePointer<Any>') range=[<EXPR>:7:18 - line:7:60])
    (brace_stmt range=[<EXPR>:7:62 - line:15:1]
      (var_decl implicit range=[<EXPR>:7:62 - line:7:62] "x" type='Optional<Int>' interface type='Optional<Int>' access=public let readImpl=stored immutable)

      (pattern_binding_decl implicit range=[<EXPR>:7:62 - line:7:62]
        (pattern_typed implicit type='Optional<Int>'
          (pattern_named implicit 'x')))

      (var_decl implicit range=[<EXPR>:7:62 - line:7:62] "$τ_0_0" type='Builtin.RawPointer' interface type='Builtin.RawPointer' access=public readImpl=stored writeImpl=stored readWriteImpl=stored)

      (pattern_binding_decl implicit range=[<EXPR>:7:62 - line:7:62]
        (pattern_typed implicit type='Builtin.RawPointer'
          (pattern_named implicit '$τ_0_0')))

      (var_decl implicit range=[<EXPR>:7:62 - line:7:62] "foo" type='Optional<Int>' interface type='Optional<Int>' access=public let readImpl=stored immutable)

      (pattern_binding_decl implicit range=[<EXPR>:7:62 - line:7:62]
        (pattern_typed implicit type='Optional<Int>'
          (pattern_named implicit 'foo')))

      (var_decl implicit range=[<EXPR>:7:62 - line:7:62] "test" type='Bool' interface type='Bool' access=public let readImpl=stored immutable)

      (pattern_binding_decl implicit range=[<EXPR>:7:62 - line:7:62]
        (pattern_typed implicit type='Bool'
          (pattern_named implicit 'test')))

      (do_catch_stmt range=[<EXPR>:8:1 - line:14:1]
        (brace_stmt range=[<EXPR>:8:4 - line:10:1]
          (repeat_while_stmt implicit range=[<EXPR>:9:1 - line:9:1]
            (brace_stmt implicit range=[<EXPR>:9:1 - line:9:1]
              (pattern_binding_decl implicit
                (pattern_named implicit '__lldb_tmp_ret_0')
                Processed init:
                (call_expr implicit type='<null>' arg_labels=test:foo:$τ_0_0:x:
                  (declref_expr implicit type='<τ_0_0> (test: Bool, foo: τ_0_0, $τ_0_0: Builtin.RawPointer, x: Optional<Int>) -> Bool' location=<EXPR>:1:1 range=[<EXPR>:1:1 - line:1:1] decl=__lldb_expr_8.(file).$__lldb_inner_expr(test:foo:$τ_0_0:x:)@<EXPR>:1:6 function_ref=unapplied)
                  (tuple_expr implicit type='(test: Bool, foo: Optional<Int>, $τ_0_0: Builtin.RawPointer, x: Optional<Int>)' location=<EXPR>:7:62 range=[<EXPR>:7:62 - line:7:62] names=test,foo,$τ_0_0,x
                    (declref_expr implicit type='Bool' location=<EXPR>:7:62 range=[<EXPR>:7:62 - line:7:62] decl=__lldb_expr_8.(file).$__lldb_expr(_:).test@<EXPR>:7:62 function_ref=unapplied)
                    (declref_expr implicit type='Optional<Int>' location=<EXPR>:7:62 range=[<EXPR>:7:62 - line:7:62] decl=__lldb_expr_8.(file).$__lldb_expr(_:).foo@<EXPR>:7:62 function_ref=unapplied)
                    (declref_expr implicit type='Builtin.RawPointer' location=<EXPR>:7:62 range=[<EXPR>:7:62 - line:7:62] decl=__lldb_expr_8.(file).$__lldb_expr(_:).$τ_0_0@<EXPR>:7:62 function_ref=unapplied)
                    (declref_expr implicit type='Optional<Int>' location=<EXPR>:7:62 range=[<EXPR>:7:62 - line:7:62] decl=__lldb_expr_8.(file).$__lldb_expr(_:).x@<EXPR>:7:62 function_ref=unapplied))))

              (var_decl implicit range=[<EXPR>:9:1 - line:9:1] "__lldb_tmp_ret_0" type='<null type>' access=internal let readImpl=stored immutable)
)
            (sequence_expr implicit type='<null>'
              (integer_literal_expr implicit type='<null>' value=1 builtin_initializer=**NULL** initializer=**NULL**)
              (unresolved_decl_ref_expr type='<null>' name=== function_ref=unapplied)
              (integer_literal_expr implicit type='<null>' value=0 builtin_initializer=**NULL** initializer=**NULL**))))
        (case_stmt range=[<EXPR>:11:1 - line:14:1]
          (case_body_variables
            (var_decl implicit range=[<EXPR>:11:12 - line:11:12] "__lldb_tmp_error" type='<null type>' let non_pattern_init readImpl=stored immutable)
          )
          (case_label_item
          (pattern_expr
            (paren_expr type='<null>'
              (unresolved_pattern_expr type='<null>'
                (pattern_let
                  (pattern_named '__lldb_tmp_error'))))))
          (brace_stmt range=[<EXPR>:12:1 - line:14:1]
            (pattern_binding_decl range=[<EXPR>:13:5 - line:13:32]
              (pattern_named '$__lldb_error_result')
              Original init:
              (declref_expr type='<null>' decl=__lldb_expr_8.(file).$__lldb_expr(_:).__lldb_tmp_error@<EXPR>:11:12 function_ref=unapplied)
              Processed init:
              (declref_expr type='<null>' decl=__lldb_expr_8.(file).$__lldb_expr(_:).__lldb_tmp_error@<EXPR>:11:12 function_ref=unapplied))

            (var_decl range=[<EXPR>:13:9 - line:13:9] "$__lldb_error_result" type='<null type>' readImpl=stored writeImpl=stored readWriteImpl=stored)
))))))

I also have a related question: would it be possible to specialize a generic function in the AST? This isn't legal in Swift, but could we do something like:

func lldb_expr<T where T == Int?>() {
   // replace foo:Int? by foo: T
}

@vedantk @Adrian_Prantl @hborla

cc @dcci

Copying _lldb_inner_exp into CE, it looks like "foo" becomes (parameter "foo" apiName=foo type='T' interface type='T'), whereas within the lldb expression evaluator it becomes (parameter "foo" apiName=foo type='τ_0_0' interface type='τ_0_0') (https://swift.godbolt.org/z/o9bpHi). Why does lldb need to pass in τ_0_0?

That might be a mistake on my part, I tried setting up these types before performTypeChecking is called in the hopes that would give enough information to infer the type of foo inside lldb_inner.

Terms of Service

Privacy Policy

Cookie Policy