[sdk/dotnet] Avoid eager conversion to Output<T> in Input<T>

This changes `Input<T>`, `InputList<T>`, `InputMap<V>`, and `InputUnion<T0, T1>` to no longer eagerly convert plain values to `Output<T>`.
This commit is contained in:
Justin Van Patten 2021-11-14 01:31:58 -08:00
parent 272c4643b2
commit 877d296c5c
6 changed files with 246 additions and 50 deletions

View file

@ -90,6 +90,21 @@ namespace Pulumi.Tests.Serialization
ImmutableArray<object>.Empty.Add(CreateOutputValue("hello", isSecret: true))
},
new object[]
{
new InputList<string> { "hello" },
ImmutableArray<object>.Empty.Add("hello")
},
new object[]
{
new InputList<string> { Output.Create("hello") },
ImmutableArray<object>.Empty.Add("hello")
},
new object[]
{
new InputList<string> { Output.CreateSecret("hello") },
ImmutableArray<object>.Empty.Add(CreateOutputValue("hello", isSecret: true))
},
new object[]
{
new Dictionary<string, Input<string>> { { "foo", "hello" } },
ImmutableDictionary<string, object>.Empty.Add("foo", "hello")
@ -105,6 +120,21 @@ namespace Pulumi.Tests.Serialization
ImmutableDictionary<string, object>.Empty.Add("foo", CreateOutputValue("hello", isSecret: true))
},
new object[]
{
new InputMap<string> { { "foo", "hello" } },
ImmutableDictionary<string, object>.Empty.Add("foo", "hello")
},
new object[]
{
new InputMap<string> { { "foo", Output.Create("hello") } },
ImmutableDictionary<string, object>.Empty.Add("foo", "hello")
},
new object[]
{
new InputMap<string> { { "foo", Output.CreateSecret("hello") } },
ImmutableDictionary<string, object>.Empty.Add("foo", CreateOutputValue("hello", isSecret: true))
},
new object[]
{
new BarArgs { Foo = new FooArgs { Foo = "hello" } },
ImmutableDictionary<string, object>.Empty.Add("foo",

View file

@ -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; }
}
/// <summary>
@ -21,29 +23,34 @@ namespace Pulumi
/// </summary>
public class Input<T> : IInput
{
/// <summary>
/// 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
/// </summary>
private protected Output<T> _outputValue;
private readonly OneOf<T, Output<T>> _value;
private protected Input(T value)
=> _value = OneOf<T, Output<T>>.FromT0(value);
private protected Input(Output<T> outputValue)
=> _outputValue = outputValue ?? throw new ArgumentNullException(nameof(outputValue));
=> _value = OneOf<T, Output<T>>.FromT1(outputValue ?? throw new ArgumentNullException(nameof(outputValue)));
private protected virtual Output<T> ToOutput()
=> _value.Match(v => Output.Create(v), v => v);
private protected virtual object? Value
=> _value.Value;
public static implicit operator Input<T>(T value)
=> Output.Create(value);
=> new Input<T>(value);
public static implicit operator Input<T>(Output<T> value)
=> new Input<T>(value);
public static implicit operator Output<T>(Input<T> input)
=> input._outputValue;
=> input.ToOutput();
IOutput IInput.ToOutput()
=> this.ToOutput();
object? IInput.Value
=> this.Value;
}
public static class InputExtensions

View file

@ -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
/// </summary>
public sealed class InputList<T> : Input<ImmutableArray<T>>, IEnumerable, IAsyncEnumerable<Input<T>>
{
public InputList() : this(Output.Create(ImmutableArray<T>.Empty))
private OneOf<ImmutableArray<Input<T>>, Output<ImmutableArray<T>>> _value;
public InputList() : this(ImmutableArray<Input<T>>.Empty)
{
}
private InputList(ImmutableArray<Input<T>> values)
: this(OneOf<ImmutableArray<Input<T>>, Output<ImmutableArray<T>>>.FromT0(values))
{
}
private InputList(Output<ImmutableArray<T>> values)
: base(values)
: this(OneOf<ImmutableArray<Input<T>>, Output<ImmutableArray<T>>>.FromT1(values))
{
}
private InputList(OneOf<ImmutableArray<Input<T>>, Output<ImmutableArray<T>>> value)
: base(ImmutableArray<T>.Empty)
{
_value = value;
}
private protected override Output<ImmutableArray<T>> ToOutput()
=> _value.Match(v => Output.All(v), v => v);
private protected override object Value
=> _value.Value;
public void Add(params Input<T>[] 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<ImmutableArray<Input<T>>, Output<ImmutableArray<T>>>.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<ImmutableArray<Input<T>>, Output<ImmutableArray<T>>>.FromT1(combined);
}
}
/// <summary>
@ -67,10 +96,33 @@ namespace Pulumi
/// returning the concatenated sequence in a new <see cref="InputList{T}"/>.
/// </summary>
public InputList<T> Concat(InputList<T> 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<T> Clone()
=> new InputList<T>(_outputValue);
=> new InputList<T>(_value);
#region construct from unary
@ -120,7 +172,7 @@ namespace Pulumi
=> values.SelectAsArray(v => (Input<T>)v);
public static implicit operator InputList<T>(ImmutableArray<Input<T>> values)
=> Output.All(values);
=> new InputList<T>(values);
#endregion
@ -147,11 +199,21 @@ namespace Pulumi
public async IAsyncEnumerator<Input<T>> GetAsyncEnumerator(CancellationToken cancellationToken)
{
var data = await _outputValue.GetValueAsync(whenUnknown: ImmutableArray<T>.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<T>.Empty)
.ConfigureAwait(false);
foreach (var value in data)
{
yield return value;
}
}
}

