/// <summary> /// Logs the generated Q# code for the part of the given evaluated syntax tree that corresponds to each file /// in the given compilation as Information using the given logger. /// If the given evaluated tree is null, queries the tree contained in the given compilation instead. /// If the id of a file is consistent with the one assigned to a code snippet, /// strips the lines of code that correspond to the wrapping defined by WrapSnippet. /// Throws an ArgumentException if this is not possible because the given syntax tree is inconsistent with that wrapping. /// Throws an ArgumentNullException if the given compilation is null. /// </summary> private static void PrintGeneratedQs(IEnumerable <QsNamespace> evaluatedTree, Compilation compilation, ILogger logger) { if (compilation == null) { throw new ArgumentNullException(nameof(compilation)); } evaluatedTree = evaluatedTree ?? compilation.SyntaxTree.Values; foreach (var file in compilation.SourceFiles) { if (Options.IsCodeSnippet(file)) { var subtree = evaluatedTree.Select(ns => FilterBySourceFile.Apply(ns, file)).Where(ns => ns.Elements.Any()); var code = new string[] { "" }.Concat(StripSnippetWrapping(subtree).Select(FormatCompilation.FormatStatement)); logger.Log(InformationCode.FormattedQsCode, Enumerable.Empty <string>(), messageParam: code.ToArray()); } else { var imports = evaluatedTree.ToImmutableDictionary(ns => ns.Name, ns => compilation.OpenDirectives(file, ns.Name).ToImmutableArray()); SyntaxTreeToQs.Apply(out List <ImmutableDictionary <NonNullable <string>, string> > generated, evaluatedTree, (file, imports)); var code = new string[] { "" }.Concat(generated.Single().Values.Select(nsCode => $"{nsCode}{Environment.NewLine}")); logger.Log(InformationCode.FormattedQsCode, Enumerable.Empty <string>(), file.Value, messageParam: code.ToArray()); }; } }
/// <summary> /// Logs the part of the given evaluated syntax tree that corresponds to each file /// in the given compilation as Information using the given logger. /// If the given evaluated tree is null, queries the tree contained in the given compilation instead. /// If the id of a file is consistent with the one assigned to a code snippet, /// strips the lines of code that correspond to the wrapping defined by WrapSnippet. /// Throws an ArgumentException if this is not possible because the given syntax tree is inconsistent with that wrapping. /// Throws an ArgumentNullException if the given compilation is null. /// </summary> private static void PrintSyntaxTree(IEnumerable <QsNamespace> evaluatedTree, Compilation compilation, ILogger logger) { if (compilation == null) { throw new ArgumentNullException(nameof(compilation)); } evaluatedTree = evaluatedTree ?? compilation.SyntaxTree.Values; foreach (var file in compilation.SourceFiles) { var stripWrapping = Options.IsCodeSnippet(file); var subtree = evaluatedTree.Select(ns => FilterBySourceFile.Apply(ns, file)).Where(ns => ns.Elements.Any()); void PrintTree(string serialization) => logger.Log( InformationCode.BuiltSyntaxTree, Enumerable.Empty <string>(), stripWrapping ? null : file.Value, messageParam: new string[] { "", serialization }); if (!stripWrapping) { PrintTree(JsonConvert.SerializeObject(subtree, Newtonsoft.Json.Formatting.Indented)); } else { PrintTree(JsonConvert.SerializeObject(StripSnippetWrapping(subtree), Newtonsoft.Json.Formatting.Indented)); } } }
/// <summary> /// Generates formatted Q# code based on the part of the syntax tree that corresponds to each file in the given compilation. /// If the id of a file is consistent with the one assigned to a code snippet, /// strips the lines of code that correspond to the wrapping defined by WrapSnippet. /// Throws an ArgumentException if this is not possible because the given syntax tree is inconsistent with that wrapping. /// Throws an ArgumentNullException if the given compilation is null. /// </summary> private static IEnumerable <string> GenerateQsCode(Compilation compilation, NonNullable <string> file, ILogger logger) { if (compilation == null) { throw new ArgumentNullException(nameof(compilation)); } if (Options.IsCodeSnippet(file)) { var subtree = compilation.SyntaxTree.Values.Select(ns => FilterBySourceFile.Apply(ns, file)).Where(ns => ns.Elements.Any()); return(DiagnoseCompilation.StripSnippetWrapping(subtree).Select(SyntaxTreeToQsharp.Default.ToCode)); } else { var imports = compilation.SyntaxTree.Values .ToImmutableDictionary(ns => ns.Name, ns => compilation.OpenDirectives(file, ns.Name).ToImmutableArray()); var success = SyntaxTreeToQsharp.Apply(out List <ImmutableDictionary <NonNullable <string>, string> > generated, compilation.SyntaxTree.Values, (file, imports)); if (!success) { logger?.Log(WarningCode.UnresolvedItemsInGeneratedQs, Enumerable.Empty <string>(), file.Value); } return(generated.Single().Select(entry => { var nsComments = compilation.NamespaceComments(file, entry.Key); string FormatComments(IEnumerable <string> comments) => string.Join( Environment.NewLine, comments.Select(line => line.Trim()).Select(line => string.IsNullOrWhiteSpace(line) ? "" : $"// {line}")) .Trim(); var leadingComments = entry.Value.StartsWith("///") ? $"{FormatComments(nsComments.OpeningComments)}{Environment.NewLine}" : FormatComments(nsComments.OpeningComments); var trailingComments = FormatComments(nsComments.ClosingComments); var code = new string[] { leadingComments, entry.Value, trailingComments }.Where(s => !string.IsNullOrWhiteSpace(s)); return string.Join(Environment.NewLine, code); })); } }
/// <summary> /// Builds the corresponding .net core assembly from the Q# syntax tree. /// </summary> private AssemblyInfo BuildAssembly(ImmutableDictionary <Uri, string> sources, CompilerMetadata metadata, QSharpLogger logger, string dllName) { logger.LogDebug($"Compiling the following Q# files: {string.Join(",", sources.Keys.Select(f => f.LocalPath))}"); var qsCompilation = this.UpdateCompilation(sources, metadata.QsMetadatas, logger); if (logger.HasErrors) { return(null); } try { // Generate C# simulation code from Q# syntax tree and convert it into C# syntax trees: var trees = new List <SyntaxTree>(); NonNullable <string> GetFileId(Uri uri) => CompilationUnitManager.TryGetFileId(uri, out var id) ? id : NonNullable <string> .New(uri.AbsolutePath); foreach (var file in sources.Keys) { var sourceFile = GetFileId(file); var code = SimulationCode.generate(sourceFile, CodegenContext.Create(qsCompilation.Namespaces)); var tree = CSharpSyntaxTree.ParseText(code, encoding: UTF8Encoding.UTF8); trees.Add(tree); logger.LogDebug($"Generated the following C# code for {sourceFile.Value}:\n=============\n{code}\n=============\n"); } // Compile the C# syntax trees: var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Debug); var compilation = CSharpCompilation.Create( Path.GetFileNameWithoutExtension(dllName), trees, metadata.RoslynMetadatas, options); // Generate the assembly from the C# compilation: using (var ms = new MemoryStream()) using (var bsonStream = new MemoryStream()) { using var writer = new BsonDataWriter(bsonStream) { CloseOutput = false }; var fromSources = qsCompilation.Namespaces.Select(ns => FilterBySourceFile.Apply(ns, s => s.Value.EndsWith(".qs"))); Json.Serializer.Serialize(writer, new QsCompilation(fromSources.ToImmutableArray(), qsCompilation.EntryPoints)); var resourceDescription = new ResourceDescription ( resourceName: QsCompiler.ReservedKeywords.DotnetCoreDll.ResourceName, dataProvider: () => new MemoryStream(bsonStream.ToArray()), isPublic: true ); var result = compilation.Emit(ms, manifestResources: new[] { resourceDescription }); if (!result.Success) { IEnumerable <Diagnostic> failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); logger.LogError("IQS000", "Could not compile Roslyn dll from working folder."); foreach (Diagnostic diagnostic in failures) { logger.LogError(diagnostic.Id, diagnostic.GetMessage()); } return(null); } else { logger.LogDebug($"Assembly successfully generated. Caching at {dllName}."); var data = ms.ToArray(); try { File.WriteAllBytes(dllName, data); } catch (Exception e) { logger.LogError("IQS001", $"Unable to save assembly cache: {e.Message}."); } return(new AssemblyInfo(Assembly.Load(data), dllName, fromSources.ToArray())); } } } catch (Exception e) { logger.LogError("IQS002", $"Unexpected error compiling assembly: {e.Message}."); return(null); } }
// interface methods public bool Transformation(QsCompilation compilation, out QsCompilation transformed) { transformed = FilterSourceFiles.Apply(compilation); var manager = new CompilationUnitManager(); // get source code from examples var fileManagers = ExamplesInDocs.Extract(transformed) .Select(g => InitializeFileManager(g, compilation, g.Key)) .Where(m => m != null).ToImmutableHashSet(); manager.AddOrUpdateSourceFilesAsync(fileManagers, suppressVerification: true); var sourceFiles = fileManagers.Select(m => m.FileName).ToImmutableHashSet(); bool IsGeneratedSourceFile(NonNullable <string> source) => sourceFiles.Contains(source); // get everything contained in the compilation as references var refName = NonNullable <string> .New(Path.GetFullPath(ReferenceSource)); var refHeaders = new References.Headers(refName, DllToQs.Rename(compilation).Namespaces); var refDict = new Dictionary <NonNullable <string>, References.Headers> { { refName, refHeaders } }; var references = new References(refDict.ToImmutableDictionary()); manager.UpdateReferencesAsync(references); // compile the examples in the doc comments and add any diagnostics to the list of generated diagnostics var built = manager.Build(); var diagnostics = built.Diagnostics(); this.Diagnostics.AddRange(diagnostics.Select(d => IRewriteStep.Diagnostic.Create(d, IRewriteStep.Stage.Transformation))); if (diagnostics.Any(d => d.Severity == VS.DiagnosticSeverity.Error)) { return(false); } // add the extracted namespace elements from doc comments to the transformed compilation var toBeAdded = built.BuiltCompilation.Namespaces.ToImmutableDictionary( ns => ns.Name, ns => FilterBySourceFile.Apply(ns, IsGeneratedSourceFile)); var namespaces = compilation.Namespaces.Select(ns => toBeAdded.TryGetValue(ns.Name, out var add) ? new QsNamespace(ns.Name, ns.Elements.AddRange(add.Elements), ns.Documentation) : ns); var addedNamespaces = toBeAdded.Values.Where(add => !compilation.Namespaces.Any(ns => ns.Name.Value == add.Name.Value)); transformed = new QsCompilation(namespaces.Concat(addedNamespaces).ToImmutableArray(), compilation.EntryPoints); // mark all newly created callables that take unit as argument as unit tests to run on the QuantumSimulator and ResourcesEstimator bool IsSuitableForUnitTest(QsCallable c) => IsGeneratedSourceFile(c.SourceFile) && c.Signature.ArgumentType.Resolution.IsUnitType; var qsimAtt = AttributeUtils.BuildAttribute(BuiltIn.Test.FullName, AttributeUtils.StringArgument(Constants.QuantumSimulator)); var restAtt = AttributeUtils.BuildAttribute(BuiltIn.Test.FullName, AttributeUtils.StringArgument(Constants.ResourcesEstimator)); transformed = AttributeUtils.AddToCallables(transformed, (qsimAtt, IsSuitableForUnitTest), (restAtt, IsSuitableForUnitTest)); return(true); }