This is an archive of the discontinued LLVM Phabricator instance.

[clang-tidy] Add support for boolean check options.
AbandonedPublic

Authored by alexfh on Oct 3 2014, 9:20 AM.

Details

Reviewers
None

Diff Detail

Event Timeline

alexfh updated this revision to Diff 14383.Oct 3 2014, 9:20 AM
alexfh retitled this revision from to [clang-tidy] Add support for boolean check options..
alexfh updated this object.
alexfh edited the test plan for this revision. (Show Details)
alexfh added a reviewer: klimek.
alexfh added subscribers: curdeius, Unknown Object (MLST).
klimek added inline comments.Oct 6 2014, 1:43 AM
clang-tidy/ClangTidy.h
45–46

If there's a reason not to use StringRef instead of const char*, it should be in ALL CAPS, RED AND BOLD in the comment above ;)

62–74

Why do we want unknown values to give Default? If we do, we should put it into the comments; can we rather make them given an error while parsing, and then do:
return get(LocalName, Default ? "true" : "false") == "true";

69–74

Will is_integral trigger for 128 bit integral values, and then narrow?

We can generalize go a step further get() and store() to all types that specialize llvm::yaml traits.
This could remove the code duplication and simplify the creation of clang-tidy checks having options of enumeration type.

clang-tidy/ClangTidy.h
45–46

What's the reason for changing std::string Default to const char* Default?
It "breaks" somehow the symmetry of T get(StringRef, T), i.e. taking T type as Default and returning T.

67

That's essentially the same code as in llvm/lib/Support/YAMLTraits.cpp::651.

67–74

Couldn't we just generalize this approach to all the types that specialize llvm::yaml::XYZTraits (XYZ being one of Scalar/ScalarEnumeration/...) ?

An ugly hack (working at least for enums specializing ScalarEnumerationTraits) would be:

template <typename T>
typename std::enable_if<!std::is_integral<T>::value &&
                            !std::is_convertible<T, std::string>::value,
                        T>::type
get(StringRef LocalName, T Default) const {
  std::string Value = get(LocalName, "");
  T Result = Default;
  if (!Value.empty()) {
    std::stringstream Content;
    Content << LocalName.str() << ": " << Value;
    llvm::yaml::Input Input(Content.str());
    Input.setCurrentDocument();
    Input.mapOptional(LocalName.data(), Result);
    if (Input.error())
      Result = Default;
  }
  return Result;
}

The hack is ugly since it uses Input::setCurrentDocument() (which should probably be private) and Input::mapOptional() and not Input::operator>>. That is so, because otherwise we would need creating a type specializing llvm::yaml::MappingTraits which would essentially do an operation equivalent to Input.mapOptional(LocalName.str(), Result);.

78–79

Similarly to above get() method, one can generalize the approach.
Another ugly hack would be:

template <typename T>
typename std::enable_if<!std::is_integral<T>::value &&
                            !std::is_convertible<T, std::string>::value,
                        void>::type
store(ClangTidyOptions::OptionMap &Options, StringRef LocalName,
      T Value) const {
  std::string Text;
  llvm::raw_string_ostream Content(Text);
  llvm::yaml::Output Output(Content);
  Output.beginMapping();
  if (Output.preflightDocument(0))
    Output.mapOptional(LocalName.data(), Value);
  Output.endMapping();

  auto T = Content.str();
  // Skip past ": " between key and value.
  auto Off = T.find(':', LocalName.size());
  assert(Off != std::string::npos);
  T = T.substr(Off + 2, std::string::npos);
  // FIXME(mkurdej): Strip enclosing apostrophes "'" if present.
  Options[NamePrefix + LocalName.str()] = T;
}
alexfh added inline comments.Oct 6 2014, 9:52 AM
clang-tidy/ClangTidy.h
67–74

Couldn't we just generalize this approach to all the types that specialize llvm::yaml::XYZTraits (XYZ being one of Scalar/ScalarEnumeration/...) ?

That sounds interesting. I'll try something along these lines.

alexfh updated this revision to Diff 14460.Oct 6 2014, 10:04 AM

Use llvm::yaml::ScalarTraits to convert all built-it and string types. No
support for enumerations though, as it is bound tighter to llvm::yaml::IO, and
no error handling (not sure if we need it).

alexfh added inline comments.Oct 6 2014, 10:07 AM
clang-tidy/ClangTidy.h
62–74

We can't error out early enough as check options are parsed after the configuration is read. So I don't currently see a better way than to fall back to default.

klimek added inline comments.Oct 6 2014, 10:21 AM
clang-tidy/ClangTidy.h
62–74

Fall back to false?

alexfh added inline comments.Oct 6 2014, 5:15 PM
clang-tidy/ClangTidy.h
62–74

Could you explain why this would be better?

klimek added inline comments.Oct 7 2014, 1:05 AM
clang-tidy/ClangTidy.h
62–74

If we catch the error in the config later on, and give a nice error, it doesn't matter, and the code gets simpler.

If we cannot catch the error, I would like an explicitly given config to not lead to different behavior depending on the default value; I find that unexpected. Plus, I think it would make the code simpler.

alexfh added inline comments.Oct 7 2014, 3:33 AM
clang-tidy/ClangTidy.h
62–74

If we catch the error in the config later on, and give a nice error,

Not sure yet if this can be done nicely.

it doesn't matter, and the code gets simpler.

It doesn't make the code simpler, as we still need to handle missing value and return Default.

If we cannot catch the error, I would like an explicitly given config to not lead to different behavior depending on the default value; I find that unexpected.

Why? On the opposite, I think that the case of invalid value is close to the case of missing value, so using the same default is more intuitive.

Plus, I think it would make the code simpler.

Not really. The code is now common for all types having ScalarTraits<>. It also falls back to Default in case of error, and there I don't see another reasonable value to fall back to in case the parsing fails.

klimek added inline comments.Oct 7 2014, 4:03 AM
clang-tidy/ClangTidy.h
62–74

Ok, I'm convinced:
If this is really the same handling for all scalar values, I find it significantly worse if we silently eat up parsing errors. I'd prefer crashing with an error message if that's the only way possible.

alexfh updated this revision to Diff 14659.Oct 9 2014, 9:52 AM

Let the check decide what to do with invalid option values.

alexfh added inline comments.Oct 9 2014, 10:06 AM
clang-tidy/ClangTidy.h
62–74

Crashing doesn't seem like a the best solution. We could (mis)use diagnostic output to report configuration errors. WDYT?

For now, I've changed OptionsView::get<> to propagate errors to the caller. And the caller can decide whether to crash in case of an error or handle it in a different way. I've changed two current users of this method to use ErrorOr<>::operator* which asserts that there's no value. Do we want to add some kind of a getValueOrCrash method to ErrorOr<> which would use report_fatal_error instead?

curdeius added inline comments.Oct 9 2014, 12:45 PM
clang-tidy/ClangTidy.h
45–54

Won't it be counterintuitive for the user of this code that get(..., string) returns a string and that get(..., T) returns ErrorOr<T>?

alexfh added inline comments.Oct 9 2014, 4:02 PM
clang-tidy/ClangTidy.h
45–54

The difference is that the string version doesn't do any parsing, so it can't encounter any errors. So maybe it should be named differently from the template version to avoid confusion.

curdeius added inline comments.Oct 10 2014, 12:17 AM
clang-tidy/ClangTidy.h
45–54

Yes, I know that, but still it's a bit weird in my eyes to have functions with same name behaving as differently as this.
Renaming it is one valid approach.
Another one would be to add an argument indicating that something went wrong, e.g. get(..., T Default, bool *Invalid = nullptr).
It would be up to the user to handle the error or not (on error, get will return Default).
In any case, we don't pass any more information with error_code than with a boolean...

What are your thoughts about it?

alexfh added inline comments.Oct 10 2014, 5:47 AM
clang-tidy/ClangTidy.h
45–54

ScalarTraits::input returns a StringRef describing the error. We can also use StringRef to avoid impedance mismatch and make use of these strings in our error messages.

Or maybe ScalarTraits<> should be changed to use std::error_code with custom errors. I'll ask the authors' opinion.

klimek added inline comments.Oct 10 2014, 5:52 AM
clang-tidy/ClangTidy.h
45–54

I'd vote for renaming.

62–74

+1 to some form of diagnostic. Also +1 for handing to to the caller.
Is there a way where the caller can just ask whether there's a value?

alexfh added inline comments.Oct 10 2014, 6:05 AM
clang-tidy/ClangTidy.h
62–74

We can add OptionsView::get overloads that return Optional<Something> (though not sure why would callers bother using it instead of the versions with default).

klimek added inline comments.Oct 10 2014, 6:13 AM
clang-tidy/ClangTidy.h
62–74

I meant as opposed to an error. Otherwise I don't understand your last comment above:
""" I've changed two current users of this method to use ErrorOr<>::operator* which asserts that there's no value. Do we want to add some kind of a getValueOrCrash method to ErrorOr<> which would use report_fatal_error instead?"""

alexfh updated this revision to Diff 15042.Oct 16 2014, 1:25 PM

Complete rewrite of the options access and validation code. Now modules can
provide typed default values for the options defined by module's checks.
Validation happens during option parsing and results in the same kind of error
reporting as other configuration format errors.

Actually, the actual command-line workflow is broken now, so I'm working on fixing it and adding a test. But I believe, the fix won't require much change, so it shouldn't postpone the review.

alexfh updated this revision to Diff 15047.Oct 16 2014, 1:57 PM

Fix for the command-line workflow (proper tests for this will be later).

alexfh updated this revision to Diff 15142.Oct 20 2014, 9:25 AM

Rebased the patch on top of D5821.

PTAL.

klimek added inline comments.Oct 20 2014, 11:50 AM
clang-tidy/ClangTidyOptions.h
79–97

So, if we want to roll with having the scalar transformations on the class, I'd just make Value private and overload get() and store() for string types - that would also make the outer code symmetrical.

Second, I'm slightly confused about storing the Validate function with the option - it seems to be completely unrelated (it's about the option type, not about the option value).

klimek resigned from this revision.Jul 3 2015, 6:43 AM
klimek removed a reviewer: klimek.
alexfh abandoned this revision.Oct 12 2020, 7:08 AM