diff --git a/src/QsCompiler/CommandLineTool/Options.cs b/src/QsCompiler/CommandLineTool/Options.cs index d40aa607a1..de79138fe8 100644 --- a/src/QsCompiler/CommandLineTool/Options.cs +++ b/src/QsCompiler/CommandLineTool/Options.cs @@ -265,12 +265,12 @@ private static NonNullable SNIPPET_FILE_ID /// /// name of the namespace within which code snippets are compiled /// - private const string SNIPPET_NAMESPACE = "_CODE_SNIPPET_NS_"; + private const string SNIPPET_NAMESPACE = "CODE_SNIPPET_NS"; /// /// name of the callable within which code snippets are compiled /// - private const string SNIPPET_CALLABLE = "_CODE_SNIPPET_CALLABLE_"; + private const string SNIPPET_CALLABLE = "CODE_SNIPPET_CALLABLE"; /// /// wraps the given content into a namespace and callable that maps Unit to Unit diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 4fded15421..86437c1ccc 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -47,8 +47,9 @@ type ErrorCode = | InvalidConstructorExpression = 3030 | InvalidKeywordWithinExpression = 3032 | InvalidUseOfReservedKeyword = 3033 - | ExcessContinuation = 3034 - | NonCallExprAsStatement = 3035 + | InvalidUseOfUnderscorePattern = 3034 + | ExcessContinuation = 3035 + | NonCallExprAsStatement = 3036 | InvalidExpression = 3101 | MissingExpression = 3102 @@ -320,6 +321,8 @@ type WarningCode = | DeprecatedNOToperator = 3301 | DeprecatedANDoperator = 3302 | DeprecatedORoperator = 3303 + | UseOfFutureReservedKeyword = 3304 + | UseOfUnderscorePattern = 3305 | DeprecatedRUSloopInFunction = 4001 | DiscardingItemInAssignment = 5001 @@ -428,6 +431,7 @@ type DiagnosticItem = | ErrorCode.InvalidConstructorExpression -> "Syntax error in constructor expression." | ErrorCode.InvalidKeywordWithinExpression -> "Invalid use of a reserved keyword within an expression." | ErrorCode.InvalidUseOfReservedKeyword -> "The symbol is reserved for internal use only." + | ErrorCode.InvalidUseOfUnderscorePattern -> "Double underscores as well as underscores before a dot or at the end of a namespace name are reserved for internal use only." | ErrorCode.ExcessContinuation -> "Unexpected code fragment." | ErrorCode.NonCallExprAsStatement -> "An expression used as a statement must be a call expression." @@ -712,6 +716,8 @@ type DiagnosticItem = | WarningCode.DeprecatedNOToperator -> "Deprecated syntax. Use \"not\" to denote the logical NOT operator." | WarningCode.DeprecatedANDoperator -> "Deprecated syntax. Use \"and\" to denote the logical AND operator." | WarningCode.DeprecatedORoperator -> "Deprecated syntax. Use \"or\" to denote the logical OR operator." + | WarningCode.UseOfFutureReservedKeyword -> "The symbol will be reserved for internal use in the future." + | WarningCode.UseOfUnderscorePattern -> "Double underscores as well as underscores before a dot or at the end of a namespace name will be reserved for internal use in the future." | WarningCode.DeprecatedRUSloopInFunction -> "The use of repeat-until-success-loops within functions may not be supported in the future. Please use a while-loop instead." | WarningCode.DiscardingItemInAssignment -> "The expression on the right hand side is discarded on assignment and can be omitted." diff --git a/src/QsCompiler/DataStructures/ReservedKeywords.fs b/src/QsCompiler/DataStructures/ReservedKeywords.fs index 408a9d6a8a..7e50c2fb35 100644 --- a/src/QsCompiler/DataStructures/ReservedKeywords.fs +++ b/src/QsCompiler/DataStructures/ReservedKeywords.fs @@ -202,8 +202,8 @@ module InternalUse = "Borrow" "Return" - "Data" - "Item" + "Data" // REL0920: can be removed once we give errors for underscore patterns + "Item" // REL0920: can be removed once we give errors for underscore patterns "QVoid" "Int64" diff --git a/src/QsCompiler/Tests.Compiler/CommandLineTests.fs b/src/QsCompiler/Tests.Compiler/CommandLineTests.fs index 75563d0fa0..47961c0290 100644 --- a/src/QsCompiler/Tests.Compiler/CommandLineTests.fs +++ b/src/QsCompiler/Tests.Compiler/CommandLineTests.fs @@ -242,7 +242,7 @@ let ``generate docs`` () = let ``find path relative`` () = let fullPath = Path.Combine (Path.GetFullPath "alpha","beta","c","test-path.qs") let options = new BuildCompilation.BuildOptions() - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.Combine (Path.GetFullPath "alpha","beta","c","test-path.g.cs") let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -252,7 +252,7 @@ let ``find path relative to outputfolder`` () = let fullPath = Path.Combine(Path.GetFullPath "alpha","beta","c","test-path.qs") let options = new BuildCompilation.BuildOptions() options.OutputFolder <- Path.Combine (pathRoot,"foo","bar") - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.Combine (pathRoot, "foo", "bar", "alpha", "beta", "c", "test-path.g.cs") let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -262,7 +262,7 @@ let ``find path relative to relative outputfolder`` () = let fullPath = Path.Combine (Path.GetFullPath "alpha","beta","c","test-path.qs") let options = new BuildCompilation.BuildOptions() options.OutputFolder <- Path.Combine("..","foo","bar") - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.Combine (parentDir,"foo","bar","alpha","beta","c","test-path.g.cs") let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -272,7 +272,7 @@ let ``find path absolute`` () = let fileName = Path.Combine (pathRoot,"alpha","beta","c","test-path.qs") let fullPath = Path.GetFullPath fileName let options = new BuildCompilation.BuildOptions() - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.GetFullPath "test-path.g.cs" let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -282,7 +282,7 @@ let ``find path absolute to outputfolder`` () = let fullPath = Path.Combine (pathRoot, "alpha","beta","c","test-path.qs") let options = new BuildCompilation.BuildOptions() options.OutputFolder <- Path.Combine (pathRoot, "foo","bar") - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.Combine (pathRoot, "foo","bar", "test-path.g.cs") let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -291,7 +291,7 @@ let ``find path absolute to outputfolder`` () = let ``find path relative to here`` () = let fullPath = Path.Combine (Path.GetFullPath "alpha","beta","c","test-path.qs") let options = new BuildCompilation.BuildOptions() - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.Combine (Path.GetFullPath "alpha","beta","c","test-path.g.cs") let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -300,7 +300,7 @@ let ``find path relative to here`` () = let ``find path relative with spaces`` () = let fullPath = Path.Combine (Path.GetFullPath "alpha", "some beta", "c", "test 00.qs") let options = new BuildCompilation.BuildOptions() - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.Combine (Path.GetFullPath "alpha","some beta","c","test 00.g.cs") let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -310,7 +310,7 @@ let ``find path relative to outputfolder with spaces`` () = let fullPath = Path.Combine (Path.GetFullPath "alpha","some beta","c","test 00.qs") let options = new BuildCompilation.BuildOptions() options.OutputFolder <- Path.Combine (pathRoot, "foo", "some bar") - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.GetFullPath (Path.Combine (pathRoot, "foo","some bar","alpha","some beta","c","test 00.g.cs")) let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -320,7 +320,7 @@ let ``find path relative to relative outputfolder with spaces`` () = let fullPath = Path.Combine (Path.GetFullPath "alpha","some beta","c","test 00.qs") let options = new BuildCompilation.BuildOptions() options.OutputFolder <- Path.Combine ("..","some foo","bar") - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.Combine (parentDir,"some foo","bar","alpha","some beta","c","test 00.g.cs") let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -329,7 +329,7 @@ let ``find path relative to relative outputfolder with spaces`` () = let ``find path absolute with spaces`` () = let fullPath = Path.Combine (pathRoot, "alpha","some beta","c","test 02.qs") let options = new BuildCompilation.BuildOptions() - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.GetFullPath "test 02.g.cs" let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -339,7 +339,7 @@ let ``find path absolute to outputfolder with spaces`` () = let fullPath = Path.Combine (pathRoot, "alpha", "some beta", "c", "test 02.qs") let options = new BuildCompilation.BuildOptions() options.OutputFolder <- Path.Combine(pathRoot, "foo", "some bar") - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.Combine (pathRoot, "foo", "some bar", "test 02.g.cs") let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) @@ -348,7 +348,7 @@ let ``find path absolute to outputfolder with spaces`` () = let ``find path relative to here with spaces`` () = let fullPath = Path.Combine (Path.GetFullPath "alpha","some beta","c","test 03.qs") let options = new BuildCompilation.BuildOptions() - let id = CompilationUnitManager.TryGetFileId (new Uri(fullPath)) |> snd + let id = CompilationUnitManager.GetFileId (new Uri(fullPath)) let expected = Path.Combine (Path.GetFullPath "alpha","some beta","c","test 03.g.cs") let actual = CompilationLoader.GeneratedFile(id, options.OutputFolder, ".g.cs") Assert.Equal(expected, actual) diff --git a/src/QsCompiler/Tests.Compiler/SyntaxTests.fs b/src/QsCompiler/Tests.Compiler/SyntaxTests.fs index 66a64c89e6..4846b284c9 100644 --- a/src/QsCompiler/Tests.Compiler/SyntaxTests.fs +++ b/src/QsCompiler/Tests.Compiler/SyntaxTests.fs @@ -8,6 +8,7 @@ open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.Diagnostics open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.TextProcessing.ExpressionParsing +open Microsoft.Quantum.QsCompiler.TextProcessing.CodeFragments open Microsoft.Quantum.QsCompiler.TextProcessing.SyntaxBuilder open System open System.Collections.Immutable @@ -17,7 +18,57 @@ open Xunit let private rawString = getStringContent (manyChars anyChar) |>> fst -// Component parsers + +[] +let ``Reserved patterns`` () = + [ + ("_mySymbol" , true , Some "_mySymbol" , []) + ("mySymbol_" , true , Some "mySymbol_" , []) + ("my_symbol" , true , Some "my_symbol" , []) + ("my__symbol", true , Some "my__symbol", [Warning WarningCode.UseOfUnderscorePattern]) + ("__mySymbol", true , Some "__mySymbol", [Warning WarningCode.UseOfUnderscorePattern]) + ("mySymbol__", true , Some "mySymbol__", [Warning WarningCode.UseOfUnderscorePattern]) + ("__my__sym" , true , Some "__my__sym" , [Warning WarningCode.UseOfUnderscorePattern]) + ("my__sym__" , true , Some "my__sym__" , [Warning WarningCode.UseOfUnderscorePattern]) + ("__mysym__" , true , None , [Error ErrorCode.InvalidUseOfReservedKeyword]) + ] + |> List.iter (testOne (symbolNameLike ErrorCode.InvalidIdentifierName)) + [ + ("a.b" , true , ([Some "a" ], Some "b" ), []) + ("_a.b" , true , ([Some "_a" ], Some "b" ), []) + ("a_.b" , true , ([Some "a_" ], Some "b" ), [Warning WarningCode.UseOfUnderscorePattern]) + ("a._b" , true , ([Some "a" ], Some "_b" ), []) + ("a.b_" , true , ([Some "a" ], Some "b_" ), []) + ("_a.b_" , true , ([Some "_a" ], Some "b_" ), []) + ("a_._b" , true , ([Some "a_" ], Some "_b" ), [Warning WarningCode.UseOfUnderscorePattern]) + ("__a.b" , true , ([Some "__a" ], Some "b" ), [Warning WarningCode.UseOfUnderscorePattern]) + ("a__a.b" , true , ([Some "a__a"], Some "b" ), [Warning WarningCode.UseOfUnderscorePattern]) + ("a__.b" , true , ([Some "a__" ], Some "b" ), [Warning WarningCode.UseOfUnderscorePattern]) + ("a.__b" , true , ([Some "a" ], Some "__b" ), [Warning WarningCode.UseOfUnderscorePattern]) + ("a.b__b" , true , ([Some "a" ], Some "b__b"), [Warning WarningCode.UseOfUnderscorePattern]) + ("a.b__" , true , ([Some "a" ], Some "b__" ), [Warning WarningCode.UseOfUnderscorePattern]) + ("__a.b__", true , ([Some "__a" ], Some "b__" ), [Warning WarningCode.UseOfUnderscorePattern; Warning WarningCode.UseOfUnderscorePattern]) + ] + |> List.iter (testOne (multiSegmentSymbol ErrorCode.InvalidIdentifierName |>> fst)) + [ + ("a.b" , true , Some "a.b" , []) + ("_a.b" , true , Some "_a.b" , []) + ("a_.b" , true , Some "a_.b" , [Warning WarningCode.UseOfUnderscorePattern]) + ("a._b" , true , Some "a._b" , []) + ("a.b_" , true , Some "a.b_" , [Warning WarningCode.UseOfUnderscorePattern]) + ("_a.b_" , true , Some "_a.b_" , [Warning WarningCode.UseOfUnderscorePattern]) + ("a_._b" , true , Some "a_._b" , [Warning WarningCode.UseOfUnderscorePattern]) + ("__a.b" , true , Some "__a.b" , [Warning WarningCode.UseOfUnderscorePattern]) + ("a__a.b" , true , Some "a__a.b" , [Warning WarningCode.UseOfUnderscorePattern]) + ("a__.b" , true , Some "a__.b" , [Warning WarningCode.UseOfUnderscorePattern]) + ("a.__b" , true , Some "a.__b" , [Warning WarningCode.UseOfUnderscorePattern]) + ("a.b__b" , true , Some "a.b__b" , [Warning WarningCode.UseOfUnderscorePattern]) + ("a.b__" , true , Some "a.b__" , [Warning WarningCode.UseOfUnderscorePattern]) + ("__a.b__", true , Some "__a.b__", [Warning WarningCode.UseOfUnderscorePattern; Warning WarningCode.UseOfUnderscorePattern]) + ] + |> List.iter (testOne (namespaceName |>> fst)) + + [] let ``String parser tests`` () = [ @@ -141,7 +192,6 @@ let ``Symbol name tests`` () = ("_a", true, "_a", []); ("_", false, "", []); ("__", false, "", []); - ("__a", true, "__a", []); ("функция25", true, "функция25", []); // Russian word 'function' followed by '25' ("λ", true, "λ", []); // Greek small letter Lambda ("ℵ", true, "ℵ", []); // Hebrew capital letter Aleph diff --git a/src/QsCompiler/TextProcessor/QsFragmentParsing.fs b/src/QsCompiler/TextProcessor/QsFragmentParsing.fs index 80aa2b276e..02ec2ddcce 100644 --- a/src/QsCompiler/TextProcessor/QsFragmentParsing.fs +++ b/src/QsCompiler/TextProcessor/QsFragmentParsing.fs @@ -59,15 +59,31 @@ let private buildSymbolTuple (items, range : Position * Position) = let private expectedIdentifierDeclaration continuation = expected localIdentifier ErrorCode.InvalidIdentifierDeclaration ErrorCode.MissingIdentifierDeclaration invalidSymbol continuation +/// Uses multiSegmentSymbol to parse the name of a namespace, +/// concatenates all path segments and the symbol with a dot, and returns a the concatenated string as Some. +/// Returns None if the path contains segments that are None (i.e. invalid). +/// Generates a suitable diagnostic if the namespace name ends in an underscore. +let internal namespaceName = // internal for testing purposes + let asNamespaceName ((path, sym : string option), range : Position * Position) = + let names = path @ [sym] + let namespaceStr = names |> List.choose id |> String.concat "." + if names |> List.contains None then (None, range) |> preturn + elif sym.Value.EndsWith "_" && not (namespaceStr.Contains "__" || namespaceStr.Contains "_.") then // REL0920: remove the second half and return None for pattern errors + buildWarning (preturn range) WarningCode.UseOfUnderscorePattern >>% (Some namespaceStr, range) + else (Some namespaceStr, range) |> preturn + multiSegmentSymbol ErrorCode.InvalidPathSegment >>= asNamespaceName + /// Given a continuation (parser), attempts to parse a qualified QsSymbol /// using multiSegmentSymbol to generate suitable errors for invalid symbol and/or path names, /// and returns the parsed symbol, or a QsSymbol representing an invalid symbol (parsing failure) if the parsing fails. /// On failure, either raises an MissingQualifiedSymbol if the given continuation succeeds at the current position, /// or raises an InvalidQualifiedSymbol and advances until the given continuation succeeds otherwise. /// Does not apply the given continuation. -let private expectedNamespaceName continuation = - let path = multiSegmentSymbol ErrorCode.InvalidPathSegment |>> asSymbol - expected path ErrorCode.InvalidQualifiedSymbol ErrorCode.MissingQualifiedSymbol invalidSymbol continuation +let private expectedNamespaceName continuation = + let namespaceName = namespaceName |>> function + | None, _ -> (InvalidSymbol, Null) |> QsSymbol.New + | Some name, range -> (name |> NonNullable.New |> Symbol, range) |> QsSymbol.New + expected namespaceName ErrorCode.InvalidQualifiedSymbol ErrorCode.MissingQualifiedSymbol invalidSymbol continuation /// Parses the condition e.g. for if, elif and until clauses. /// Uses optTupleBrackets to raise the corresponding missing bracket errors if the condition is not within tuple brackets. diff --git a/src/QsCompiler/TextProcessor/SyntaxBuilder.fs b/src/QsCompiler/TextProcessor/SyntaxBuilder.fs index e45422090b..e4d35d9c86 100644 --- a/src/QsCompiler/TextProcessor/SyntaxBuilder.fs +++ b/src/QsCompiler/TextProcessor/SyntaxBuilder.fs @@ -345,17 +345,25 @@ let internal symbolNameLike errCode = preCheckStart = isSymbolStart, preCheckContinue = isSymbolContinuation) |> identifier getPosition .>>. id .>>. getPosition |>> fun ((p1, name), p2) -> name, (p1,p2) - let whenValid (name : string, range) = - let isReserved = name.StartsWith "__" && name.EndsWith "__" || InternalUse.CsKeywords.Contains name + let whenValid ((name : string, range), isBeforeDot) = + // REL0920: + // The warning for futureReservedUnderscorePattern should be replaced with an error in the future, + // and the first half of isReserved should be removed. + let futureReservedUnderscorePattern = name.Contains "__" || (isBeforeDot && name.EndsWith "_") + let isReserved = name.StartsWith "__" && name.EndsWith "__" || InternalUse.CsKeywords.Contains name let isCsKeyword = SyntaxFacts.IsKeywordKind (SyntaxFacts.GetKeywordKind name) let moreThanUnderscores = name.TrimStart('_').Length <> 0 if isCsKeyword || isReserved then buildError (preturn range) ErrorCode.InvalidUseOfReservedKeyword >>% None - else if moreThanUnderscores then preturn name |>> Some - else buildError (preturn range) errCode >>% None + elif not moreThanUnderscores then buildError (preturn range) errCode >>% None + elif futureReservedUnderscorePattern then buildWarning (preturn range) WarningCode.UseOfUnderscorePattern >>% Some name + else preturn name |>> Some let invalid = let invalidName = pchar '\'' |> opt >>. manySatisfy isDigit >>. identifier buildError (getPosition .>> invalidName .>>. getPosition) errCode >>% None - notFollowedBy qsReservedKeyword >>. attempt (identifier >>= whenValid <|> invalid) // NOTE: *needs* to fail on reserverd keywords here! + let validSymbolName = + let checkFollowedByDot = nextCharSatisfies ((=)'.') >>% true <|>% false + (identifier .>>. checkFollowedByDot) >>= whenValid + notFollowedBy qsReservedKeyword >>. attempt (validSymbolName <|> invalid) // NOTE: *needs* to fail on reserverd keywords here! /// Handles permissive parsing of a symbol: /// Uses symbolNameLike to generate suitable errors if the current symbol-like text is not a valid symbol in Q# @@ -379,14 +387,6 @@ let internal asQualifiedSymbol ((path, sym), range : Position * Position) = let (ns, sym) = (String.concat "." parts.[0..parts.Length-2]) |> NonNullable.New, parts.[parts.Length-1] |> NonNullable.New (QualifiedSymbol (ns, sym), range) |> QsSymbol.New -/// Given the path, the symbol and the range parsed by multiSegmentSymbol, -/// concatenates all path segments and the symbol with a dot, and returns a simple Symbol of the concatenated string as QsSymbol. -/// Returns a QsSymbol corresponding to an invalid symbol if the path contains segments that are None (i.e. invalid). -let internal asSymbol ((path, sym), range : Position * Position) = - let names = [for segment in path do yield segment ] @ [sym] - if names |> List.contains None then (InvalidSymbol, Null) |> QsSymbol.New - else (names |> List.choose id |> String.concat "." |> NonNullable.New |> Symbol, range) |> QsSymbol.New - /// Handles permissive parsing of a qualified symbol: /// Uses symbolNameLike for each path fragment separated by dots /// to generate suitable errors if the symbol-like fragment text is not a valid symbol in Q#.