Repository: opentofu/opentofu
File: internal/lang/funcs/ephemeral.go (Go)

Code before:
			Type:             cty.DynamicPseudoType,
			AllowUnknown:     true,
			AllowNull:        true,
			AllowMarked:      true,
			AllowDynamicType: true,
		},
	},
	Type: func(args []cty.Value) (cty.Type, error) {
		// The result type is always the same as the argument type.
		return args[0].Type(), nil
	},
	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
		var ephemeralAsNull func(cty.Value) cty.Value
		// Inspired heavily by https://github.com/zclconf/go-cty/blob/v1.16.3/cty/unknown_as_null.go#L10
		ephemeralAsNull = func(val cty.Value) cty.Value {
			ty := val.Type()

			// Preserve non-ephemeral marks
			nonEphemeralMarks := val.Marks()
			delete(nonEphemeralMarks, marks.Ephemeral)

			switch {
			case val.IsNull():
				return cty.NullVal(ty).WithMarks(nonEphemeralMarks)
			case !val.IsKnown():
				// TODO ensure we are handling unknown properly
				// See comments in sensitive.go
				return cty.UnknownVal(val.Type()).WithMarks(nonEphemeralMarks)
			case val.HasMark(marks.Ephemeral):
				// This whole value is marked as ephemeral and should be null
				return cty.NullVal(val.Type()).WithMarks(nonEphemeralMarks)
			case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
				unmarkedVal, marks := val.Unmark()
				oldVals := unmarkedVal.AsValueSlice()
				if len(oldVals) == 0 {
					return val
				}

				newVals := make([]cty.Value, len(oldVals))
				for i, v := range oldVals {
					newVals[i] = ephemeralAsNull(v)
				}
				switch {
				case ty.IsListType():
					return cty.ListVal(newVals).WithMarks(marks)
				case ty.IsTupleType():
					return cty.TupleVal(newVals).WithMarks(marks)
				default:
					return cty.SetVal(newVals).WithMarks(marks)
				}
			case ty.IsMapType() || ty.IsObjectType():

Review comment (security):
Tested this with an ephemeral resource deferred for the apply phase and it worked correctly:
```hcl
output "test6" {
  value = ephemeralasnull(ephemeral.playground_secret.test.secret)
}
```
Output during plan:
> Changes to Outputs:
>   + test6 = (known after apply)

There was no output rendered after the apply since the whole value is nullified.

Code after:

var EphemeralAsNullFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name:             "value",
			Type:             cty.DynamicPseudoType,
			AllowUnknown:     true,
			AllowNull:        true,
			AllowMarked:      true,
			AllowDynamicType: true,
		},
	},
	Type: func(args []cty.Value) (cty.Type, error) {
		// The result type is always the same as the argument type.
		return args[0].Type(), nil
	},
	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
		return cty.Transform(args[0], func(_ cty.Path, val cty.Value) (cty.Value, error) {
			ty := val.Type()

			// Preserve non-ephemeral marks
			nonEphemeralMarks := val.Marks()
			delete(nonEphemeralMarks, marks.Ephemeral)

			switch {
			case val.IsNull():
				return cty.NullVal(ty).WithMarks(nonEphemeralMarks), nil
			case !val.IsKnown():
				// This mirrors the logic in IsSensitive()
				//
				// When a value is unknown its ephemerality is also not yet
				// finalized, because authors can write expressions where the
				// ephemerality of the result is decided based on some other
				// value that isn't yet known itself. As a simple example:
				//    var.unknown_bool ? var.some_ephemeral_value : "b"
				//
				// Therefore we must report that we can't predict whether an
				// unknown value will be ephemeral or not. For more information,
				// refer to https://github.com/opentofu/opentofu/issues/2415

				return cty.UnknownVal(val.Type()).WithMarks(nonEphemeralMarks), nil
			case val.HasMark(marks.Ephemeral):
				// This whole value is marked as ephemeral and should be null
				return cty.NullVal(val.Type()).WithMarks(nonEphemeralMarks), nil
			default:
				// Not marked
				return val, nil
			}
		})
	},
})