-
Notifications
You must be signed in to change notification settings - Fork 789
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
Ensure that Language Version support flows to Parser #6891
Ensure that Language Version support flows to Parser #6891
Conversation
| UNDERSCORE DOT pathOp { let (LongIdentWithDots(lid,dotms)) = $3 in (None,LongIdentWithDots(ident("_",rhs parseState 1)::lid, rhs parseState 2::dotms)) } | ||
| UNDERSCORE DOT pathOp { | ||
if not (parseState.LexBuffer.SupportsFeature LanguageFeature.SingleUnderscorePattern) then | ||
raiseParseErrorAt (rhs parseState 2) (FSComp.SR.parsUnexpectedSymbolDot()) |
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'm not sure this approach will work in all features. Here effectively there was an error when this rule was absent, but what about features where the absence of the rule doesn't result in an error message but a different parser result?
Moreover I think the spirit of this switch is to provide a mechanism for breaking changes. When a feature resulted in an error (like the underscore feature) normally is not a breaking change.
I'm not sure what's the best way to solve this, maybe adding a when featureX
to the rule, but I think the fsy file doesn't support when clauses (though not sure).
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 current parser does not really allow for retry on pattern matches or filters, once it arrives in one of these blocks of code, the parsed source code has been successfully parsed. Once we are here we pretty much can do only one of two things … error out or modify the AST produced within this block.
If the source is to have a changed behavior based on language version, then produce an AST that reflects the needs of the new language feature, or if the new feature is not supported, then produce the AST required by the old language version. If the source didn't match before then provoke an error.
The switch is not about allowing breaking changes in the language. It's purpose is to allow users of the compiler to access a specific version of the language.
We expect source code that compiled and ran using the F# X version of the language to continue to successfully compile and run with F# X+1 or F# X+2 or F# X+99 of the language.
Some times we will fix bugs in the implementation of the tooling that will cause the above to not be achieved. Cases where we fixed a bug should not be impacted by this switch. On the other hand, often those bugs, become an intrinsic part of the language and never get fixed.
I hope this helps
Kevin
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 the explanation. If that's the case, it looks good to me.
Seems ok to me |
/// LanguageFeature enumeration | ||
[<RequireQualifiedAccess>] | ||
type LanguageFeature = | ||
| LanguageVersion46 = 0 |
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 it really a language feature? Wouldn't it be better as a separate enum for language versions? It could be quite straightforward to check if a feature supported by a language version.
val langFeatureSupported: langVersion -> langFeature -> bool
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.
There are two audiences … compiler users are focused on language version. Thus the langversion switch. Feature developers are focused on their feature. Until a feature is confirmed for a specific language version, it is assigned to the version beyond latest. The developer merely asks if their feature is supported by the specified LanguageVersion, and invokes the feature code or the old version failure path.
When a feature is approved for an RTM language version, the code will be modified to check for that specific language version and the feature flag will disappear. We will do this to keep the compatibility matrix from blowing our minds. The roslyn guys tell me this is what they do too, because no one was happy with enabling individual features.
type LanguageVersion (specifiedVersion) = | ||
|
||
// When we increment language versions here preview is higher than current RTM version | ||
static let languageVersion46 = 4.6m |
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 don't think that using numbers is better than enum here.
Numbers could be good as an underlying type inside some LanguageVersion
type and wouldn't be exposed this way.
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.
Well we do a numeric comparison on the number, to figure out if the version is supported or not. Sure we could compare enum ordinals, but it's not really much of a thing.
c62d136
to
74dc501
Compare
@brettfo, @cartermp, Okay I have enabled languageversion feature tests for the wild card feature: Tests are here: Comparer here: Note: this reverts the tests modified when the feature was merged, back to 4.6 compatible code. And adds in new tests that run under 4.7 and 4.6. The tests run in the fsharpsuite test suite and rely on SingleTestBuild and run. I have also amended where the @@@@@ were originally to, flow LanguageVersion via the lexer and parser. |
45aa5ff
to
f08fb25
Compare
Also tagging @TIHan for review |
3f4192b
to
110bdb2
Compare
if isStringEmpty content then expect | ||
else expect.Replace(content, "") | ||
|
||
let rdr = XmlReader.Create(new StringReader(nocontentxpect)) |
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.
Why not System.Xml.Linq.XDocument
? It's (I think) a much better API than using a reader.
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.
If I had used Google search for samples I'm sure I would have got the good api, but I used Bing.
|
||
let FunctionAsLexbuf (bufferFiller: char[] * int * int -> int) : Lexbuf = | ||
LexBuffer<_>.FromFunction bufferFiller | ||
let StringAsLexbuf (supportsFeature: Features.LanguageFeature -> bool, s:string) : Lexbuf = |
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.
@KevinRansom I know I spoke to you that I thought this approach was fine. Now that I'm looking at this more carefully, I don't think it is a good idea.
This feels like a code smell. I don't like that we need to pass supportsFeature
in order to create a lexbuffer, it should not care about this. The only reason why is to be able to access supportsFeature
in the parser (.fsy
).
Lexbuffers and language features are two separate concerns and should not be intertwined this way; we have made this more complex.
@@ -421,6 +421,7 @@ parsAttributesMustComeBeforeVal,"Attributes should be placed before 'val'" | |||
568,parsAllEnumFieldsRequireValues,"All enum fields must be given values" | |||
569,parsInlineAssemblyCannotHaveVisibilityDeclarations,"Accessibility modifiers are not permitted on inline assembly code types" | |||
571,parsUnexpectedIdentifier,"Unexpected identifier: '%s'" | |||
10,parsUnexpectedSymbolDot,"Unexpected symbol '.' in member definition. Expected 'with', '=' or other token." |
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 is interesting that we have to put this kind of error message in FSComp. Before, we relied on what the parser would give us for the error. But since we are adding logic for language version, we have no choice but to copy the same error message that the parser would create, and then create a new resource in FSComp in order to raise the same error message in conjunction with language version. While this solves the problem for the language version, there is now potential for error text to be out of sync if the parser decides to change the text for the same kind of error in a different rule. Though, in this case, it is unlikely to change anytime soon.
While we do have other parser errors in FSComp, I don't think we have errors with exact text of what the parser would normally produce on its own; we only have specific parse errors that try to be more descriptive for the user.
This is sort of a code smell in my opinion, but not a big deal. It isn't necessarily due to the language version flag itself; this problem will exist if you try to preserve old parser logic and errors behind any kind of flag. However, I only think a flag would be absolutely necessary if we change the syntax in such a way that broke existing code; which that isn't even the purpose of the language version flag anyway or something that we ever wish to do.
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 realize this has already been reviewed and merged in. I should have reviewed this sooner.
I have a very strong feeling, that trying to preserve old parser logic + errors behind any kind of flag in this manner, is going make our parser more complex than before. This is mainly the fault of YACC and the lack of backtracking.
I urge that we do not try to do anything more to our parser regarding language version, which effectively means, I think we should halt any more changes to our compiler that tries to preserve logic behind the language version flag.
In principle, I love the idea behind the language version flag. I'm just very concerned about how much complexity this will add to our compiler. We have put flags in parts of the compiler before, but the language version flag will be much more invasive.
Is the concern about the parser or LangVersion in general? Because we need LangVersion if we ever hope to merge language features in progressively, and it'll be necessary for .NET 5. I can't speak for the validity of the change with regards to the parser, but we'll also need a way to guard new syntax and supply a meaningful error message about it. Is there a better alternative? |
There certainly could be. I don't know what it is though. We would need to spend more time looking into it.
Both, but I'm putting more concern on the parser.
I do not think we need LangVersion to handle this. An alternative to this problem is to have a preview/feature flag that enables specific features that we dub as preview-able. This would be less invasive and less scope than a full-on LangVersion flag. As far as .NET 5, I don't know what exactly makes it necessary. |
Can we not just update the parser and emit the "old" error messages later based on versioning? |
So ... over the holiday weekend, I amused myself by trying to figure a good way to flow the LanguageVersion to the parser.
As a proof of concept I uses the WildCard self Language feature provided by @gusty because it's 1. easy, and 2. it's wholly implemented in the parser.
In RTM F# this code fails to compile:
Like this:
With this pr it still fails like this:
With language preview enabled it succeeds:
So ….
In the parser the code to verify a feature support looks like:
ToDo:
@dsyme, @gusty let me know what you think
Thanks