Skip to content

Commit

Permalink
adjust non-optional nullable interop
Browse files Browse the repository at this point in the history
  • Loading branch information
dsyme committed May 27, 2020
1 parent 13aaaef commit 6f0332d
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 68 deletions.
128 changes: 71 additions & 57 deletions src/fsharp/MethodCalls.fs
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,13 @@ let AdjustCalledArgTypeForOptionals (g: TcGlobals) enforceNullableOptionalsKnown
calledArgTy
else
match calledArg.OptArgInfo with
| NotOptional ->
// CSharpMethod(x = arg), non-optional C#-style argument, may have type Nullable<ty>.
| NotOptional when not (g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop) ->
calledArgTy

// CSharpMethod(x = arg), optional C#-style argument, may have type Nullable<ty>.
// The arg should have type ty. However for backwards compat, we also allow arg to have type Nullable<ty>
| NotOptional
// CSharpMethod(x = arg), optional C#-style argument, may have type Nullable<ty>.
| CallerSide _ ->
if isNullableTy g calledArgTy && g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop then
// If inference has worked out it's a nullable then use this
Expand Down Expand Up @@ -1148,72 +1150,84 @@ let GetDefaultExpressionForOptionalArg tcFieldInit g (calledArg: CalledArg) eCal
let callerArg = CallerArg(calledArgTy, mMethExpr, false, expr)
preBinder, { NamedArgIdOpt = None; CalledArg = calledArg; CallerArg = callerArg }

let MakeNullableExprIfNeeded (infoReader: InfoReader) calledArgTy callerArgTy callerArgExpr m =
let g = infoReader.g
let amap = infoReader.amap
if isNullableTy g callerArgTy then
callerArgExpr
else
let calledNonOptTy = destNullableTy g calledArgTy
let minfo = GetIntrinsicConstructorInfosOfType infoReader m calledArgTy |> List.head
let callerArgExprCoerced = mkCoerceIfNeeded g calledNonOptTy callerArgTy callerArgExpr
MakeMethInfoCall amap m minfo [] [callerArgExprCoerced]

// Adjust all the optional arguments, filling in values for defaults,
let AdjustCallerArgForOptional tcFieldInit eCallerMemberName (infoReader: InfoReader) (assignedArg: AssignedCalledArg<_>) =
let g = infoReader.g
let amap = infoReader.amap
let callerArg = assignedArg.CallerArg
let (CallerArg(callerArgTy, m, isOptCallerArg, callerArgExpr)) = callerArg
let calledArg = assignedArg.CalledArg
match calledArg.OptArgInfo with
| NotOptional ->
let calledArgTy = calledArg.CalledArgumentType
match calledArg.OptArgInfo with
| NotOptional when not (g.langVersion.SupportsFeature LanguageFeature.NullableOptionalInterop) ->
if isOptCallerArg then errorR(Error(FSComp.SR.tcFormalArgumentIsNotOptional(), m))
assignedArg
| _ ->
let callerArgExpr2 =
match calledArg.OptArgInfo with
| NotOptional -> failwith "unreachable"

| _ ->

let callerArgExpr2 =
match calledArg.OptArgInfo with
| NotOptional ->
// T --> Nullable<T> widening at callsites
if isOptCallerArg then errorR(Error(FSComp.SR.tcFormalArgumentIsNotOptional(), m))
if isNullableTy g calledArgTy then
MakeNullableExprIfNeeded infoReader calledArgTy callerArgTy callerArgExpr m
else
callerArgExpr

| CallerSide dfltVal ->
let calledArgTy = calledArg.CalledArgumentType

