Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.

Fix for #594. #614

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions src/Jupyter/ConfigurationSource/IConfigurationSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,18 +232,28 @@ public TEnum GetOptionOrDefault<TEnum>(string optionName, TEnum defaultValue) wh
public string WorkspaceName =>
GetOptionOrDefault("azure.quantum.workspace.name", string.Empty);

/// <summary>
/// If set to <c>true</c>, shows additional performance breakdowns
/// from within the notebook.
/// </summary>
public bool InternalShowPerf =>
GetOptionOrDefault("internal.showPerf", false);

/// <summary>
/// If set to <c>true</c>, shows additional performance breakdowns
/// forwarded from the Q# compiler.
/// </summary>
public bool InternalShowCompilerPerf =>
GetOptionOrDefault("internal.showCompilerPerf", false);
// We also want to support some internal-only options that are disabled
// in release builds, making it easier to diagnose some internals
// during local development. When in release mode, we'll disable setting
// these options and always use defaults.

#if DEBUG
public TEnum GetInternalOptionOrDefault<TEnum>(string optionName, TEnum defaultValue) where TEnum : struct =>
GetOptionOrDefault("internal." + optionName, defaultValue, Enum.Parse<TEnum>);
public string GetInternalOptionOrDefault(string optionName, string defaultValue) =>
GetOptionOrDefault("internal." + optionName, defaultValue, e => e);
public bool GetInternalOptionOrDefault(string optionName, bool defaultValue) =>
GetOptionOrDefault("internal." + optionName, defaultValue, bool.Parse);
#else
public string GetInternalOptionOrDefault(string optionName, string defaultValue) =>
defaultValue;
public TEnum GetInternalOptionOrDefault<TEnum>(string optionName, TEnum defaultValue) where TEnum : struct =>
defaultValue;
public bool GetInternalOptionOrDefault(string optionName, bool defaultValue) =>
defaultValue;
#endif

public bool InternalHelpShowAllAttributes =>
GetInternalOptionOrDefault("help.showAllAttributes", false);
}
}
29 changes: 23 additions & 6 deletions src/Jupyter/SymbolResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,16 @@ public class IQSharpSymbol : ISymbol
/// <summary>
/// </summary>
[JsonProperty("inputs", NullValueHandling=NullValueHandling.Ignore)]
public ImmutableDictionary<string?, string?>? Inputs { get; private set; } = null;
public ImmutableDictionary<string?, string?> Inputs { get; private set; }

/// <summary>
/// </summary>
[JsonProperty("examples", NullValueHandling=NullValueHandling.Ignore)]
public ImmutableList<string?> Examples { get; private set; }


[JsonProperty("type_parameters", NullValueHandling=NullValueHandling.Ignore)]
public ImmutableDictionary<string?, string?> TypeParameters { get; private set; }

// TODO: continue exposing documentation here.

Expand All @@ -94,12 +103,20 @@ public IQSharpSymbol(OperationInfo op)
.Operation
.GetStringAttributes("Description")
.SingleOrDefault();
var inputs = this
this.Inputs = this
.Operation
.GetDictionaryAttributes("Input")
.ToImmutableDictionary();
this.TypeParameters = this
.Operation
.GetDictionaryAttributes("TypeParameter")
.ToImmutableDictionary();
this.Examples = this
.Operation
.GetDictionaryAttributes("Input");
this.Inputs = inputs.Count >= 0
? inputs.ToImmutableDictionary()
: null;
.GetStringAttributes("Example")
.Where(ex => ex != null)
.ToImmutableList();

}
}

Expand Down
13 changes: 9 additions & 4 deletions src/Kernel/IQSharpEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ private void AttachCommsListeners()
};
}

private void RegisterDisplayEncoder<T>()
where T: IResultEncoder =>
RegisterDisplayEncoder(ActivatorUtilities.CreateInstance<T>(services));


