-
Notifications
You must be signed in to change notification settings - Fork 842
Omitting Optional; DefaultParameterValue arguments passes the default value and works for F# to F# calls #1812
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
Conversation
…t-param-value # Conflicts: # src/fsharp/IlxGen.fs # src/fsharp/TcGlobals.fs
src/fsharp/IlxGen.fs
Outdated
| and GenDefaultParameterValue (Attrib (_,_,exprs,_,_,_,_)) = | ||
| let (AttribExpr (_,defaultValueExpr)) = List.head exprs | ||
| match defaultValueExpr with | ||
| | Expr.Const ((Const.String s),_,_) -> Some (ILFieldInit.String s) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GenFieldInit?
|
Re GenSlotParam: GenSlotParam generates the data used for parameters at definitions of abstract method slots such as interface methods or |
|
This looks very good. I suppose the F#-to-F# case is covered by this todo-like comment in infos.fs I think you want to crack the attributes at that point to produce one of these (as we do for .NET methods) It looks like you also have to consider null" default values along the lines of this for C# code You can probably share some of the code each way |
|
Thanks much for the pointers, I'll take a look at that. Start digression In other words these default values are caller side, even though they are optically defined at the callee side. Interestingly, this means that if I change the default value in an assembly, this default value would only be picked up by clients after they recompile their assemblies. I would bet at least one person has lost several hours due to that subtlety... |
|
With latest commits it looks like F# to F# optional args work. I left some comments in the code where I did things that may be controversial. Very simple example works; next step: adding tests! |
|
@kurtschelfthout This is looking good |
src/fsharp/infos.fs
Outdated
| static member FieldInitForDefaultParameterValueAttrib (Attrib (_,_,exprs,_,_,_,_)) = | ||
| let (AttribExpr (_,defaultValueExpr)) = List.head exprs | ||
| match defaultValueExpr with | ||
| | Expr.Const ((Const.String s),_,_) -> Some (ILFieldInit.String s) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is closely related to GenFieldInit in IlxGen.fs. May make sense to share the common core of the code, putting it in TastOps.fs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good pointer, will try to reuse...tomorrow evening, time for relaxing now. :)
| open System | ||
|
|
||
| type Class() = | ||
| static member Method1 ([<Optional;DefaultParameterValue(42y)>]i:sbyte) = i |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for being systematic :)
|
@kurtschelfthout Please also check cases where C# and F# arguments have |
|
With the latest commits, I've taken care of all review comments so far, and all issues I raised in the initial PR description above. So almost there! The tests revealed one issue though - we should add a check whether the argument of For that, I need to figure out where to add the error, and how to add compile errors :) |
| static member Method15 ([<Optional;DefaultParameterValue(new DateTime())>]i:DateTime) = i | ||
|
|
||
| //Check nullables. | ||
| static member MethodNullable1 ([<Optional;DefaultParameterValue(null)>]i:Nullable<int>) = i |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In C#, can you just declare Optional with `DefaultParameterValue(null)`` implied?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, or whatever the default value of the type is. To support that we could add some type-directed analysis (a simplified version of analyze which you mentioned earlier - wouldn't need to support marshal and ref parameters) to the added code in infos.fs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe we do need to support byref optional arguments and COM marshalling. Perhaps I'm just assuming things to make my life easier.
|
I added a compiler error to deal with the unverifiable code in infos.fs. A few potential issues with that.
To mitigate the first issue I'll look into allowing to omit the DefautParameterValue if you just want to specify the default value for the type. |
src/fsharp/FSComp.txt
Outdated
| 3208,tcCouldNotFindOffsetToStringData,"Could not find method System.Runtime.CompilerServices.OffsetToStringData in references when building 'fixed' expression." | ||
| 3209,chkNoByrefReturnOfLocal,"The address of the variable '%s' cannot be used at this point. A method or function may not return the address of this local value." | ||
| 3210,tcNamedActivePattern,"%s is an active pattern and cannot be treated as a discriminated union case with named fields." | ||
| 3211,DefaultParameterValueNotAppropriateForArgument,"The default value does not have the same type as the argument." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please also specify the names of the types.
| open System | ||
|
|
||
| type Class() = | ||
| //check that all known supported primitive types work. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add a space between // and the text. I think we do that everywhere in the code base
src/fsharp/IlxGen.fs
Outdated
| // Generate object expressions as ILX "closures" | ||
| //-------------------------------------------------------------------------- | ||
|
|
||
| // generates the data used for parameters at definitions of abstract method slots such as interface methods or override methods. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please make it a triple slash comment and uppercase it?
The behavior here matches that of C# as far as I can tell, but it seems unspecified anywhere. I could not find a reference to it in the C# 5.0 spec or C# 6.0 draft spec. In particular the System.Reflection.Missing for obj is interesting.
|
This is ready for final review. I do feel like it snowballed a bit on me, so feel free to take as much time as you need. |
|
@kurtschelfthout Dude there's a reason why it wasn't fixed earlier. You've done an important job, snowball and all :) |
dsyme
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Superb job. One small change request. Also update the RFC please
Could you take a look if there are optional-argument-functions-with-default-value-attributes in the .NET Framework which we could write some tests against? Also, have you tested it against the original scenarios mentioned in original reports?
src/fsharp/infos.fs
Outdated
| let defaultValue = OptionalArgInfo.ValueOfDefaultParameterValueAttrib attr | ||
| match defaultValue with | ||
| | Some (Expr.Const (_, m, typ)) when not (typeEquiv g typ ty) -> | ||
| error(Error(FSComp.SR.DefaultParameterValueNotAppropriateForArgument(), m)); NotOptional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a warning would be better here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that then the compiler can generate unverifiable IL at call sites where the argument is omitted - the default value is put on the stack and the method is called, but the value on the stack will have the type of the default value, which does not correspond to the method argument's type.
So our options are:
- Keep it as an error
- Make it a warning, but ignore the default value (i.e. infer the default value from the type)
- Make it a warning, ignore the default value and the optional argument (i.e. won't be able to omit this argument) - because inferring the default value from the type can have some surprising consequences.
- Just thought of a fourth option - generate a warning at the definition site, and an error at the call site when the parameter is omitted.
Also note C# generates an error in this case:
|
@dsyme other review items:
|
|
I think we do this: "Make it a warning, ignore the default value and the optionality of the argument"
Yes :) thanks! |
|
So now the error is a warning as requested, but there is a problem I'm not sure how to address. Every time the method I have tried to move the checks to Perhaps the |
I don't think this is such a problem in practice. I'm happy with things as they are, I think we can now accept this? |
|
@kurtschelfthout Thank you so much for addressing this, it's a major improvement |
|
@dsyme thanks for the many pointers and support. The only reason I could even get started on this is because you put such a nice analysis on the bug report. |
|
Well done =). Can I use this commit of the compiler with Visual Studio on Windows straight away somehow? |
|
@haf-lynx sorry no. I mean yes you can build your own compiler, but it's not exactly recommended to use that one in production. |
|
I am still confused. Now that this is merged, What is the correct way to declare optional parameters that can also be used via C# or Ironpython? |
|
@goswinr The latter: type Class1() =
static member Right([<Optional;DefaultParameterValue(12)>] i: int) = i + 1
type Class2() =
// Compiler warning
static member Wrong([<Optional;DefaultParameterValue("I am a string")>] i: int) = i + 1 |
|
@dsyme Any thoughts here? It appears that any unchecked value is not accepted. open System.Runtime.InteropServices
[<Struct>]
type Egads = { Yee: int; Doot: string }
type C() =
member __.M([<Optional; DefaultParameterValue(Unchecked.defaultof<Egads>)>] ?e: Egads) = ()It does not appear to be covered in the RFC either. |
|
Yes there is an incompleteness here. The relevant C# equivalent is this: using System.Runtime.InteropServices;
class C {
static void M(System.DateTime e = new System.DateTime()) { System.Console.WriteLine ("e = {0}", e); }
static void Main() { M(); }
}Or this The IL is this: I somehow hadn't realised that We think we can make it that |
|
What about constructors? Does this PR cover them too? |