if isOptCallerArg then
// CSharpMethod(?x=b)
if isOptionTy g callerArgTy then
if isNullableTy g calledArgTy then
// CSharpMethod(?x=b) when 'b' has optional type and 'x' has nullable type --> CSharpMethod(x=Option.toNullable b)
mkOptionToNullable g m (destOptionTy g callerArgTy) callerArgExpr
else
// CSharpMethod(?x=b) when 'b' has optional type and 'x' has non-nullable type --> CSharpMethod(x=Option.defaultValue DEFAULT v)
let _wrapper, defaultExpr = GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg calledArgTy dfltVal eCallerMemberName m
let ty = destOptionTy g callerArgTy
mkOptionDefaultValue g m ty defaultExpr callerArgExpr
else
// This should be unreachable but the error will be reported elsewhere
callerArgExpr
else
if isNullableTy g calledArgTy then
// CSharpMethod(x=b) when 'x' has nullable type
if isNullableTy g callerArgTy then
// CSharpMethod(x=b) when both 'x' and 'b' have nullable type --> CSharpMethod(x=b)
callerArgExpr
else
// CSharpMethod(x=b) when 'x' has nullable type and 'b' does not --> CSharpMethod(x=Nullable(b))
let calledNonOptTy = destNullableTy g calledArgTy
let minfo = GetIntrinsicConstructorInfosOfType infoReader m calledArgTy |> List.head
let callerArgExprCoerced = mkCoerceIfNeeded g calledNonOptTy callerArgTy callerArgExpr
MakeMethInfoCall amap m minfo [] [callerArgExprCoerced]
else
// CSharpMethod(x=b) --> CSharpMethod(?x=b)
callerArgExpr

| CalleeSide ->
if isOptCallerArg then
// CSharpMethod(?x=b) --> CSharpMethod(?x=b)
callerArgExpr
else
// CSharpMethod(x=b) when CSharpMethod(A) --> CSharpMethod(?x=Some(b :> A))
let calledArgTy = assignedArg.CalledArg.CalledArgumentType
if isOptionTy g calledArgTy then
let calledNonOptTy = destOptionTy g calledArgTy
let callerArgExprCoerced = mkCoerceIfNeeded g calledNonOptTy callerArgTy callerArgExpr
mkSome g calledNonOptTy callerArgExprCoerced m
| CallerSide dfltVal ->
let calledArgTy = calledArg.CalledArgumentType

if isOptCallerArg then
// CSharpMethod(?x=b)
if isOptionTy g callerArgTy then
if isNullableTy g calledArgTy then
// CSharpMethod(?x=b) when 'b' has optional type and 'x' has nullable type --> CSharpMethod(x=Option.toNullable b)
mkOptionToNullable g m (destOptionTy g callerArgTy) callerArgExpr
else
assert false
callerArgExpr // defensive code - this case is unreachable
// CSharpMethod(?x=b) when 'b' has optional type and 'x' has non-nullable type --> CSharpMethod(x=Option.defaultValue DEFAULT v)
let _wrapper, defaultExpr = GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg calledArgTy dfltVal eCallerMemberName m
let ty = destOptionTy g callerArgTy
mkOptionDefaultValue g m ty defaultExpr callerArgExpr
else
// This should be unreachable but the error will be reported elsewhere
callerArgExpr
else
if isNullableTy g calledArgTy then
// CSharpMethod(x=b) when 'x' has nullable type
// CSharpMethod(x=b) when both 'x' and 'b' have nullable type --> CSharpMethod(x=b)
// CSharpMethod(x=b) when 'x' has nullable type and 'b' does not --> CSharpMethod(x=Nullable(b))
MakeNullableExprIfNeeded infoReader calledArgTy callerArgTy callerArgExpr m
else
// CSharpMethod(x=b) --> CSharpMethod(?x=b)
callerArgExpr

| CalleeSide ->
if isOptCallerArg then
// CSharpMethod(?x=b) --> CSharpMethod(?x=b)
callerArgExpr
else
// CSharpMethod(x=b) when CSharpMethod(A) --> CSharpMethod(?x=Some(b :> A))
if isOptionTy g calledArgTy then
let calledNonOptTy = destOptionTy g calledArgTy
let callerArgExprCoerced = mkCoerceIfNeeded g calledNonOptTy callerArgTy callerArgExpr
mkSome g calledNonOptTy callerArgExprCoerced m
else
assert false
callerArgExpr // defensive code - this case is unreachable