private async Task StartAsync()
{
base.Start();
Expand Down Expand Up @@ -214,11 +219,11 @@ private async Task StartAsync()


logger.LogDebug("Registering IQ# display and JSON encoders.");
RegisterDisplayEncoder(new IQSharpSymbolToHtmlResultEncoder());
RegisterDisplayEncoder(new IQSharpSymbolToTextResultEncoder());
RegisterDisplayEncoder<IQSharpSymbolToHtmlResultEncoder>();
RegisterDisplayEncoder<IQSharpSymbolToTextResultEncoder>();
RegisterDisplayEncoder(new TaskStatusToTextEncoder());
RegisterDisplayEncoder(new StateVectorToHtmlResultEncoder(configurationSource));
RegisterDisplayEncoder(new StateVectorToTextResultEncoder(configurationSource));
RegisterDisplayEncoder<StateVectorToHtmlResultEncoder>();
RegisterDisplayEncoder<StateVectorToTextResultEncoder>();
RegisterDisplayEncoder(new DataTableToHtmlEncoder());
RegisterDisplayEncoder(new DataTableToTextEncoder());
RegisterDisplayEncoder(new DisplayableExceptionToHtmlEncoder());
Expand Down
178 changes: 178 additions & 0 deletions src/Kernel/SymbolEncoders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,125 @@
using Microsoft.Jupyter.Core;
using Markdig;
using Microsoft.Quantum.IQSharp.Jupyter;
using System.Linq;
using Microsoft.Quantum.QsCompiler.SyntaxTree;
using System;
using Microsoft.Quantum.QsCompiler.SyntaxTokens;
using System.Threading.Tasks;
using System.Net.Http;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json.Linq;

namespace Microsoft.Quantum.IQSharp.Kernel
{
using ResolvedTypeKind = QsTypeKind<ResolvedType, UserDefinedType, QsTypeParameter, CallableInformation>;

// NB: These are defined in the documentation generation tool in the
// compiler, and should not be duplicated here. These should be removed
// before merging to main.
internal static class SyntaxExtensions
{
internal static List<(string, ResolvedType)> InputDeclarations(this QsTuple<LocalVariableDeclaration<QsLocalSymbol>> items) => items switch
{
QsTuple<LocalVariableDeclaration<QsLocalSymbol>>.QsTuple tuple =>
tuple.Item.SelectMany(
item => item.InputDeclarations())
.ToList(),
QsTuple<LocalVariableDeclaration<QsLocalSymbol>>.QsTupleItem item =>
new List<(string, ResolvedType)>
{
(
item.Item.VariableName switch
{
QsLocalSymbol.ValidName name => name.Item,
_ => "__invalid__",
},
item.Item.Type),
},
_ => throw new Exception(),
};

internal static string ToSyntax(this ResolvedCharacteristics characteristics) =>
characteristics.SupportedFunctors switch
{
{ IsNull: true } => "",
{ Item: { Count: 0 } } => "",

// Be sure to add the leading space before is!
{ Item: var functors } => $" is {string.Join(" + ", functors.Select(functor => functor.ToSyntax()))}",
};

internal static string ToSyntax(this QsFunctor functor) =>
functor.Tag switch
{
QsFunctor.Tags.Adjoint => "Adj",
QsFunctor.Tags.Controlled => "Ctl",
_ => "__invalid__",
};

// TODO: memoize
internal async static Task<string?> TryResolveXref(string xref)
{
var client = new HttpClient();
try
{
var response = await client.GetStringAsync($"https://xref.docs.microsoft.com/query?uid={xref}");
var json = JToken.Parse(response);
return json.Value<string>("href");
}
catch
{
return null;
}
}

internal async static Task<string> ToLink(string text, string xref, string? fragment = null)
{
var href = await TryResolveXref(xref);
if (href == null)
{
return text;
}
else
{
return $"<a href=\"{href}{(fragment == null ? "" : $"#{fragment}")}</a>";
}
}

internal static async Task<string> ToHtml(this ResolvedType type) => type.Resolution switch
{
ResolvedTypeKind.ArrayType array => $"{await array.Item.ToHtml()}[]",
ResolvedTypeKind.Function function =>
$"{await function.Item1.ToHtml()} -> {await function.Item2.ToHtml()}",
ResolvedTypeKind.Operation operation =>
$"{await operation.Item1.Item1.ToHtml()} => {await operation.Item1.Item2.ToHtml()} "
+ operation.Item2.Characteristics.ToSyntax(),
ResolvedTypeKind.TupleType tuple => "(" + string.Join(
",", tuple.Item.Select(async type => await type.ToHtml())) + ")",
ResolvedTypeKind.UserDefinedType udt => await udt.Item.ToHtml(),
ResolvedTypeKind.TypeParameter typeParam =>
$"'{typeParam.Item.TypeName}",
_ => type.Resolution.Tag switch
{
ResolvedTypeKind.Tags.BigInt => await ToLink("BigInt", "xref:microsoft.quantum.qsharp.valueliterals", "bigint-literals"),
ResolvedTypeKind.Tags.Bool => await ToLink("Bool", "xref:microsoft.quantum.qsharp.valueliterals", "bool-literals"),
ResolvedTypeKind.Tags.Double => await ToLink("Double", "xref:microsoft.quantum.qsharp.valueliterals", "double-literals"),
ResolvedTypeKind.Tags.Int => await ToLink("Int", "xref:microsoft.quantum.qsharp.valueliterals", "int-literals"),
ResolvedTypeKind.Tags.Pauli => await ToLink("Pauli", "xref:microsoft.quantum.qsharp.valueliterals", "pauli-literals"),
ResolvedTypeKind.Tags.Qubit => await ToLink("Qubit", "xref:microsoft.quantum.qsharp.valueliterals", "qubit-literals"),
ResolvedTypeKind.Tags.Range => await ToLink("Range", "xref:microsoft.quantum.qsharp.valueliterals", "range-literals"),
ResolvedTypeKind.Tags.String => await ToLink("String", "xref:microsoft.quantum.qsharp.valueliterals", "string-literals"),
ResolvedTypeKind.Tags.UnitType => await ToLink("Unit", "xref:microsoft.quantum.qsharp.valueliterals", "unit-literal"),
ResolvedTypeKind.Tags.Result => await ToLink("Result", "xref:microsoft.quantum.qsharp.valueliterals", "result-literal"),
ResolvedTypeKind.Tags.InvalidType => "__invalid__",
_ => $"__invalid<{type.Resolution.ToString()}>__",
},
};

internal static async Task<string> ToHtml(this UserDefinedType type) =>
await ToLink($"{type.Namespace}.{type.Name}", $"{type.Namespace}.{type.Name}");
}

/// <summary>
/// Encodes Q# symbols into plain text, e.g. for printing to the console.
/// </summary>
Expand Down Expand Up @@ -39,15 +155,24 @@ public class IQSharpSymbolToTextResultEncoder : IResultEncoder
/// </summary>
public class IQSharpSymbolToHtmlResultEncoder : IResultEncoder
{
private readonly IConfigurationSource ConfigurationSource;

/// <inheritdoc />
public string MimeType => MimeTypes.Html;

public IQSharpSymbolToHtmlResultEncoder(IConfigurationSource configurationSource)
{
this.ConfigurationSource = configurationSource;
}

/// <summary>
/// Checks if a displayable object is an IQ# symbol, and if so,
/// returns an encoding of that symbol into HTML.
/// </summary>
public EncodedData? Encode(object displayable)
{
var tableEncoder = new TableToHtmlDisplayEncoder();

if (displayable is IQSharpSymbol symbol)
{
var codeLink =
Expand All @@ -58,10 +183,63 @@ public class IQSharpSymbolToHtmlResultEncoder : IResultEncoder
var description = symbol.Description != null
? "<h5>Description</h5>" + Markdown.ToHtml(symbol.Description)
: string.Empty;
// TODO: Make sure to list
// type parameters even if they're not documented.
var typeParams = symbol.TypeParameters.Count > 0
? "<h5>Type Parameters</h5>\n" +
tableEncoder.Encode(new Table<KeyValuePair<string?, string?>>
{
Columns = new List<(string, Func<KeyValuePair<string?, string?>, string>)>
{
("", input => $"<code>{input.Key}</code>"),
("", input => Markdown.ToHtml(input.Value))
},
Rows = symbol.TypeParameters.ToList()
})!.Value.Data
: string.Empty;

// TODO: Check if Inputs is empty before formatting, make sure
// to list even if they're not documented.
var inputDecls = symbol.Operation.Header.ArgumentTuple.InputDeclarations().ToDictionary(item => item.Item1, item => item.Item2);
var inputs = symbol.Inputs.Count > 0
? "<h5>Inputs</h5>\n" + tableEncoder.Encode(new Table<KeyValuePair<string?, string?>>
{
Columns = new List<(string, Func<KeyValuePair<string?, string?>, string>)>
{
("", input => $"<code>{input.Key}</code>"),
("", input => $"<code>{inputDecls[input.Key].ToHtml().Result}</code>"),
("", input => Markdown.ToHtml(input.Value))
},
Rows = symbol.Inputs.ToList()
})!.Value.Data
: string.Empty;
var examples = string.Join("\n",
symbol.Examples.Select(example => $"<h5>Example</h5>\n{Markdown.ToHtml(example)}")
);

var attributes = ConfigurationSource.InternalHelpShowAllAttributes
? tableEncoder.Encode(new Table<QsDeclarationAttribute>
{
Columns = new List<(string, Func<QsDeclarationAttribute, string>)>
{
("Name", attr => attr.TypeId switch
{
{ Item: UserDefinedType udt } => $"{udt.Namespace}.{udt.Name}",
_ => "<unknown>"
}),
("Value", attr => attr.Argument.ToString())
},
Rows = symbol.Operation.Header.Attributes.ToList()
})!.Value.Data
: "";
return $@"
<h4><i class=""fa fas fa-terminal""></i> {symbol.Name} {codeLink}</h4>
{summary}
{description}
{typeParams}
{inputs}
{examples}
{attributes}
".ToEncodedData();

}
Expand Down
2 changes: 2 additions & 0 deletions src/Tests/TelemetryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ public async Task CompileCode()
var logger = GetAppLogger(services);

var snippets = services.GetService<ISnippets>();

// Filter out device capabilities, since they may only be sent
// well after we initialize the workspace.
Func<EventProperties, bool> filter =
Expand Down Expand Up @@ -377,6 +378,7 @@ public async Task LoadProjects()

var ws = services.GetService<IWorkspace>();


// Filter out device capabilities, since they may only be sent
// well after we initialize the workspace.
Func<EventProperties, bool> filter =
Expand Down