-
Notifications
You must be signed in to change notification settings - Fork 785
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
Make .Is* discriminated union properties visible #16341
Conversation
@kerams you're the MVP, thanks for reviving it! I remember I've hit some verification issues (ilverify tests iirc), and didn't have much time to investigate. Let me know if you'll hit them as well and will want some help looking into them. |
It would be good to add some tests when using lowercase DU ie: [<RequireQualifiedAccess>]
type X =
| A
| a
where we would have IsA and Isa |
@psfinaki, how would I go about constraining which symbols can be renamed? Entities that are either compiler generated or implied should not offer the rename context menu option. Maybe I can return an empty result from On second thought, maybe rename should be allowed here? |
@kerams, a shot in the dark but:
|
Thinking about it now. I'm a bit confused. You mean if we should allow renames on Is* members? |
Yes. On the one hand, if you decide to rename a union case, it should be possible to rename I might just ignore this issue for now. |
Yeah, it's a tough issue. I agree that we can ignore it for now. |
on the rename front, there is also the subtlety of renaming a property to the compiler generated name for an existing case. I think the most important is just to prevent a rename on the call to |
One more thing I am unsure about. If assembly A is compiled with lang version preview and exposes a union, assembly B with a reference to A has access to that union's case testers even if it uses a lower language version. Finally, these members are currently not surfaced without using the right language version, meaning we don't an error guiding you to update. You also don't get to use the members in 3rd party libraries, even if they have been recompiled, but without the approriate language version. Perhaps it would be best to always surface the members in metadata, but add a usage check in CheckExpressions? That would address both issues in this comment. (Older compilers will likely see these properties either way (see #11394 (comment)), and existing libraries will still need to be recompiled in order to expose union case testers) |
I personally believe that the language check should be only done at the producer of the DU. After all, the 'producer' could write similar tester properties by hand as of now already. |
Similar, yes, but not identical. Assemblies emit IL for these members already, so there is no run-time difference. We're only talking about metadata. Imagine you are consuming a third party assembly whose author updates the SDK, but doesn't want to bump the language version. If the feature check is on the producer side, you wouldn't be able to use case testers from that assembly in your code, even if you use 'preview'. At the same time, whether or not the case testers are visible outwardly makes no difference to the author, because they exist regardless. |
The code owned by the producer might already be providing similar checks (maybe with some additional features/logic) and wouldn't want to expose the automatically created getters to keep the recommended API layer clean (for F# consumers at least). Keeping autocomplete results clean might be one aspect to it. The producer should be able to control the augmentation via attributes as is possible with existing augmentations. I do realize this depends on one's opinion on 'adding to the offered F# API with hopefully harmless properties'. From the runtime perspective, it does not matter of course. But considering the tooling aspect with autocomplete and API discovery, I do see a difference. |
tests/FSharp.Compiler.ComponentTests/Language/DiscriminatedUnionTests.fs
Show resolved
Hide resolved
On the rename front, I'd say there are two premises here:
I haven't deeply looked into the implementation yet, however that's what I'd probably consider an ideal tooling behavior. Given the code: type Contact =
| Email of address: string
| Phone of countryCode: int * number: string
type Person = { name: string; contact: Contact }
let canSendEmailTo person =
person.contact.IsEmail
Now, since I don't expect That's just my perspective. Maybe @0101 you have some ideas. On that note, if you @kerams save renaming files in this PR for a followup, reviewers will appreciate it :) And thanks for your 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.
That's great, thanks!
@kerams Could you please add some tests for intersection with interface members? Sorry if I've missed them in the test type I =
abstract IsA: bool
type U =
| A
| B
member this.IsA = false
interface I with
member this.IsA = this.IsA type I =
abstract IsA: bool
type U =
| A
| B
interface I with
member this.IsA = this.IsA type I =
abstract IsA: bool
type U =
| A
| B
member this.IsA = 1
interface I with
member this.IsA = this.IsA |
@@ -1933,6 +1933,9 @@ module TastDefinitionPrinting = | |||
let props = | |||
GetImmediateIntrinsicPropInfosOfType (None, ad) g amap m ty | |||
|> List.filter (fun pinfo -> shouldShow pinfo.ArbitraryValRef) | |||
// Filter out 'IsA' properties which are implied by the union cases since they don't need to be displayed |
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.
Just for my education - what are some examples of the printed outputs here? As in, are we sure it doesn't ever make sense to show these?
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.
Any DU properties you define.
@@ -578,6 +578,10 @@ val PublishTypeDefn: cenv: TcFileState -> env: TcEnv -> mspec: Tycon -> unit | |||
/// Publish a value definition to the module/namespace type accumulator. | |||
val PublishValueDefn: cenv: TcFileState -> env: TcEnv -> declKind: DeclKind -> vspec: Val -> unit | |||
|
|||
/// Publish a value definition to the module/namespace type accumulator. |
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.
Probably should be decopypasted :)
// Build augmentation declarations | ||
//------------------------------------------------------------------------- | ||
|
||
module AddAugmentationDeclarations = |
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.
It would be nice to separate this to a separate file so that CheckDeclarations keeps at least somewhat manageable size.
I mean I know it was there so you don't have to :) Just since you've looked into that, you might know if it is easy to do or not.
|> shouldSucceed | ||
|
||
[<FSharp.Test.FactForNETCOREAPP>] | ||
let ``Lowercase Is* discriminated union properties are visible, proper values are returned`` () = |
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 maybe also add a sanity check test that "isABC" doesn't work?
type Foo = | Foo of string | ``Mars Bar`` | ||
let foo = Foo.Foo "hi" | ||
if not foo.IsFoo then failwith "Should be Foo" | ||
if foo.``IsMars Bar`` then failwith "Should not be ``Mars Bar``" |
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.
Interesting. Thinking about it - this is probably a viable approach, but just curious - how hard would it be to rather have foo.Is``Mars Bar``
or to ban such elements from having Is
property altogether?
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.
Is``Mars Bar``
is not a valid identifier.
tests/FSharp.Compiler.ComponentTests/Language/DiscriminatedUnionTests.fs
Show resolved
Hide resolved
Damn my review came too late :) |
@kerams Oh this is great! Thanks for bringing this puppy home |
|> withLangVersionPreview | ||
|> typecheck | ||
|> shouldFail | ||
|> withErrorMessage "The type 'ValueOption<_>' does not define the field, constructor or member 'IsValueNone'. Maybe you want one of the following: |
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.
@kerams :
I am having a problem with this test in a different PR and I cannot get my head around it - how is it ensured, that the generation is prevented for ValueOption?
Afterall, it does NOT have DefaultAugmentation(false).
What am I missing?
I tried to scan this PR's diff for any kind of logical condition that would skip IsValueNone and IsValueSome for ValueOption, but I do not see it.
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.
While the case testers exist, they aren't exposed in prim-types.fsi.
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.
And this PR does not add them automatically to anything in prim-types unless specified, or how does it work?
i.e. what condition is in place to make sure that ValueOption does NOT get them and a user-defined DU gets them?
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 is it done based on exposing/not exposing in a signature file?
Meaning that all users of signature files (not many, I admit) will only benefit from this feature if they manually add the testers to .fsi?
(this might be relevant for docs)
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, if there's a signature file, case testers need to be exposed manually. The RFC mentions it briefly.
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, got it.
For some reason, I have it keep coming in the nullness PR (e.g. ValueOption does come up with the IsValueNone tester), not sure what is so different over there.
Implements fsharp/fslang-suggestions#222, attempts to finish #11394.