diff --git a/sdk/dotnet/Pulumi.Tests/Serialization/MarshalOutputTests.cs b/sdk/dotnet/Pulumi.Tests/Serialization/MarshalOutputTests.cs index 2331d0d93..447a0245f 100644 --- a/sdk/dotnet/Pulumi.Tests/Serialization/MarshalOutputTests.cs +++ b/sdk/dotnet/Pulumi.Tests/Serialization/MarshalOutputTests.cs @@ -90,6 +90,21 @@ namespace Pulumi.Tests.Serialization ImmutableArray.Empty.Add(CreateOutputValue("hello", isSecret: true)) }, new object[] + { + new InputList { "hello" }, + ImmutableArray.Empty.Add("hello") + }, + new object[] + { + new InputList { Output.Create("hello") }, + ImmutableArray.Empty.Add("hello") + }, + new object[] + { + new InputList { Output.CreateSecret("hello") }, + ImmutableArray.Empty.Add(CreateOutputValue("hello", isSecret: true)) + }, + new object[] { new Dictionary> { { "foo", "hello" } }, ImmutableDictionary.Empty.Add("foo", "hello") @@ -105,6 +120,21 @@ namespace Pulumi.Tests.Serialization ImmutableDictionary.Empty.Add("foo", CreateOutputValue("hello", isSecret: true)) }, new object[] + { + new InputMap { { "foo", "hello" } }, + ImmutableDictionary.Empty.Add("foo", "hello") + }, + new object[] + { + new InputMap { { "foo", Output.Create("hello") } }, + ImmutableDictionary.Empty.Add("foo", "hello") + }, + new object[] + { + new InputMap { { "foo", Output.CreateSecret("hello") } }, + ImmutableDictionary.Empty.Add("foo", CreateOutputValue("hello", isSecret: true)) + }, + new object[] { new BarArgs { Foo = new FooArgs { Foo = "hello" } }, ImmutableDictionary.Empty.Add("foo", diff --git a/sdk/dotnet/Pulumi/Core/Input.cs b/sdk/dotnet/Pulumi/Core/Input.cs index c8b72f0ec..75d2afc36 100644 --- a/sdk/dotnet/Pulumi/Core/Input.cs +++ b/sdk/dotnet/Pulumi/Core/Input.cs @@ -1,7 +1,8 @@ -// Copyright 2016-2019, Pulumi Corporation +// Copyright 2016-2021, Pulumi Corporation using System; using System.Threading.Tasks; +using OneOf; namespace Pulumi { @@ -13,6 +14,7 @@ namespace Pulumi internal interface IInput { IOutput ToOutput(); + object? Value { get; } } /// @@ -21,29 +23,34 @@ namespace Pulumi /// public class Input : IInput { - /// - /// Technically, in .net we can represent Inputs entirely using the Output type (since - /// Outputs can wrap values and promises). However, it would look very weird to state that - /// the inputs to a resource *had* to be Outputs. So we basically just come up with this - /// wrapper type so things look sensible, even though under the covers we implement things - /// using the exact same type - /// - private protected Output _outputValue; + private readonly OneOf> _value; + + private protected Input(T value) + => _value = OneOf>.FromT0(value); private protected Input(Output outputValue) - => _outputValue = outputValue ?? throw new ArgumentNullException(nameof(outputValue)); + => _value = OneOf>.FromT1(outputValue ?? throw new ArgumentNullException(nameof(outputValue))); + + private protected virtual Output ToOutput() + => _value.Match(v => Output.Create(v), v => v); + + private protected virtual object? Value + => _value.Value; public static implicit operator Input(T value) - => Output.Create(value); + => new Input(value); public static implicit operator Input(Output value) => new Input(value); public static implicit operator Output(Input input) - => input._outputValue; + => input.ToOutput(); IOutput IInput.ToOutput() => this.ToOutput(); + + object? IInput.Value + => this.Value; } public static class InputExtensions diff --git a/sdk/dotnet/Pulumi/Core/InputList.cs b/sdk/dotnet/Pulumi/Core/InputList.cs index ac2a37c16..7694fbec9 100644 --- a/sdk/dotnet/Pulumi/Core/InputList.cs +++ b/sdk/dotnet/Pulumi/Core/InputList.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; +using OneOf; namespace Pulumi { @@ -46,20 +47,48 @@ namespace Pulumi /// public sealed class InputList : Input>, IEnumerable, IAsyncEnumerable> { - public InputList() : this(Output.Create(ImmutableArray.Empty)) + private OneOf>, Output>> _value; + + public InputList() : this(ImmutableArray>.Empty) + { + } + + private InputList(ImmutableArray> values) + : this(OneOf>, Output>>.FromT0(values)) { } private InputList(Output> values) - : base(values) + : this(OneOf>, Output>>.FromT1(values)) { } + private InputList(OneOf>, Output>> value) + : base(ImmutableArray.Empty) + { + _value = value; + } + + private protected override Output> ToOutput() + => _value.Match(v => Output.All(v), v => v); + + private protected override object Value + => _value.Value; + public void Add(params Input[] inputs) { - // Make an Output from the values passed in, mix in with our own Output, and combine - // both to produce the final array that we will now point at. - _outputValue = Output.Concat(_outputValue, Output.All(inputs)); + if (_value.IsT0) + { + var combined = _value.AsT0.AddRange(inputs); + _value = OneOf>, Output>>.FromT0(combined); + } + else + { + // Make an Output from the values passed in, mix in with our own Output, and combine + // both to produce the final array that we will now point at. + var combined = Output.Concat(_value.AsT1, Output.All(inputs)); + _value = OneOf>, Output>>.FromT1(combined); + } } /// @@ -67,10 +96,33 @@ namespace Pulumi /// returning the concatenated sequence in a new . /// public InputList Concat(InputList other) - => Output.Concat(_outputValue, other._outputValue); + { + if (_value.IsT0) + { + if (other._value.IsT0) + { + return _value.AsT0.AddRange(other._value.AsT0); + } + else + { + return Output.Concat(Output.All(_value.AsT0), other._value.AsT1); + } + } + else + { + if (other._value.IsT0) + { + return Output.Concat(_value.AsT1, Output.All(other._value.AsT0)); + } + else + { + return Output.Concat(_value.AsT1, other._value.AsT1); + } + } + } internal InputList Clone() - => new InputList(_outputValue); + => new InputList(_value); #region construct from unary @@ -120,7 +172,7 @@ namespace Pulumi => values.SelectAsArray(v => (Input)v); public static implicit operator InputList(ImmutableArray> values) - => Output.All(values); + => new InputList(values); #endregion @@ -147,11 +199,21 @@ namespace Pulumi public async IAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken) { - var data = await _outputValue.GetValueAsync(whenUnknown: ImmutableArray.Empty) - .ConfigureAwait(false); - foreach (var value in data) + if (_value.IsT0) { - yield return value; + foreach (var value in _value.AsT0) + { + yield return value; + } + } + else + { + var data = await _value.AsT1.GetValueAsync(whenUnknown: ImmutableArray.Empty) + .ConfigureAwait(false); + foreach (var value in data) + { + yield return value; + } } } diff --git a/sdk/dotnet/Pulumi/Core/InputMap.cs b/sdk/dotnet/Pulumi/Core/InputMap.cs index 325544d91..c542a7496 100644 --- a/sdk/dotnet/Pulumi/Core/InputMap.cs +++ b/sdk/dotnet/Pulumi/Core/InputMap.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; +using OneOf; namespace Pulumi { @@ -42,20 +43,64 @@ namespace Pulumi /// public sealed class InputMap : Input>, IEnumerable, IAsyncEnumerable>> { - public InputMap() : this(Output.Create(ImmutableDictionary.Empty)) + private OneOf>, Output>> _value; + + public InputMap() : this(ImmutableDictionary>.Empty) + { + } + + private InputMap(ImmutableDictionary> values) + : this(OneOf>, Output>>.FromT0(values)) { } private InputMap(Output> values) - : base(values) + : this(OneOf>, Output>>.FromT1(values)) { } + private InputMap(OneOf>, Output>> value) + : base(ImmutableDictionary.Empty) + { + _value = value; + } + + private protected override Output> ToOutput() + => _value.Match( + v => + { + var kvps = v.ToImmutableArray(); + var keys = kvps.SelectAsArray(kvp => kvp.Key); + var values = kvps.SelectAsArray(kvp => kvp.Value); + return Output.Tuple(Output.Create(keys), Output.All(values)).Apply(x => + { + var builder = ImmutableDictionary.CreateBuilder(); + for (int i = 0; i < x.Item1.Length; i++) + { + builder.Add(x.Item1[i], x.Item2[i]); + } + return builder.ToImmutable(); + }); + }, + v => v); + + private protected override object Value + => _value.Value; + public void Add(string key, Input value) { - var inputDictionary = (Input>)_outputValue; - _outputValue = Output.Tuple(inputDictionary, value) - .Apply(x => x.Item1.Add(key, x.Item2)); + + if (_value.IsT0) + { + var combined = _value.AsT0.Add(key, value); + _value = OneOf>, Output>>.FromT0(combined); + } + else + { + var combined = Output.Tuple((Input>)_value.AsT1, value) + .Apply(x => x.Item1.Add(key, x.Item2)); + _value = OneOf>, Output>>.FromT1(combined); + } } public Input this[string key] @@ -76,25 +121,53 @@ namespace Pulumi /// both input maps. public static InputMap Merge(InputMap first, InputMap second) { - var output = Output.Tuple(first._outputValue, second._outputValue) - .Apply(dicts => - { - var result = new Dictionary(dicts.Item1); - // Overwrite keys if duplicates are found - foreach (var (k, v) in dicts.Item2) - result[k] = v; - return result; - }); - return output; + if (first._value.IsT0) + { + if (second._value.IsT0) + { + var result = first._value.AsT0; + // Overwrite keys if duplicates are found + foreach (var (k, v) in second._value.AsT0) + result = result.SetItem(k, v); + return new InputMap(result); + } + else + { + return Output.Tuple(first.ToOutput(), second._value.AsT1) + .Apply(dicts => Merge(dicts)); + } + } + else + { + if (second._value.IsT0) + { + return Output.Tuple(first._value.AsT1, second.ToOutput()) + .Apply(dicts => Merge(dicts)); + } + else + { + return Output.Tuple(first._value.AsT1, second._value.AsT1) + .Apply(dicts => Merge(dicts)); + } + } + + Dictionary Merge((ImmutableDictionary, ImmutableDictionary) dicts) + { + var result = new Dictionary(dicts.Item1); + // Overwrite keys if duplicates are found + foreach (var (k, v) in dicts.Item2) + result[k] = v; + return result; + } } #region construct from dictionary types public static implicit operator InputMap(Dictionary values) - => Output.Create(values); + => new InputMap(ImmutableDictionary.CreateRange(values.Select(kvp => KeyValuePair.Create(kvp.Key, (Input)kvp.Value)))); public static implicit operator InputMap(ImmutableDictionary values) - => Output.Create(values); + => new InputMap(ImmutableDictionary.CreateRange(values.Select(kvp => KeyValuePair.Create(kvp.Key, (Input)kvp.Value)))); public static implicit operator InputMap(Output> values) => values.Apply(ImmutableDictionary.CreateRange); @@ -114,11 +187,30 @@ namespace Pulumi public async IAsyncEnumerator>> GetAsyncEnumerator(CancellationToken cancellationToken) { - var data = await _outputValue.GetValueAsync(whenUnknown: ImmutableDictionary.Empty) - .ConfigureAwait(false); - foreach (var value in data) + if (_value.IsT0) { - yield return value; + foreach (var value in _value.AsT0) + { + var input = (IInput)value.Value; + if (input.Value is IOutput) + { + yield return Output.Tuple((Input)value.Key, value.Value) + .Apply(x => KeyValuePair.Create(x.Item1, x.Item2)); + } + else + { + yield return KeyValuePair.Create(value.Key, (V)input.Value!); + } + } + } + else + { + var data = await _value.AsT1.GetValueAsync(whenUnknown: ImmutableDictionary.Empty) + .ConfigureAwait(false); + foreach (var value in data) + { + yield return value; + } } } diff --git a/sdk/dotnet/Pulumi/Core/InputUnion.cs b/sdk/dotnet/Pulumi/Core/InputUnion.cs index 9ed084c61..2bcb0f2f5 100644 --- a/sdk/dotnet/Pulumi/Core/InputUnion.cs +++ b/sdk/dotnet/Pulumi/Core/InputUnion.cs @@ -1,4 +1,4 @@ -// Copyright 2016-2019, Pulumi Corporation +// Copyright 2016-2021, Pulumi Corporation namespace Pulumi { @@ -9,7 +9,12 @@ namespace Pulumi /// public sealed class InputUnion : Input> { - public InputUnion() : this(Output.Create(default(Union))) + public InputUnion() : this(default(Union)) + { + } + + private InputUnion(Union oneOf) + : base(oneOf) { } @@ -21,10 +26,10 @@ namespace Pulumi #region common conversions public static implicit operator InputUnion(T0 value) - => Output.Create(value); + => new InputUnion(Union.FromT0(value)); public static implicit operator InputUnion(T1 value) - => Output.Create(value); + => new InputUnion(Union.FromT1(value)); public static implicit operator InputUnion(Output value) => new InputUnion(value.Apply(Union.FromT0)); diff --git a/sdk/dotnet/Pulumi/Serialization/Serializer.cs b/sdk/dotnet/Pulumi/Serialization/Serializer.cs index dc3bfa6bd..2af8001c4 100644 --- a/sdk/dotnet/Pulumi/Serialization/Serializer.cs +++ b/sdk/dotnet/Pulumi/Serialization/Serializer.cs @@ -110,7 +110,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: Log.Debug($"Serialize property[{ctx}]: Recursing into IInput"); } - return await SerializeAsync(ctx, input.ToOutput(), keepResources, keepOutputValues).ConfigureAwait(false); + return await SerializeAsync(ctx, input.Value, keepResources, keepOutputValues).ConfigureAwait(false); } if (prop is IUnion union)