anchor.diff:                                 not TypeSpecs ->
                                       f("~s~s", [FieldTxt1, CommaSep])
                               end,
  -                LineUpStr2 = lineup(iolist_size(FieldTxt2), 52),
  -                f("~s~s% = ~w, ~s",
  -                  [FieldTxt2, LineUpStr2, FNum, type_to_comment(Field)])
  +                LineUpCol2 = if TypeSpecs -> 52;
  +                               not TypeSpecs -> 40
  +                            end,
  +                LineUpStr2 = lineup(iolist_size(FieldTxt2), LineUpCol2),
  +                TypeComment = type_to_comment(Field, TypeSpecs),
  +                f("~s~s% = ~w~s~s",
  +                  [FieldTxt2, LineUpStr2, FNum,
  +                   [", " || TypeComment /= ""], TypeComment])
           end,
           index_seq(Fields)),
         "\n").
   
  -
  -type_to_typestr(#field{type=Type, occurrence=Occurrence}) ->
  +get_type_specs_by_opts(Opts) ->
  +    Default = false,
  +    proplists:get_value(type_specs, Opts, Default).
  +
  +type_to_typestr(#field{type=Type, occurrence=Occurrence}, Defs) ->
       case Occurrence of
  -        required -> type_to_typestr_2(Type);
  -        repeated -> "[" ++ type_to_typestr_2(Type) ++ "]";
  -        optional -> type_to_typestr_2(Type) ++ " | 'undefined'"
  -    end.
  -
  -type_to_typestr_2(sint32)   -> "integer()";
  -type_to_typestr_2(sint64)   -> "integer()";
  -type_to_typestr_2(int32)    -> "integer()";
  -type_to_typestr_2(int64)    -> "integer()";
  -type_to_typestr_2(uint32)   -> "non_neg_integer()";
  -type_to_typestr_2(uint64)   -> "non_neg_integer()";
  -type_to_typestr_2(bool)     -> "boolean()";
  -type_to_typestr_2(fixed32)  -> "non_neg_integer()";
  -type_to_typestr_2(fixed64)  -> "non_neg_integer()";
  -type_to_typestr_2(sfixed32) -> "integer()";
  -type_to_typestr_2(sfixed64) -> "integer()";
  -type_to_typestr_2(float)    -> "float()";
  -type_to_typestr_2(double)   -> "float()";
  -type_to_typestr_2(string)   -> "string()";
  -type_to_typestr_2(bytes)    -> "binary()";
  -type_to_typestr_2({enum,_}) -> "atom()"; %% FIXME: can be more narrow
  -type_to_typestr_2({msg,M})  -> f("#~p{}", [M]).
  -
  -type_to_comment(#field{type=Type, occurrence=Occurrence}) ->
  +        required -> type_to_typestr_2(Type, Defs);
  +        repeated -> "[" ++ type_to_typestr_2(Type, Defs) ++ "]";
  +        optional -> type_to_typestr_2(Type, Defs) ++ " | 'undefined'"
  +    end.
  +
  +type_to_typestr_2(sint32, _Defs)   -> "integer()";
  +type_to_typestr_2(sint64, _Defs)   -> "integer()";
  +type_to_typestr_2(int32, _Defs)    -> "integer()";
  +type_to_typestr_2(int64, _Defs)    -> "integer()";
  +type_to_typestr_2(uint32, _Defs)   -> "non_neg_integer()";
  +type_to_typestr_2(uint64, _Defs)   -> "non_neg_integer()";
  +type_to_typestr_2(bool, _Defs)     -> "boolean()";
  +type_to_typestr_2(fixed32, _Defs)  -> "non_neg_integer()";
  +type_to_typestr_2(fixed64, _Defs)  -> "non_neg_integer()";
  +type_to_typestr_2(sfixed32, _Defs) -> "integer()";
  +type_to_typestr_2(sfixed64, _Defs) -> "integer()";
  +type_to_typestr_2(float, _Defs)    -> "float()";
  +type_to_typestr_2(double, _Defs)   -> "float()";
  +type_to_typestr_2(string, _Defs)   -> "string()";
  +type_to_typestr_2(bytes, _Defs)    -> "binary()";
  +type_to_typestr_2({enum,E}, Defs)  -> enum_typestr(E, Defs);
  +type_to_typestr_2({msg,M}, _DEfs)  -> f("#~p{}", [M]).
  +
  +enum_typestr(E, Defs) ->
  +    {value, {{enum,E}, Enumerations}} = lists:keysearch({enum,E}, 1, Defs),
  +    string:join(["'"++atom_to_list(EName)++"'" || {EName, _} <- Enumerations],
  +                " | ").
  +
  +type_to_comment(#field{type=Type}, true=_TypeSpec) ->
  +    case Type of
  +        sint32   -> "32 bits";
  +        sint64   -> "32 bits";
  +        int32    -> "32 bits";
  +        int64    -> "32 bits";
  +        uint32   -> "32 bits";
  +        uint64   -> "32 bits";
  +        fixed32  -> "32 bits";
  +        fixed64  -> "32 bits";
  +        sfixed32 -> "32 bits";
  +        sfixed64 -> "32 bits";
  +        {enum,E} -> "enum "++atom_to_list(E);
  +        _        -> ""
  +    end;
  +type_to_comment(#field{type=Type, occurrence=Occurrence}, false=_TypeSpec) ->
       case Occurrence of
           required -> f("~w", [Type]);
           repeated -> "[" ++ f("~w", [Type]) ++ "]";
  -        optional -> f("~w", [Type]) ++ " | 'undefined'"
  +        optional -> f("~w (optional)", [Type])
       end.
   
   lineup(CurrentCol, TargetCol) when CurrentCol < TargetCol ->
  
positive.code: %% `false'. If you set it to `true', you may get into troubles for
  %% messages referencing other messages, when compiling the generated
  %% files.
  file(File, Opts) ->
      case parse_file(File, Opts) of
          {ok, Defs} ->
              possibly_probe_defs(Defs, Opts),
              Ext = filename:extension(File),
              Mod = list_to_atom(filename:basename(File, Ext)),
              Erl = change_ext(File, ".erl"),
              Hrl = change_ext(File, ".hrl"),
              file_write_file(Erl, format_erl(Mod, Defs, Opts), Opts),
              file_write_file(Hrl, format_hrl(Mod, Defs, Opts), Opts);
          {error, _Reason} = Error ->
              Error
      end.
  
negative.code:                                  '$no'   -> "";
                                   Default -> f(" = ~p", [Default])
                               end,
                  TypeStr = f("~s", [type_to_typestr(Field, Defs)]),
                  CommaSep = if I < length(Fields) -> ",";
                                true               -> "" %% last entry
                             end,
  