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

Reply via email to