Skip to content

Changing the SRTP of an inline operator in the same expression results in compile error. #7917

Closed
@teo-tsirpanis

Description

@teo-tsirpanis

Today, I encountered a strange compiler error while working with operators that use SRTPs. It can be best explained by the following snippet I created:

type MyString = MyString of string
with
  member x.Append(s) =
    let (MyString x) = x
    x + s |> MyString
  member x.Append(d) = x.Append(sprintf "%d" d)
  member x.FinalAppend(s: string) =
    let (MyString x) = x.Append(s)
    x

type MyOtherString = MyOtherString of string
with
  member x.Append(s) =
    let (MyOtherString x) = x
    x + s |> MyOtherString
  member x.Append(d) = x.Append(sprintf "%f" d)
  member x.FinalAppend(s: string) =
    let (MyOtherString x) = x.Append(s)
    x

// This function is generic.
// It performs ms.Append(x),
// for any two values whose
// types have this member.
let inline (.>>) ms x = (^TMyString : (member Append: ^TMyOperand -> ^TMyString) (ms, x))

// These functions are _not_ generic.
let inline (=%) (ms: MyString) x = ms.FinalAppend x
let inline (=@) (ms: MyOtherString) x = ms.FinalAppend x

// These examples work.
// The type at the left of .>> is always the same.
let works1 = MyString "This" .>> " chain" .>> 6 =% " works."
let works2 = MyOtherString "Working" .>> " as" .>> 6.0 =@ " well."

// Removing the final operator
// however causes errors as soon
// as we use a type different than
// the string we were using until now...
let notworks = MyString "Doesn't" .>> " work." .>> 6

At the final example, we omit the =% operator and it should return the MyString object directly. However, the compiler expects the 6 to have been a string, instead of an int.

At the first example, the =% operator at the end makes sure that the left operand is a MyString, the right operand is a string, and the result is a string as well.

The question is: Didn't the compiler already know that already? Let's take a look at his deductive process:

  • MyString "Doesn't" is surely of type MyString.
  • MyString "Doesn't" .>> " work" must be a MyString, from the definition of .>>.
  • MyString "Doesn't" .>> " work." .>> 6, is again a MyString. The final .>> should resolve to MyString.Append(int), but our compiler falsely resolves it to MyString.Append(string) instead.

Using .>> like that does not seem to be prohibited; it works above, when we used the =% operator, and made the compiler feel safe that he was dealing with an object of type MyString.

I made a couple of theories about this bug:

  • In ^TMyString : (member Append: ^TMyOperand -> ^TMyString), don't these ^TMyStrings refer to the same type? Doesn't that guarantee that the method's return type is the same with the type the method resides?

  • let x = MyString "Doesn't" .>> " work" in x .>> 6 works, which led me to wonder if the compiler mistakenly believes that .>>'s ^TMyOperand must remain the same in the entire let block. But this does does not hold, since there is no problem when we added =%.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugImpact-Low(Internal MS Team use only) Describes an issue with limited impact on existing code.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions