diff --git a/src/QsCompiler/Core/ConstructorExtensions.fs b/src/QsCompiler/Core/ConstructorExtensions.fs index 2c3a8226ec..65cf870ed0 100644 --- a/src/QsCompiler/Core/ConstructorExtensions.fs +++ b/src/QsCompiler/Core/ConstructorExtensions.fs @@ -83,7 +83,7 @@ type TypedExpression with /// the ResolvedType is set to the type constructed by resolving it using ResolveTypeParameters and the given look-up. static member New (expr, typeParamResolutions : ImmutableDictionary<_,_>, exType, exInfo, range) = { Expression = expr - TypeArguments = typeParamResolutions |> Seq.map (fun kv -> fst kv.Key, snd kv.Key, kv.Value) |> ImmutableArray.CreateRange + TypeArguments = typeParamResolutions |> TypedExpression.AsTypeArguments ResolvedType = ResolvedType.ResolveTypeParameters typeParamResolutions exType InferredInformation = exInfo Range = range diff --git a/src/QsCompiler/DataStructures/SyntaxTree.fs b/src/QsCompiler/DataStructures/SyntaxTree.fs index 6ebbbb14a3..b2db81ed6c 100644 --- a/src/QsCompiler/DataStructures/SyntaxTree.fs +++ b/src/QsCompiler/DataStructures/SyntaxTree.fs @@ -364,6 +364,11 @@ type TypedExpression = { member this.TypeParameterResolutions = this.TypeArguments.ToImmutableDictionary((fun (origin, name, _) -> origin, name), (fun (_,_,t) -> t)) + /// Given a dictionary containing the type resolutions for an expression, + /// returns the corresponding ImmutableArray to initialize the TypeArguments with. + static member AsTypeArguments (typeParamResolutions : ImmutableDictionary<_,_>) = + typeParamResolutions |> Seq.map (fun kv -> fst kv.Key, snd kv.Key, kv.Value) |> ImmutableArray.CreateRange + /// Returns true if the expression is a call-like expression, and the arguments contain a missing expression. /// Returns false otherwise. static member public IsPartialApplication kind = diff --git a/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs b/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs index 3158e6fda2..6282abcb54 100644 --- a/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs +++ b/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs @@ -101,9 +101,9 @@ and private SymbolsFromExpr item : QsSymbol list * QsType list * QsExpression li | QsExpressionKind.InvalidExpr -> [], [], [item] let private AttributeAsCallExpr (sym : QsSymbol, ex : QsExpression) = - let compinedRange = QsPositionInfo.CombinedRange (sym.Range, ex.Range) + let combinedRange = QsPositionInfo.CombinedRange (sym.Range, ex.Range) let id = {Expression = QsExpressionKind.Identifier (sym, Null); Range = sym.Range} - {Expression = QsExpressionKind.CallLikeExpression(id, ex); Range = compinedRange} + {Expression = QsExpressionKind.CallLikeExpression(id, ex); Range = combinedRange} let rec private SymbolDeclarations (sym : QsSymbol) = match sym.Symbol with diff --git a/src/QsCompiler/Tests.Compiler/CallGraphTests.fs b/src/QsCompiler/Tests.Compiler/CallGraphTests.fs new file mode 100644 index 0000000000..b47843038d --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/CallGraphTests.fs @@ -0,0 +1,306 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.QsCompiler.Testing + +open System +open System.Collections.Generic +open System.Collections.Immutable +open Microsoft.Quantum.QsCompiler.DataTypes +open Microsoft.Quantum.QsCompiler.SyntaxExtensions +open Microsoft.Quantum.QsCompiler.SyntaxTokens +open Microsoft.Quantum.QsCompiler.SyntaxTree +open Microsoft.Quantum.QsCompiler.Transformations +open Xunit +open Xunit.Abstractions + + +type CallGraphTests (output:ITestOutputHelper) = + + let qualifiedName name = + ("NS" |> NonNullable.New, name |> NonNullable.New) |> QsQualifiedName.New + + let typeParameter (id : string) = + let pieces = id.Split(".") + Assert.True(pieces.Length = 2) + let parent = qualifiedName pieces.[0] + let name = pieces.[1] |> NonNullable.New + QsTypeParameter.New (parent, name, Null) + + let resolution (res : (QsTypeParameter * QsTypeKind<_,_,_,_>) list) = + res.ToImmutableDictionary((fun (tp,_) -> tp.Origin, tp.TypeName), snd >> ResolvedType.New) + + let Foo = qualifiedName "Foo" + let Bar = qualifiedName "Bar" + + let FooA = typeParameter "Foo.A" + let FooB = typeParameter "Foo.B" + let BarA = typeParameter "Bar.A" + let BarC = typeParameter "Bar.C" + let BazA = typeParameter "Baz.A" + + static member CheckResolution (parent, expected : IDictionary<_,_>, [] resolutions) = + let expectedKeys = ImmutableHashSet.CreateRange(expected.Keys) + let compareWithExpected (d : ImmutableDictionary<_,_>) = + let keysMismatch = expectedKeys.SymmetricExcept d.Keys + keysMismatch.Count = 0 && expected |> Seq.exists (fun kv -> d.[kv.Key] <> kv.Value) |> not + let mutable combined = ImmutableDictionary.Empty + let success = CallGraph.TryCombineTypeResolutions(parent, &combined, resolutions) + Assert.True(compareWithExpected combined, "combined resolutions did not match the expected ones") + success + + static member AssertResolution (parent, expected, [] resolutions) = + let success = CallGraphTests.CheckResolution (parent, expected, resolutions) + Assert.True(success, "Combining type resolutions was not successful.") + + static member AssertResolutionFailure (parent, expected, [] resolutions) = + let success = CallGraphTests.CheckResolution (parent, expected, resolutions) + Assert.False(success, "Combining type resolutions should have failed.") + + + [] + [] + member this.``Type resolution: resolution to concrete`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + (FooB, Int) + ] + let res2 = resolution [ + (BarA, Int) + ] + let expected = resolution [ + (FooA, Int) + (FooB, Int) + ] + CallGraphTests.AssertResolution(Foo, expected, res1, res2) + + [] + [] + member this.``Type resolution: resolution to type parameter`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + ] + let res2 = resolution [ + (BarA, BazA |> TypeParameter) + ] + let expected = resolution [ + (FooA, BazA |> TypeParameter) + ] + CallGraphTests.AssertResolution(Foo, expected, res1, res2) + + [] + [] + member this.``Type resolution: resolution via identity mapping`` () = + + let res1 = resolution [ + (FooA, FooA |> TypeParameter) + ] + let res2 = resolution [ + (FooA, Int) + ] + let expected = resolution [ + (FooA, Int) + ] + CallGraphTests.AssertResolution(Foo, expected, res1, res2) + + [] + [] + member this.``Type resolution: multi-stage resolution`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + ] + let res2 = resolution [ + (BarA, Int) + (FooB, Int) + ] + let expected = resolution [ + (FooA, Int) + (FooB, Int) + ] + CallGraphTests.AssertResolution(Foo, expected, res1, res2) + + [] + [] + member this.``Type resolution: multiple resolutions to concrete`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + (FooB, BarA |> TypeParameter) + ] + let res2 = resolution [ + (BarA, Int) + ] + let expected = resolution [ + (FooA, Int) + (FooB, Int) + ] + CallGraphTests.AssertResolution(Foo, expected, res1, res2) + + [] + [] + member this.``Type resolution: multiple resolutions to type parameter`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + (FooB, BarA |> TypeParameter) + ] + let res2 = resolution [ + (BarA, BazA |> TypeParameter) + ] + let res3 = resolution [ + (BazA, Double) + ] + let expected = resolution [ + (FooA, Double) + (FooB, Double) + ] + CallGraphTests.AssertResolution(Foo, expected, res1, res2, res3) + + [] + [] + member this.``Type resolution: multi-stage resolution of multiple resolutions to concrete`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + ] + let res2 = resolution [ + (FooB, BarA |> TypeParameter) + ] + let res3 = resolution [ + (BarA, Int) + ] + let expected = resolution [ + (FooA, Int) + (FooB, Int) + ] + CallGraphTests.AssertResolution(Foo, expected, res1, res2, res3) + + [] + [] + member this.``Type resolution: redundant resolution to concrete`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + ] + let res2 = resolution [ + (BarA, Int) + (FooA, Int) + ] + let expected = resolution [ + (FooA, Int) + ] + CallGraphTests.AssertResolution(Foo, expected, res1, res2) + + [] + [] + member this.``Type resolution: redundant resolution to type parameter`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + ] + let res2 = resolution [ + (BarA, BazA |> TypeParameter) + ] + let res3 = resolution [ + (FooA, BazA |> TypeParameter) + ] + let expected = resolution [ + (FooA, BazA |> TypeParameter) + ] + CallGraphTests.AssertResolution(Foo, expected, res1, res2, res3) + + [] + [] + member this.``Type resolution: conflicting resolution to concrete`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + ] + let res2 = resolution [ + (BarA, Int) + (FooA, Double) + ] + let expected = resolution [ + (FooA, Double) + ] + CallGraphTests.AssertResolutionFailure(Foo, expected, res1, res2) + + [] + [] + member this.``Type resolution: conflicting resolution to type parameter`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + ] + let res2 = resolution [ + (BarA, BazA |> TypeParameter) + (FooA, BarC |> TypeParameter) + ] + let expected = resolution [ + (FooA, BarC |> TypeParameter) + ] + CallGraphTests.AssertResolutionFailure(Foo, expected, res1, res2) + + [] + [] + member this.``Type resolution: direct resolution to native`` () = + + let res1 = resolution [ + (FooA, FooA |> TypeParameter) + ] + let expected = resolution [ + (FooA, FooA |> TypeParameter) + ] + CallGraphTests.AssertResolution(Foo, expected, res1) + + [] + [] + member this.``Type resolution: indirect resolution to native`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + ] + let res2 = resolution [ + (BarA, BazA |> TypeParameter) + ] + let res3 = resolution [ + (BazA, FooA |> TypeParameter) + ] + let expected = resolution [ + (FooA, FooA |> TypeParameter) + ] + CallGraphTests.AssertResolution(Foo, expected, res1, res2, res3) + + [] + [] + member this.``Type resolution: direct resolution constrains native`` () = + + let res1 = resolution [ + (FooA, FooB |> TypeParameter) + ] + let expected = resolution [ + (FooA, FooB |> TypeParameter) + ] + CallGraphTests.AssertResolutionFailure(Foo, expected, res1) + + [] + [] + member this.``Type resolution: indirect resolution constrains native`` () = + + let res1 = resolution [ + (FooA, BarA |> TypeParameter) + ] + let res2 = resolution [ + (BarA, BazA |> TypeParameter) + ] + let res3 = resolution [ + (BazA, FooB |> TypeParameter) + ] + let expected = resolution [ + (FooA, FooB |> TypeParameter) + ] + CallGraphTests.AssertResolutionFailure(Foo, expected, res1, res2, res3) + diff --git a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj index 879f089a5d..897df1a5ab 100644 --- a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj +++ b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj @@ -143,6 +143,7 @@ + @@ -179,9 +180,7 @@ $(MSBuildThisFileDirectory)..\TestTargets\Simulation\Example\bin\$(Configuration)\netcoreapp3.1\Example.dll $(MSBuildThisFileDirectory)..\TestTargets\Libraries\Library1\bin\$(Configuration)\netcoreapp3.1\Library1.dll - + diff --git a/src/QsCompiler/TextProcessor/QsKeywords.fs b/src/QsCompiler/TextProcessor/QsKeywords.fs index b873770526..903955efd0 100644 --- a/src/QsCompiler/TextProcessor/QsKeywords.fs +++ b/src/QsCompiler/TextProcessor/QsKeywords.fs @@ -47,7 +47,7 @@ let private addFragmentHeader word = _FragmentHeaders.Add word |> ignore qsKeyword word -/// Given the keyword for two functors, constructs and returns the keyword for the compined functor +/// Given the keyword for two functors, constructs and returns the keyword for the combined functor /// under the assumption tha the order of the functors does not matter. /// Adds the keyword for the combined functor to the list of QsReservedKeywords, QsLanguageKeywords and QsFragmentHeaders. let private addFunctorCombination (word1 : QsKeyword, word2 : QsKeyword) = diff --git a/src/QsCompiler/TextProcessor/QsTypeParsing.fs b/src/QsCompiler/TextProcessor/QsTypeParsing.fs index 04947fb6cb..711112f72f 100644 --- a/src/QsCompiler/TextProcessor/QsTypeParsing.fs +++ b/src/QsCompiler/TextProcessor/QsTypeParsing.fs @@ -25,7 +25,7 @@ let private characteristicsExpression = new OperatorPrecedenceParser Characteristics.New // *needs* to be invalid if the compined range is Null! + QsPositionInfo.WithCombinedRange (lRange, rRange) kind InvalidSetExpr |> Characteristics.New // *needs* to be invalid if the combined range is Null! let private applyBinary operator _ (left : Characteristics) (right : Characteristics) = buildCombinedExpression (operator (left, right)) (left.Range, right.Range) @@ -186,7 +186,7 @@ let internal typeParser tupleType = ] let buildArrays p = let combine kind (lRange, rRange) = - QsPositionInfo.WithCombinedRange (lRange, rRange) kind InvalidType |> QsType.New // *needs* to be invalid if the compined range is Null! + QsPositionInfo.WithCombinedRange (lRange, rRange) kind InvalidType |> QsType.New // *needs* to be invalid if the combined range is Null! let rec applyArrays (t : QsType, item) = match item with | [] -> t diff --git a/src/QsCompiler/TextProcessor/SyntaxExtensions.fs b/src/QsCompiler/TextProcessor/SyntaxExtensions.fs index b90542b2a8..ce0b087e07 100644 --- a/src/QsCompiler/TextProcessor/SyntaxExtensions.fs +++ b/src/QsCompiler/TextProcessor/SyntaxExtensions.fs @@ -16,7 +16,7 @@ type QsPositionInfo with static member Range (pos1, pos2) = Value (QsPositionInfo.New pos1, QsPositionInfo.New pos2) - /// If the given right and left range both have contain a Value, returns the given kind as well as a Value with the compined range. + /// If the given right and left range both have contain a Value, returns the given kind as well as a Value with the combined range. /// Returns the given fallback and Null otherwise. static member WithCombinedRange (lRange, rRange) kind fallback = match (lRange, rRange) with diff --git a/src/QsCompiler/Transformations/CallGraphWalker.cs b/src/QsCompiler/Transformations/CallGraphWalker.cs index c1c41004e0..f1e91ff19a 100644 --- a/src/QsCompiler/Transformations/CallGraphWalker.cs +++ b/src/QsCompiler/Transformations/CallGraphWalker.cs @@ -11,7 +11,7 @@ using Microsoft.Quantum.QsCompiler.Transformations.Core; -namespace Microsoft.Quantum.QsCompiler.Transformations.CallGraphWalker +namespace Microsoft.Quantum.QsCompiler.Transformations { using ExpressionKind = QsExpressionKind; using ResolvedTypeKind = QsTypeKind; @@ -37,11 +37,112 @@ public struct CallGraphNode /// Class used to track call graph of a compilation public class CallGraph { + // TODO: + // This is the method that should be invoked to verify cycles of interest, + // i.e. where each callable in the cycle is type parametrized. + // It should probably generate diagnostics; I'll add the doc comment once its use is fully defined. + internal static bool VerifyCycle(CallGraphNode rootNode, params CallGraphEdge[] edges) + { + var parent = rootNode.CallableName; + var validResolution = TryCombineTypeResolutions(parent, out var combined, edges.Select(edge => edge.ParamResolutions).ToArray()); + var resolvedToConcrete = combined.Values.All(res => !(res.Resolution is ResolvedTypeKind.TypeParameter tp) || tp.Item.Origin.Equals(parent)); + return validResolution && resolvedToConcrete; + //var isClosedCycle = validCycle && combined.Values.Any(res => res.Resolution is ResolvedTypeKind.TypeParameter tp && EqualsParent(tp.Item.Origin)); + // TODO: check that monomorphization correctly processes closed cycles - meaning add a test... + } + + /// + /// Combines subsequent concretions as part of a nested expression, or concretions as part of a cycle in the call graph, + /// into a single dictionary containing the resolution for the type parameters of the specified parent callable. + /// The given resolutions are expected to be ordered starting with the dictionary containing the initial mapping for the + /// type parameters of the specified parent callable (the "innermost resolutions"). This mapping may potentially be to + /// type parameters of other callables, which are then further concretized by subsequent resolutions. + /// Returns the constructed dictionary as out parameter. Returns true if the combination of the given resolutions is valid, + /// i.e. if there are no conflicting resolutions and type parameters of the parent callables are uniquely resolved + /// to either a concrete type, a type parameter of another callable, or themselves. + /// Throws an ArgumentNullException if the given parent is null. + /// Throws an ArgumentException if the given resolutions imply that type parameters from multiple callables are + /// simultaneously treated as concrete types. + /// NOTE: This routine prioritizes the verifications to ensure the correctness of the resolution over performance. + /// + public static bool TryCombineTypeResolutions + (QsQualifiedName parent, out ImmutableDictionary>, ResolvedType> combined, + params ImmutableDictionary>, ResolvedType>[] resolutions) + { + if (parent == null) throw new ArgumentNullException(nameof(parent)); + var combinedBuilder = ImmutableDictionary.CreateBuilder>, ResolvedType>(); + var success = true; + + static Tuple> AsTypeResolutionKey(QsTypeParameter tp) => Tuple.Create(tp.Origin, tp.TypeName); + static bool ResolutionToTypeParameter(Tuple> typeParam, ResolvedType res) => + res.Resolution is ResolvedTypeKind.TypeParameter tp && tp.Item.Origin.Equals(typeParam.Item1) && tp.Item.TypeName.Equals(typeParam.Item2); + + // Returns true if the given resolution for the given key constrains the type parameter + // by mapping it to a different type parameter belonging to the same callable. + bool InconsistentResolutionToNative(Tuple> key, ResolvedType resolution) + { + var resolutionToTypeParam = resolution.Resolution as ResolvedTypeKind.TypeParameter; + var isResolutionToNative = resolutionToTypeParam != null && resolutionToTypeParam.Item.Origin.Equals(parent); + return isResolutionToNative + // We can omit this check as long as combinedBuilder only ever contains native type parameters: + // && key.Item1.Equals(parent) + && key.Item2.Value != resolutionToTypeParam.Item.TypeName.Value; + } + + foreach (var resolution in resolutions) + { + // Contains a lookup of all the keys in the combined resolutions whose value needs to be updated + // if a certain type parameter is resolved by the currently processed dictionary. + var mayBeReplaced = combinedBuilder + .Where(kv => kv.Value.Resolution.IsTypeParameter) + .ToLookup( + kv => AsTypeResolutionKey(((ResolvedTypeKind.TypeParameter)kv.Value.Resolution).Item), + entry => entry.Key); + + // We need to ensure that the mappings for external type parameters are processed first, + // to cover an edge case that would otherwise be indicated as a conflicting resolution. + foreach (var entry in resolution.Where(entry => mayBeReplaced.Contains(entry.Key))) + { + // resolution of an external type parameter that is currently listed as value in the combined type resolution dictionary + foreach (var keyInCombined in mayBeReplaced[entry.Key]) + { + // If one of the values is a type parameter from the parent callable, + // but it isn't mapped to itself then the combined resolution is invalid. + success = success && !InconsistentResolutionToNative(keyInCombined, entry.Value); + combinedBuilder[keyInCombined] = entry.Value; + } + } + + // resolution of a type parameter that belongs to the parent callable + foreach (var entry in resolution.Where(entry => entry.Key.Item1.Equals(parent))) + { + // A native type parameter cannot be resolved to another native type parameter, since this would constrain them. + success = success && !InconsistentResolutionToNative(entry.Key, entry.Value); + // Check that there is no conflicting resolution already defined. + var conflictingResolutionExists = combinedBuilder.TryGetValue(entry.Key, out var current) + && !current.Equals(entry.Value) && !ResolutionToTypeParameter(entry.Key, current); + success = success && !conflictingResolutionExists; + combinedBuilder[entry.Key] = entry.Value; + } + + if (resolution.Any(entry => !mayBeReplaced.Contains(entry.Key) && !entry.Key.Item1.Equals(parent))) + { + // It does not make sense to support this case, since there is no valid context in which type parameters + // belonging to multiple callables can/should be treated as concrete types simultaneously. + throw new ArgumentException("attempting to define resolution for type parameter that does not belong to parent callable"); + } + } + + combined = combinedBuilder.ToImmutable(); + QsCompilerError.Verify(combined.Keys.All(key => key.Item1.Equals(parent)), "for type parameter that does not belong to parent callable"); + return success; + } + /// /// This is a dictionary mapping source nodes to information about target nodes. This information is represented /// by a dictionary mapping target node to the edges pointing from the source node to the target node. /// - private Dictionary>> _Dependencies = + private readonly Dictionary>> _Dependencies = new Dictionary>>(); private QsNullable> RemovePositionFromTypeArgs(QsNullable> tArgs) => diff --git a/src/QsCompiler/Transformations/ClassicallyControlled.cs b/src/QsCompiler/Transformations/ClassicallyControlled.cs index c39d2838b7..6316f9df8f 100644 --- a/src/QsCompiler/Transformations/ClassicallyControlled.cs +++ b/src/QsCompiler/Transformations/ClassicallyControlled.cs @@ -72,29 +72,6 @@ private class StatementTransformation : StatementTransformation parent) : base(parent) { } - /// - /// Get the combined type resolutions for a pair of nested resolutions, - /// resolving references in the inner resolutions to the outer resolutions. - /// - private TypeArgsResolution GetCombinedTypeResolution(TypeArgsResolution outer, TypeArgsResolution inner) - { - var outerDict = outer.ToDictionary(x => (x.Item1, x.Item2), x => x.Item3); - return inner.Select(innerRes => - { - if (innerRes.Item3.Resolution is ResolvedTypeKind.TypeParameter typeParam && - outerDict.TryGetValue((typeParam.Item.Origin, typeParam.Item.TypeName), out var outerRes)) - { - outerDict.Remove((typeParam.Item.Origin, typeParam.Item.TypeName)); - return Tuple.Create(innerRes.Item1, innerRes.Item2, outerRes); - } - else - { - return innerRes; - } - }) - .Concat(outerDict.Select(x => Tuple.Create(x.Key.Item1, x.Key.Item2, x.Value))).ToImmutableArray(); - } - /// /// Checks if the scope is valid for conversion to an operation call from the conditional control API. /// It is valid if there is exactly one statement in it and that statement is a call like expression statement. @@ -112,25 +89,27 @@ private TypeArgsResolution GetCombinedTypeResolution(TypeArgsResolution outer, T && !TypedExpression.IsPartialApplication(expr.Item.Expression) && call.Item1.Expression is ExpressionKind.Identifier) { - // We are dissolving the application of arguments here, so the call's type argument - // resolutions have to be moved to the 'identifier' sub expression. - - var callTypeArguments = expr.Item.TypeArguments; - var idTypeArguments = call.Item1.TypeArguments; - var combinedTypeArguments = GetCombinedTypeResolution(callTypeArguments, idTypeArguments); + var newCallIdentifier = call.Item1; + var callTypeArguments = expr.Item.TypeParameterResolutions; // This relies on anything having type parameters must be a global callable. - var newCallIdentifier = call.Item1; - if (combinedTypeArguments.Any() - && newCallIdentifier.Expression is ExpressionKind.Identifier id - && id.Item1 is Identifier.GlobalCallable global) + if (newCallIdentifier.Expression is ExpressionKind.Identifier id + && id.Item1 is Identifier.GlobalCallable global + && (callTypeArguments.Any())) { + // We are dissolving the application of arguments here, so the call's type argument + // resolutions have to be moved to the 'identifier' sub expression. + var combined = CallGraph.TryCombineTypeResolutions(global.Item, + out var combinedTypeArguments, + newCallIdentifier.TypeParameterResolutions, callTypeArguments); + QsCompilerError.Verify(combined, "failed to combine type parameter resolution"); + var globalCallable = SharedState.Compilation.Namespaces .Where(ns => ns.Name.Equals(global.Item.Namespace)) .Callables() .FirstOrDefault(c => c.FullName.Name.Equals(global.Item.Name)); - QsCompilerError.Verify(globalCallable != null, $"Could not find the global reference {global.Item.Namespace.Value + "." + global.Item.Name.Value}"); + QsCompilerError.Verify(globalCallable != null, $"Could not find the global reference {global.Item}."); var callableTypeParameters = globalCallable.Signature.TypeParameters .Select(x => x as QsLocalSymbol.ValidName); @@ -142,8 +121,8 @@ private TypeArgsResolution GetCombinedTypeResolution(TypeArgsResolution outer, T id.Item1, QsNullable>.NewValue( callableTypeParameters - .Select(x => combinedTypeArguments.First(y => y.Item2.Equals(x.Item)).Item3).ToImmutableArray())), - combinedTypeArguments, + .Select(x => combinedTypeArguments[Tuple.Create(global.Item, x.Item)]).ToImmutableArray())), + TypedExpression.AsTypeArguments(combinedTypeArguments), call.Item1.ResolvedType, call.Item1.InferredInformation, call.Item1.Range); @@ -322,7 +301,7 @@ private TypedExpression CreateApplyConditionallyExpression(TypedExpression condi // Takes a single TypedExpression of type Result and puts in into a // value array expression with the given expression as its only item. - TypedExpression BoxResultInArray(TypedExpression expression) => + static TypedExpression BoxResultInArray(TypedExpression expression) => new TypedExpression( ExpressionKind.NewValueArray(ImmutableArray.Create(expression)), TypeArgsResolution.Empty, @@ -551,7 +530,7 @@ private bool IsConditionedOnResultLiteralExpression(TypedExpression expression, } else if (expression.Expression is ExpressionKind.NEQ neq) { - QsResult FlipResult(QsResult result) => result.IsZero ? QsResult.One : QsResult.Zero; + static QsResult FlipResult(QsResult result) => result.IsZero ? QsResult.One : QsResult.Zero; if (neq.Item1.Expression is ExpressionKind.ResultLiteral literal1) {