Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC FS-1093] Additional type directed conversions #10884

Merged
merged 78 commits into from
Aug 13, 2021
Merged

Conversation

dsyme
Copy link
Contributor

@dsyme dsyme commented Jan 15, 2021

Implementation of RFC https://github.com/fsharp/fslang-design/blob/master/FSharp-6.0/FS-1093-additional-conversions.md

This primarily loosens the auto-widening rules in the presence of explicit type annotations or other "known" type information, e.g. to allow things like:

let x: obj = if true then 1 else 3.0

It also adds int32 --> int64 conversions and a limited form of op_Implicit support.

In this PR the rule is:

  • When processing an expression the "overall type" is usually labelled as "MustConvertTo" the known type, instead of "MustEqual"

  • This is propagated over if/then/else and other control structures. So if if then A then B must convert to T, then both A and B must convert to T, but A and B may have different types.

  • For leaf expressions, we often have immediate knowledge of both the source and MustConvertTo types. If a unification between the two would have failed then subsumption and adhoc conversions can be tried instead.

The adhoc conversions enabled are in the RFC.

This also allows (the removal of upcast coercions like asExpr on this line](https://github.com/dotnet/fsharp/blob/main/src/fsharp/FSharp.Core/Linq.fs#L282), e.g. in favour of one explicit type annotation.

A longer list of things enabled is below and in the RFC

let x1 : obj = (1,2)
let x2 : obj * obj = (1,2)
let x3 : (obj * obj) = (let x = 1 in let y = 2 in (x,y))
let x4 : struct (obj * obj) = struct (1,2)
let x5 : (unit -> obj) = (fun () -> 1)
let x6 : (unit -> obj * obj) = (fun () -> (1,2))
let x7 () : obj * obj = (1,2)
let x8 (s:string) : obj = s

type R = { mutable F1: (obj * obj) }

let annotations = 
    let _ : obj = (1,2)
    let _ : obj * obj = (1,2)

    // structure through let
    let _ : (obj * obj) = (let x = 1 in let y = 2 in (x,y))

    // structure through let rec
    let _ : (obj * obj) = (let rec f x = x in (3,4.0))

    // structure through sequence
    let _ : (obj * obj) = (); (3,4.0)

    // struct tuple
    let _ : struct (obj * obj) = struct (1,2)
    
    // record (both field and overall result)
    // let _ : obj = { F1 = (1, 2) } // TODO
    
    // record (both field and overall result)
    { F1 = (1, 2uy) }.F1 <- (3.0, 4)
    
    // anon record
    let _ : {| A: obj |} = {| A = 1 |}
    
    // lambda return
    let _ : (unit -> obj) = (fun () -> 1)

    // function lambda return
    let _ : (int -> obj) = (function 1 -> 1 | 2 -> 3.0 | _ -> 4uy)

    let _ : (unit -> obj * obj) = (fun () -> (1,2))

    // constants
    (1 :> System.IComparable) |> ignore

    let _ : System.IComparable = 1 

    // array constants
    let _ : System.Array = [| 1us |] 

    let _ : System.Array = [| 1I |] 

    let _ : System.IComparable = 1I
    
    // property
    let _ : System.IComparable<string> = System.String.Empty 
    
    // method
    let _ : System.IComparable<string> = System.String.Format("")

    let _ : obj = System.String.Format("")

    let _ : System.IComparable = System.String.Format("")
    
    // array constants

    let _ : obj[] = [| 1 |] 
    let _ : (obj * obj)[] = [| (1,1) |]
    let _ : (obj * obj)[] = [| ("abc",1) |]
    let _ : (string * obj)[] = [| ("abc",1) |]
    let _ : (string * obj)[] = [| ("abc",1); ("abc",3.0) |]
    let _ : (string * obj)[] = [| Unchecked.defaultof<_>; ("abc",3.0) |]
    let _ : struct (string * obj)[] = [| Unchecked.defaultof<_>; struct ("abc",3.0) |]

    let _ : obj = 1
    let _ : obj = ""
    let _ : obj = { new System.ICloneable with member x.Clone() = obj() }
    let _ : obj = ""
    let _ : obj = string ""
    let _ : obj = id ""
    
    // conditional
    let _ : obj = if true then 1 else 3.0
    let _ : obj = (if true then 1 else 3.0)
    let _ : obj = (if true then 1 elif true then 2uy else 3.0) 
    
    // try-with
    let _ : obj = try 1 with _ -> 3.0
    
    // try-finally
    let _ : obj = try 1 finally ()

    // match
    let _ : obj = match true with _ -> 1 | _ -> 3.0
    ()

let f1 () : obj = 1
let f2 () : obj = if true then 1 else 3.0

@dsyme
Copy link
Contributor Author

dsyme commented Jan 16, 2021

Test failures (all expected, at a first glance)

2021-01-16T17:04:27.7873329Z Conformance\Expressions\Type-relatedExpressions (E_rigidtypeannotation02.fs) -- failed
2021-01-16T17:04:27.7904419Z Conformance\Expressions\Type-relatedExpressions (E_rigidtypeannotation02b.fs) -- failed
2021-01-16T17:04:30.5798506Z Conformance\InferenceProcedures\ConstraintSolving (E_NoImplicitDowncast01.fs) -- failed
2021-01-16T17:06:47.8831078Z Conformance\ObjectOrientedTypeDefinitions\ClassTypes\Misc (E_ExplicitConstructor.fs) -- failed
2021-01-16T17:09:35.2129707Z Conformance\TypesAndTypeConstraints\CheckingSyntacticTypes (DefaultConstructorConstraint02.fs) -- failed

@charlesroddie
Copy link
Contributor

This would implement fsharp/fslang-suggestions#849

@dsyme dsyme changed the title Experiment: feature/auto-widen WIP: feature/auto-widen Jan 18, 2021
@dsyme dsyme marked this pull request as draft January 18, 2021 16:18
@dsyme
Copy link
Contributor Author

dsyme commented Jan 19, 2021

I set up auto-flow from main to this branch here: https://github.com/dotnet/roslyn-tools/blob/master/src/GitHubCreateMergePRs/config.xml

@dsyme
Copy link
Contributor Author

dsyme commented Jan 21, 2021

RFC is here: https://github.com/fsharp/fslang-design/blob/master/RFCs/FS-1093-additional-conversions.md

Don Syme and others added 4 commits January 21, 2021 14:50
* [main] Update dependencies from dotnet/arcade (#10913)

Microsoft.DotNet.Arcade.Sdk
 From Version 6.0.0-beta.21068.2 -> To Version 6.0.0-beta.21069.2

Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>

* Refactor everything but the type provider tests in signature help testing (#10908)

Co-authored-by: dotnet-maestro[bot] <42748379+dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Phillip Carter <[email protected]>
@Swoorup
Copy link
Contributor

Swoorup commented Jan 22, 2021

Testing this branch along with anonymous union.
Looks like records and DUs fail to widen.

type DU = DU of int
let _: obj = DU(0) //error FS0001: This expression was expected to have type ...

type R = { a: int }
let _: obj = { a = 0 } //error FS0001: This expression was expected to have type ...

let _: obj = {|a=1|} // works

[<Measure>]
type id
type UserId = int<id>
let id: UserId = 0<id>
let _: obj = id // works

Edit: just noticed your notes on record which is todo😛

@Happypig375
Copy link
Member

But we can still disable automatic upcasting at override returns (and go on the error path before this PR) and emit a covariant return instead?

@Happypig375
Copy link
Member

Happypig375 commented Jul 26, 2021

Like

type [<AbstractClass>] A() =
    abstract A: unit -> obj
type B() =
    inherits A()
    override A() = () // Covariant return
type C() =
    inherits A()
    override A() = Unchecked.defaultof<_> // Infer obj since type inference kicks in and no other type is known
type D() =
    inherits A()
    override A() = () :> _ // Infer obj type for type hole

@dsyme
Copy link
Contributor Author

dsyme commented Jul 26, 2021

But we can still disable automatic upcasting at override returns (and go on the error path before this PR) and emit a covariant return instead?

I don't think so, because existing code already relies on the eager application of the non-covariant type information.

To be honest, co-variant returns are just unlikely to ever make it into F#. Remember F# strongly biases against class/interface hierarchies as a modelling technique (though this stuff does have its uses).

If we ever add them, I'm pretty certain we would want strong type information for updated types for the slots.

type [<AbstractClass>] A() =
    abstract A: unit -> obj
type B() =
    inherits A()
    override A() : string = "a"

@dsyme
Copy link
Contributor Author

dsyme commented Jul 26, 2021

@KevinRansom This is now ready for preview.

@KevinRansom KevinRansom closed this Aug 9, 2021
@KevinRansom KevinRansom reopened this Aug 9, 2021
Copy link
Member

@KevinRansom KevinRansom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few things

tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj Outdated Show resolved Hide resolved
tests/fsharp/tests.fs Show resolved Hide resolved
@KevinRansom
Copy link
Member

I'll fix the test issues

@dsyme
Copy link
Contributor Author

dsyme commented Aug 10, 2021

I'll fix the test issues

@KevinRansom thanks!

@dsyme
Copy link
Contributor Author

dsyme commented Aug 10, 2021

@KevinRansom all ready I think

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants