code: func unknownBlockStub(schema *configschema.Block) cty.Value {
  	vals := make(map[string]cty.Value)
  	for name, attrS := range schema.Attributes {
  		vals[name] = cty.UnknownVal(attrS.Type)
  	}
  	for name, blockS := range schema.BlockTypes {
  		switch blockS.Nesting {
  		case configschema.NestingSingle, configschema.NestingGroup:
  			vals[name] = unknownBlockStub(&blockS.Block)
  		case configschema.NestingList:
  			// In principle we may be expected to produce a tuple value here,
  			// if there are any dynamically-typed attributes in our nested block,
  			// but the legacy SDK doesn't support that, so we just assume it'll
  			// never be necessary to normalize those. (Incorrect usage in any
  			// other SDK would be caught and returned as an error before we
  			// get here.)
  			vals[name] = cty.ListVal([]cty.Value{unknownBlockStub(&blockS.Block)})
  		case configschema.NestingSet:
  			vals[name] = cty.SetVal([]cty.Value{unknownBlockStub(&blockS.Block)})
  		case configschema.NestingMap:
  			// A nesting map can never be unknown since we then wouldn't know
  			// what the keys are. (Legacy SDK doesn't support NestingMap anyway,
  			// so this should never arise.)
  			vals[name] = cty.MapValEmpty(blockS.Block.ImpliedType())
  		}
  	}
  	return cty.ObjectVal(vals)
  }
docs: // unknownBlockStub constructs an object value that approximates an unknown
  // block by producing a known block object with all of its leaf attribute
  // values set to unknown.
  //
  // Blocks themselves cannot be unknown, so if the legacy SDK tries to return
  // such a thing, we'll use this result instead. This convention mimics how
  // the dynamic block feature deals with being asked to iterate over an unknown
  // value, because our value-checking functions already accept this convention
  // as a special case.