let callerArg2 = CallerArg(tyOfExpr g callerArgExpr2, m, isOptCallerArg, callerArgExpr2)
{ assignedArg with CallerArg=callerArg2 }
let callerArg2 = CallerArg(tyOfExpr g callerArgExpr2, m, isOptCallerArg, callerArgExpr2)
{ assignedArg with CallerArg=callerArg2 }

// Handle CallerSide optional arguments.
//
Expand Down
27 changes: 27 additions & 0 deletions tests/fsharp/core/fsfromfsviacs/lib3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,34 @@ public static int OverloadedMethodTakingNullableOptionals(long? x = null, string
length = y.Length;
return (x.HasValue ? (int) x.Value : -1) + length + (int) (d.HasValue ? d.Value : -1.0) + 7;
}
public static int MethodTakingNullables(int? x, string y, double? d)
{
int length;
if (y == null)
length = -1;
else
length = y.Length;
return (x.HasValue ? x.Value : -1) + length + (int) (d.HasValue ? d.Value : -1.0);
}

public static int OverloadedMethodTakingNullables(int? x, string y, double? d)
{
int length;
if (y == null)
length = -1;
else
length = y.Length;
return (x.HasValue ? x.Value : -1) + length + (int) (d.HasValue ? d.Value : -1.0);
}
public static int OverloadedMethodTakingNullables(long? x, string y, double? d)
{
int length;
if (y == null)
length = -1;
else
length = y.Length;
return (x.HasValue ? (int) x.Value : -1) + length + (int) (d.HasValue ? d.Value : -1.0) + 7;
}
public static int SimpleOverload(int? x = 3)
{
return (x.HasValue ? x.Value : 100);
Expand Down
57 changes: 46 additions & 11 deletions tests/fsharp/core/fsfromfsviacs/test.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,35 @@ module TestConsumeCSharpOptionalParameter =
// Check the type inferred for an un-annotated first-class use of the method
check "csoptional23982f55" (let f = SomeClass.MethodTakingNullableOptionals in ((f : unit -> int) ())) -3

#if LANGVERSION_PREVIEW
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, "aaaaaa", 8.0)) 20
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, "aaaaaa", Nullable 8.0)) 20
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, "aaaaaa", Nullable ())) 11
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(Nullable (), "aaaaaa", 8.0)) 13
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(Nullable 6, "aaaaaa", 8.0)) 20

check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, "aaaaaa", d=8.0)) 20
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, "aaaaaa", d=Nullable 8.0)) 20
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, "aaaaaa", d=Nullable ())) 11
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(Nullable (), "aaaaaa", d=8.0)) 13
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(Nullable 6, "aaaaaa", d=8.0)) 20

check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, y="aaaaaa", d=8.0)) 20
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, y="aaaaaa", d=Nullable 8.0)) 20
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, y="aaaaaa", d=Nullable ())) 11
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(Nullable (), y="aaaaaa", d=8.0)) 13
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(Nullable 6, y="aaaaaa", d=8.0)) 20

check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, y="aaaaaa", d=8.0)) 20
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, y="aaaaaa", d=Nullable 8.0)) 20
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(6, y="aaaaaa", d=Nullable ())) 11
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(Nullable (), y="aaaaaa", d=8.0)) 13
check "acsoptional23982f51" (SomeClass.MethodTakingNullables(Nullable 6, y="aaaaaa", d=8.0)) 20

// Check the type inferred for an un-annotated first-class use of the method
check "acsoptional23982f55" (let f = SomeClass.MethodTakingNullables in ((f : Nullable<int> * string * Nullable<double> -> int) (Nullable 1,"aaaa",Nullable 3.0))) 8
#endif

// This tests overloaded variaitons of the methods, where the overloads vary by type but not nullability
//
// The CHECK_ERRORS cases are not execpted to compile
Expand Down Expand Up @@ -177,8 +206,10 @@ module TestConsumeCSharpOptionalParameterOverloads =

check "csoptional23982f523o" (SomeClass.OverloadedMethodTakingNullableOptionals(x = 6)) 4