See #96.
This code seems to be going in the right direction in that as far as I can tell it now results in the same IL as the C# compiler generates for
DefaultParameterValue(and for optional arguments, which in C# is just syntactic sugar for bothOptionalandDefaultParameterValue).However we are not out of the woods.
F# still does not recognise a parameter with both
[<Optional; DefaultParameterValue>]as optional only if the method definition is compiled by fsc. In other words:Optional value defined in C# can be omitted at F# call site.
Optional value defined in F# can be omitted at C# call site, and now picks up the default value defined on the F# side.
Optional value defined in F# can not be omitted at F# call site (both same assembly or two different assemblies).
So @dsyme to be pedantic, it seem like C#-style optional arguments defined in F# cannot be consumed by F# as optional #1783 is not really a duplicate of [<DefaultParameterValue>] is ignored in F#-authored code #96 in that I think this PR as is pretty much solves [<DefaultParameterValue>] is ignored in F#-authored code #96 but not C#-style optional arguments defined in F# cannot be consumed by F# as optional #1783.
Note that I filtered out
DefaultParameterValuefrom the attributes that are actually written in the IL, as, likeOptionalit is supposed to be written in the IL attributes directly. As far as I can tell, nothing should have problems with this, but I thought I'd mention it.I have no idea what
GenSlotParamis for, just modified it blindly likeGenParamAttribs.I have little doubt there will be test failures, as well as new tests to add.
Meanwhile any thoughts are appreciated.