View file

@ -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
/// </summary>
public sealed class InputMap<V> : Input<ImmutableDictionary<string, V>>, IEnumerable, IAsyncEnumerable<Input<KeyValuePair<string, V>>>
{
public InputMap() : this(Output.Create(ImmutableDictionary<string, V>.Empty))
private OneOf<ImmutableDictionary<string, Input<V>>, Output<ImmutableDictionary<string, V>>> _value;
public InputMap() : this(ImmutableDictionary<string, Input<V>>.Empty)
{
}
private InputMap(ImmutableDictionary<string, Input<V>> values)
: this(OneOf<ImmutableDictionary<string, Input<V>>, Output<ImmutableDictionary<string, V>>>.FromT0(values))
{
}
private InputMap(Output<ImmutableDictionary<string, V>> values)
: base(values)
: this(OneOf<ImmutableDictionary<string, Input<V>>, Output<ImmutableDictionary<string, V>>>.FromT1(values))
{
}
private InputMap(OneOf<ImmutableDictionary<string, Input<V>>, Output<ImmutableDictionary<string, V>>> value)
: base(ImmutableDictionary<string, V>.Empty)
{
_value = value;
}
private protected override Output<ImmutableDictionary<string, V>> 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<string, V>();
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<V> value)
{
var inputDictionary = (Input<ImmutableDictionary<string, V>>)_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<ImmutableDictionary<string, Input<V>>, Output<ImmutableDictionary<string, V>>>.FromT0(combined);
}
else
{
var combined = Output.Tuple((Input<ImmutableDictionary<string, V>>)_value.AsT1, value)
.Apply(x => x.Item1.Add(key, x.Item2));
_value = OneOf<ImmutableDictionary<string, Input<V>>, Output<ImmutableDictionary<string, V>>>.FromT1(combined);
}
}
public Input<V> this[string key]
@ -76,25 +121,53 @@ namespace Pulumi
/// both input maps.</returns>
public static InputMap<V> Merge(InputMap<V> first, InputMap<V> second)
{
var output = Output.Tuple(first._outputValue, second._outputValue)
.Apply(dicts =>
{
var result = new Dictionary<string, V>(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<V>(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<string, V> Merge((ImmutableDictionary<string, V>, ImmutableDictionary<string, V>) dicts)
{
var result = new Dictionary<string, V>(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<V>(Dictionary<string, V> values)
=> Output.Create(values);
=> new InputMap<V>(ImmutableDictionary.CreateRange(values.Select(kvp => KeyValuePair.Create(kvp.Key, (Input<V>)kvp.Value))));
public static implicit operator InputMap<V>(ImmutableDictionary<string, V> values)
=> Output.Create(values);
=> new InputMap<V>(ImmutableDictionary.CreateRange(values.Select(kvp => KeyValuePair.Create(kvp.Key, (Input<V>)kvp.Value))));
public static implicit operator InputMap<V>(Output<Dictionary<string, V>> values)
=> values.Apply(ImmutableDictionary.CreateRange);
@ -114,11 +187,30 @@ namespace Pulumi
public async IAsyncEnumerator<Input<KeyValuePair<string, V>>> GetAsyncEnumerator(CancellationToken cancellationToken)
{
var data = await _outputValue.GetValueAsync(whenUnknown: ImmutableDictionary<string, V>.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<string>)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<string, V>.Empty)
.ConfigureAwait(false);
foreach (var value in data)
{
yield return value;
}
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2016-2019, Pulumi Corporation
// Copyright 2016-2021, Pulumi Corporation
namespace Pulumi
{
@ -9,7 +9,12 @@ namespace Pulumi
/// </summary>
public sealed class InputUnion<T0, T1> : Input<Union<T0, T1>>
{
public InputUnion() : this(Output.Create(default(Union<T0, T1>)))
public InputUnion() : this(default(Union<T0, T1>))
{
}
private InputUnion(Union<T0, T1> oneOf)
: base(oneOf)
{
}
@ -21,10 +26,10 @@ namespace Pulumi
#region common conversions
public static implicit operator InputUnion<T0, T1>(T0 value)
=> Output.Create(value);
=> new InputUnion<T0, T1>(Union<T0, T1>.FromT0(value));
public static implicit operator InputUnion<T0, T1>(T1 value)
=> Output.Create(value);
=> new InputUnion<T0, T1>(Union<T0, T1>.FromT1(value));
public static implicit operator InputUnion<T0, T1>(Output<T0> value)
=> new InputUnion<T0, T1>(value.Apply(Union<T0, T1>.FromT0));

View file

@ -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)