// When a C# argument has a default value and is nullable (without a default), using ?x to provide an argument takes type option
check "csoptional23982f527o" (SomeClass.OverloadedMethodTakingNullableOptionals(?x = Some 6)) 4
check "csoptional23982f52o1" (SomeClass.OverloadedMethodTakingNullables(6, "aaaaaa", 8.0)) 20 // can provide non-nullable
check "csoptional23982f52o2" (SomeClass.OverloadedMethodTakingNullables(Nullable(6), "aaaaaa", 8.0)) 20 // can provide nullable
check "csoptional23982f52o3" (SomeClass.OverloadedMethodTakingNullables(Nullable(6), "aaaaaa", Nullable(8.0))) 20 // can provide nullable

#endif

#if CHECK_ERRORS
Expand All @@ -195,24 +226,28 @@ module TestConsumeCSharpOptionalParameterOverloads =
// Check the type inferred for an un-annotated first-class use of the method
check "csoptional23982f35o" (let f = SomeClass.OverloadedMethodTakingOptionals in ((f : unit -> int) ())) 11

check "csoptional23982f41o" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults()) 11
check "csoptional23982f43o" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(y = "aaaaaa")) 14
check "csoptional23982f44o" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(d = Nullable 8.0)) 14 // can provide nullable for legacy
check "csoptional23982f442o" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(d = 8.0)) 14
check "csoptional23982f446o" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(?d = Some 8.0)) 14
check "csoptional23982f43Eo" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(?x = None)) -92
check "csoptional23982f44Ro" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(?d = None)) 6
check "csoptional23982f41ox" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults()) 11
check "csoptional23982f43ox" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(y = "aaaaaa")) 14
check "csoptional23982f44ox" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(d = Nullable 8.0)) 14 // can provide nullable for legacy
check "csoptional23982f442ox" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(d = 8.0)) 14
check "csoptional23982f446ox" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(?d = Some 8.0)) 14
check "csoptional23982f43Eox" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(?x = None)) -92
check "csoptional23982f44Rox" (SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults(?d = None)) 6
// Check the type inferred for an un-annotated first-class use of the method
check "csoptional23982f45o" (let f = SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults in ((f : unit -> int) ())) 11
check "csoptional23982f45ox" (let f = SomeClass.OverloadedMethodTakingNullableOptionalsWithDefaults in ((f : unit -> int) ())) 11

check "csoptional23982f51o" (SomeClass.OverloadedMethodTakingNullableOptionals()) -3
check "csoptional23982f53o" (SomeClass.OverloadedMethodTakingNullableOptionals(y = "aaaaaa")) 4
check "csoptional23982f54o" (SomeClass.OverloadedMethodTakingNullableOptionals(d = 8.0)) 6
check "soptional23982f54o" (SomeClass.OverloadedMethodTakingNullableOptionals(d = 8.0)) 6
check "csoptional23982f54o" (SomeClass.OverloadedMethodTakingNullableOptionals(d = Nullable 8.0)) 6 // can provide nullable for legacy
check "csoptional23982f544o" (SomeClass.OverloadedMethodTakingNullableOptionals(d = 8.0)) 6
check "csoptional23982f548o" (SomeClass.OverloadedMethodTakingNullableOptionals(?d = Some 8.0)) 6
check "csoptional23982f52To" (SomeClass.OverloadedMethodTakingNullableOptionals(?x = None)) -3
check "csoptional23982f54Yo" (SomeClass.OverloadedMethodTakingNullableOptionals(?d = None)) -3
check "csoptional23982f55o" (let f = SomeClass.OverloadedMethodTakingNullableOptionals in ((f : unit -> int) ())) -3

check "dcsoptional23982f544o" (SomeClass.OverloadedMethodTakingNullables(x= Nullable(), "aaaa" d = Nullable())) 6
check "dcsoptional23982f55o" (let (f: Nullable<_> * string * Nullable<_> -> int) = SomeClass.OverloadedMethodTakingNullables in f (Nullable(), "aaa", Nullable())) -3
#endif

module NestedStructPatternMatchingAcrossAssemblyBoundaries =
Expand Down

0 comments on commit 6f0332d

Please sign in to comment.