hokein added inline comments.
================ Comment at: clang-tools-extra/clangd/ConfigCompile.cpp:65 + if (F.HasUnrecognizedCondition) + Conditions.push_back([&](const Params &) { return false; }); + ---------------- sammccall wrote: > hokein wrote: > > I think if this case happened, we might just bail out directly -- adding > > the regex conditions seems unnecessary, we never execute them on Line 127 > > (but we might still need computing the regex for diagnostics). > > > > and I'm not sure the motivation of using `vector` for `Conditions` and > > `Apply`, it looks like Apply is just 1 element (if we do the bailout for > > conditions, `Conditions` will be at most 1 element). > > > > I feel like using a single unique_function for `Conditions` and `Apply` > > might be a better fit with `Fragment`. There is a comment in > > `ConfigFragment.h`: > > > > > Each separate condition must match (combined with AND). > > > When one condition has multiple values, any may match (combined with OR). > > > > so my reading is that: > > > > - `Fragment` and `CompiledFragment` is a 1:1 mapping > > - A `Fragment::Condition` represents a single condition, so the AND match > > applies for different `Fragment`s, which is in ConfigProvider? > > - A single condition can have multiple values (`PathMatch`), we use OR match > > > > Is that correct? > > I think if this case happened, we might just bail out directly -- adding > > the regex conditions seems unnecessary, we never execute them on Line 127 > > (but we might still need computing the regex for diagnostics). > > > > and I'm not sure the motivation of using vector for Conditions and Apply, > > it looks like Apply is just 1 element (if we do the bailout for conditions, > > Conditions will be at most 1 element). > > So the explanation for both of these things is that this file is going to > grow a lot, and in particular these sections handling PathMatch etc are going > to occur again and again, so we need a scalable pattern. I've deliberately > limited the scope of the first patchset to only support one condition, one > setting to apply etc, but you have to imagine there are likely to be 5 > conditions and maybe 25 settings. > > So with that in mind: > - yes, actually saving the conditions isn't necessary, but we do need to > evaluate them for side-effects (diagnostics) and it's simpler not to keep > track of them. Optimizing performance of broken configs is not a big win :-) > - it's marginally simpler to have only one Apply function, but much simpler > if separate parts of compile() can push settings independently. Same goes for > condition in fact... > > > Fragment and CompiledFragment is a 1:1 mapping > > Yes. I think this is somewhere where the naming is helpful at least :-) > > > A Fragment::Condition represents a single condition, so the AND match > > applies for different Fragments, which is in ConfigProvider? > > The AND match applies to the multiple Conditions in a single > CompiledFragment. Each fragment only considers its own conditions. > So given: > ``` > If: > Platform: win > PathMatch: [foo1/.*, foo2/.*] > CompileFlags: > Add: -foo > --- > CompileFlags: > Add: -bar > ``` > > We end up with... one Provider, suppling two CompiledFragments (each compiled > from a separate Fragment). > > The first CompiledFragment has two Conditions, conceptually: > - `Params.Platform == "win"` > - `Regex("foo1/.*").matches(Params.File) || > Regex("foo2/.*").matches(Params.File)` > > The second CompiledFragment has no Conditions. Each fragment has one Apply > function. > > The CompiledFragments are applied independently. They each consider their own > Conditions, ANDing them together. > > So CompiledFragment #1 checks both conditions. If any fails, it bails out. > Otherwise it runs its Apply function, adding `-foo`. > > Then CompiledFragment #2 runs. It has no conditions, so never bails out. It > adds `-bar`. oh, thanks for the explanation, this helps me a lot to understand the code. so the member `Condition` in `Fragment` will be enhanced to something like `std::vector<XXX> Conditions` in the future? If so, can we defer that for `CompiledFragment` as well? Since `Fragment` and `CompiledFragment` is 1:1 mapping, I think it is important to keep their implementation aligned, it'd help a lot for readers to understand the code. The condition logic (AND, OR) is subtle and complicated, I was reading this part of code back and forth, still inferred it in an incorrect way. ================ Comment at: clang-tools-extra/clangd/ConfigCompile.cpp:40 + +struct Impl { + std::vector<llvm::unique_function<bool(const Params &) const>> Conditions; ---------------- nit: I'd use a more verbose name, `CompiledFragmentImpl`. ================ Comment at: clang-tools-extra/clangd/ConfigCompile.cpp:46 + if (!C(P)) { + dlog("config::Fragment {0}: condition not met", this); + return false; ---------------- maybe `Impl::applying config ...` to allow better debug logging? this reminds us: since the config can easily become a monster, think about multiple config files from different sources (user config file, project config file, LSP, flags) are applied together, we might need a tracing mechanism (at least some useful logs) to help us diagnose a final complicated config. no need to do anything in this patch, just something we should keep in mind. ================ Comment at: clang-tools-extra/clangd/ConfigCompile.cpp:126 +CompiledFragment Fragment::compile(DiagnosticCallback D) && { + llvm::StringRef SourceFile = "<unknown>"; + std::pair<unsigned, unsigned> LineCol = {0, 0}; ---------------- nit: maybe name it ConfigFile, SourceFile sounds like a .cpp file. ================ Comment at: clang-tools-extra/clangd/ConfigFragment.h:35 +#include "ConfigProvider.h" #include "llvm/ADT/Optional.h" ---------------- the dependence ( `ConfigFragment.h` depends on `ConfigProvider.h`) seems a bit counterintuitive... ================ Comment at: clang-tools-extra/clangd/ConfigFragment.h:81 + /// The returned function is a cheap-copyable wrapper of refcounted internals. + CompiledFragment compile(DiagnosticCallback) &&; + ---------------- this API is neat, thanks! ================ Comment at: clang-tools-extra/clangd/ConfigProvider.h:47 + /// This always produces a usable compiled fragment (errors are recovered). + explicit CompiledFragment(Fragment, DiagnosticCallback); + ---------------- sammccall wrote: > sammccall wrote: > > hokein wrote: > > > sammccall wrote: > > > > hokein wrote: > > > > > Would it be more nature to have a function `compile`(or so) to do the > > > > > actual compile (Fragment -> CompiledFragment) rather than doing it in > > > > > a constructor? > > > > I'm not sure. The idea that the input/result is > > > > Fragment/CompiledFragment, and that the compilation cannot fail, > > > > suggests to me that a constructor is OK here. > > > > On the other hand, the DiangosticCallback param (which we don't keep a > > > > reference to) is a bit weird. > > > > > > > > So I don't really feel strongly about it one way or the other, happy to > > > > change it if you do think it would be significantly better. > > > You point is fair. > > > I'm not sure using `compile` is *significantly* better, but it is better > > > IMO -- conceptually, the class name `CompiledFragment` implies that there > > > should be a `compile` API (this was my impression when reading the code > > > at the first time) > > > > > > it might be ok to keep as-is. > > Argh, you convinced me this would be better, but I can't come up with a > > form I like. > > > > - `static CompiledFragment::compile` repeats itself in a weird way. But any > > other option requires messing around with friend functions, which feels off > > - `Fragment::compile` is maybe the most natural, but the dependencies are > > backwards (needs the CompiledFragment type, it gets implemented in the > > wrong file...). > > - standalone `compile()` is maybe the most palatable, but seems a little > > undiscoverable > > - having a function return CompiledFragment means that putting it into a > > shared_ptr is annoying work, and breaks our pointer-logging trick, but > > having the factory function return a pointer also seems odd. > > > > I'm starting to think maybe the CompiledFragment class shouldn't be public > > at all, and we should just `using CompiledFragment = > > unique_function<bool(const Params&, Config&) const>`. WDYT? > > I'm starting to think maybe the CompiledFragment class shouldn't be public > > at all > > Thinking more about this: > > - making it std::function<...> instead lets us wrap the shared_ptr inside > which makes a few APIs a bit cleaner > - as its an interface type, this leaves config providers free to use other > ways of manipulating config rather than going via fragments, which is > potentially nice for embedders > - the main downside is we're committing to not exposing any more structure > (e.g. querying a fragment for which directory it applies to) > > starting to quite like this idea. SG Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D82612/new/ https://reviews.llvm.org/D82612 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits