code: func (m *Message) SetField(name string, val starlark.Value) error {
  	fd, ok := m.typ.fields[name]
  	if !ok {
  		return fmt.Errorf("proto message %q has no field %q", m.Type(), name)
  	}
  
  	// Setting a field to None removes it completely.
  	if val == starlark.None {
  		if err := m.checkMutable(); err != nil {
  			return err
  		}
  		delete(m.fields, name)
  		return nil
  	}
  
  	// If assigning to a messaged-valued field (singular or repeated), recognize
  	// dicts and Nones and use them to instantiate values (perhaps empty) of the
  	// corresponding proto type. This allows to construct deeply nested protobuf
  	// messages just by using lists, dicts and primitive values. Python does this
  	// too.
  	val, err := maybeMakeMessages(fd.typ, val)
  	if err != nil {
  		return fmt.Errorf("when constructing %q in proto %q - %s", name, m.Type(), err)
  	}
  
  	// Do a light type check. It doesn't "recurse" into lists or tuples. So it is
  	// still possible to assign e.g. a list of strings to a "repeated int64"
  	// field. This will be discovered later in ToProto when trying to construct
  	// a proto message from Starlark values.
  	if err := checkAssignable(fd.typ, val); err != nil {
  		return fmt.Errorf("can't assign value of type %q to field %q in proto %q - %s", val.Type(), name, m.Type(), err)
  	}
  	if err := m.checkMutable(); err != nil {
  		return err
  	}
  	m.fields[name] = val
  
  	// onChanged hooks is used by oneof's to clear alternatives that weren't
  	// picked.
  	if fd.onChanged != nil {
  		fd.onChanged(m.fields)
  	}
  
  	return nil
  }
docs: // SetField implements starlark.HasSetField.