hokein accepted this revision. hokein added a comment. This revision is now accepted and ready to land.
Thanks, looks good. ================ Comment at: clang-tools-extra/clangd/ConfigCompile.cpp:65 + if (F.HasUnrecognizedCondition) + Conditions.push_back([&](const Params &) { return false; }); + ---------------- sammccall wrote: > hokein wrote: > > 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. > > so the member Condition in Fragment will be enhanced to something like > > std::vector<XXX> Conditions in the future? > > Ah, not quite... > Fragment is supposed to closely follow the YAML format, which is: > `If: { PathMatch: foo, Platform: bar }` > not > `If: [ {PathMatch: foo}, {Platform: bar} ]` > That is, the partitioning of parts of the `If` block into `Conditions` is > implicit rather than explicit. (It's basically partitioning on the map keys, > but maybe there'll be exceptions). > > The subtle difference between Fragment::Condition and > CompiledFragmentImpl::Condition is confusing though. I think renaming the > former to Fragment::If is much clearer (not sure why I didn't name it that in > the first place). > > > Since Fragment and CompiledFragment is 1:1 mapping, I think it is important > > to keep their implementation aligned > > I think the fragment and the fragmentcompiler are pretty well aligned, but I > don't think duplicating the fragment structure in the final > CompiledFragmentImpl is a good idea. > > The reason is captured pretty well in the essay "Parse, don't validate". > Through compilation here we want to examine the config and establish that we > can apply this configuration efficiently, and without any more errors. The > type-driven way to do that is to emit a function that applies the > configuration and whose signature doesn't allow for errors. > > https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ oh, I think I understand how the design is driven like this now. that makes sense. I think it would be helpful to add some documentation (and give a concrete config example, the example you gave is good enough) for CompiledFragmentImpl::Conditions. ================ Comment at: clang-tools-extra/clangd/ConfigCompile.cpp:46 + if (!C(P)) { + dlog("config::Fragment {0}: condition not met", this); + return false; ---------------- sammccall wrote: > hokein wrote: > > 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. > I've changed these from config::Fragment to "Config fragment" to be less > misleading. I'm not sure sprinkling in internal class names helps much vs > having them be clear sentences, I always end up searching for the log string > to see exactly where it comes from anyway. > > This is dlog, it would be nice to make it vlog but I think it's just way too > spammy, we'll get several of these for every file that's opened and > background-indexed... > > > we might need a tracing mechanism (at least some useful logs) > > My secret plan is to have a clangd flag like `clangd --check <filename>` that > will *not* run the language server, but instead dump a bunch of information > about how clangd would process that file (stringing together CDB, config, > query-driver, diagnostics...). This would miss config-over-LSP but in every > other respect seems much simpler than trying to find ways to extract this > stuff live and get it in front of the user somehow. that sounds good. ================ Comment at: clang-tools-extra/clangd/ConfigFragment.h:100 /// Each separate condition must match (combined with AND). /// When one condition has multiple values, any may match (combined with OR). /// ---------------- I think this comment is important, but it is a bit vague (not quite friendly to readers without much config context), we might want to refine it -- would be nice to clearly state what's a condition, e.g. "PathMatch" is a condition, "Platform" is a condition. maybe separate the rename to a separate patch, but up to you. ================ Comment at: clang-tools-extra/clangd/ConfigFragment.h:108 + struct IfBlock { /// The file being processed must fully match a regular expression. std::vector<Located<std::string>> PathMatch; ---------------- nit: maybe explicitly spell this is an `OR` match. 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