@@ -39,6 +39,9 @@ type value struct {
3939 base * value // the base value, if any
4040 schema * schema.Schema // the value's schema
4141
42+ mergedKeys []string // the value's merged keys. computed lazily--use keys().
43+ exported * esc.Value // non-nil if this value has already been exported
44+
4245 // true if the value is unknown (e.g. because it did not evaluate successfully or is the result of an unevaluated
4346 // fn::open)
4447 unknown bool
@@ -149,20 +152,36 @@ func (v *value) combine(others ...*value) {
149152// keys returns the value's keys if the value is an object. This method should be used instead of accessing the
150153// underlying map[string]*value directly, as it takes JSON merge patch semantics into account.
151154func (v * value ) keys () []string {
152- keySet := make (map [string ]struct {})
153- for v != nil {
155+ if v == nil {
156+ return nil
157+ }
158+ if v .mergedKeys == nil {
154159 m , ok := v .repr .(map [string ]* value )
155160 if ! ok {
156- break
161+ return nil
157162 }
158- for k := range m {
159- keySet [k ] = struct {}{}
163+
164+ baseKeys := v .base .keys ()
165+ if len (baseKeys ) == 0 {
166+ v .mergedKeys = maps .Keys (m )
167+ } else {
168+ l := len (baseKeys )
169+ if l < len (m ) {
170+ l = len (m )
171+ }
172+ keySet := make (map [string ]struct {}, l )
173+
174+ for _ , k := range baseKeys {
175+ keySet [k ] = struct {}{}
176+ }
177+ for k := range m {
178+ keySet [k ] = struct {}{}
179+ }
180+ v .mergedKeys = maps .Keys (keySet )
160181 }
161- v = v . base
182+ sort . Strings ( v . mergedKeys )
162183 }
163- keys := maps .Keys (keySet )
164- sort .Strings (keys )
165- return keys
184+ return v .mergedKeys
166185}
167186
168187// property returns the named property (if any) as per JSON merge patch semantics. If the receiver is unknown,
@@ -269,6 +288,10 @@ func (v *value) toString() (str string, unknown bool, secret bool) {
269288
270289// export converts the value into its serializable representation.
271290func (v * value ) export (environment string ) esc.Value {
291+ if v .exported != nil {
292+ return * v .exported
293+ }
294+
272295 var pv any
273296 switch repr := v .repr .(type ) {
274297 case []* value :
@@ -295,7 +318,7 @@ func (v *value) export(environment string) esc.Value {
295318 base = & b
296319 }
297320
298- return esc.Value {
321+ v . exported = & esc.Value {
299322 Value : pv ,
300323 Secret : v .secret ,
301324 Unknown : v .unknown ,
@@ -304,6 +327,7 @@ func (v *value) export(environment string) esc.Value {
304327 Base : base ,
305328 },
306329 }
330+ return * v .exported
307331}
308332
309333// unexport creates a value from a Value. This is used when interacting with providers, as the Provider API works on
@@ -327,7 +351,7 @@ func unexport(v esc.Value, x *expr) *value {
327351 }
328352 vv .repr , vv .schema = a , schema .Tuple (items ... ).Schema ()
329353 case map [string ]esc.Value :
330- m , properties := make (map [string ]* value , len (pv )), make (map [ string ] schema.Builder , len (pv ))
354+ m , properties := make (map [string ]* value , len (pv )), make (schema.SchemaMap , len (pv ))
331355 for k , v := range pv {
332356 uv := unexport (v , x )
333357 m [k ], properties [k ] = uv , uv .schema
@@ -348,7 +372,12 @@ func mergedSchema(base, top *schema.Schema) *schema.Schema {
348372 return top
349373 }
350374
351- record := make (map [string ]schema.Builder )
375+ l := len (base .Properties )
376+ if l < len (top .Properties ) {
377+ l = len (top .Properties )
378+ }
379+
380+ record := make (schema.SchemaMap , l )
352381 for k , base := range base .Properties {
353382 record [k ] = base
354383 }
0 commit comments