Skip to content

fix(checker): bind data arguments before masked expressions - #447

Open
sims1253 wants to merge 2 commits into
mainfrom
fix/data-mask-argument-binding
Open

fix(checker): bind data arguments before masked expressions#447
sims1253 wants to merge 2 commits into
mainfrom
fix/data-mask-argument-binding

Conversation

@sims1253

@sims1253 sims1253 commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Data-mask expressions now use the argument bound to the data formal, even when it appears later in the call. For example, dplyr::mutate(new = .data$missing, .data = d) reports RY060 when d has a known schema without that column.

The schema and ordinary call paths share the source lookup. Data arguments are checked once in the caller scope. Tidy-select helpers such as dplyr::vars(1L, .data$column) keep an unknown mask, and named arguments preserve result schemas and with() return types.

Validation: workspace tests, Clippy with warnings denied, formatting, and the full R oracle pass. Tests cover valid and missing columns, absent data bindings, named joins, and quoting helpers. The frozen 472-package comparison is unchanged: 3,106 diagnostics, none added or removed. The comparison caught a valid vars_rename() call with a character vector of names; the follow-up keeps its .data pronoun opaque and adds an R oracle control.

Closes #417.

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 36 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: c1fb4bf4-6882-4e08-8100-4efc684efa91

📥 Commits

Reviewing files that changed from the base of the PR and between 92c822a and 4c97468.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • crates/ry-checker/src/infer/args.rs
  • crates/ry-checker/src/infer/call.rs
  • crates/ry-checker/src/nse.rs
  • crates/ry-checker/src/tests/quoting_data_mask.rs
  • crates/ry-checker/testdata/oracle/named_data_masks.R
  • crates/ry-checker/testdata/oracle/named_data_missing_column.R

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the full PR against main (all 7 files), plus verification: the complete ry-checker test suite, clippy --all-targets -D warnings, fmt --check, and six probe scenarios exercising pipes, declared-source formula APIs, and the missing-data fallback. The R oracle fixtures could not be executed in this environment (no Rscript); CI's oracle job covers them.

  • First-formal data-source fallback in data_mask_source_arg — when a signature declares no data_mask_source, the source is the first formal unless it is ... or itself masked/quoted, then resolved by full R argument matching, so named data =/.data =/x = actuals supply the mask from any position while quoting helpers (aes(), vars(), nesting()) keep an unknown mask. I audited the entire vendored population: every signature where this fallback fires has the data frame as its first formal (dplyr .data, tidyr data, base x/_data/data, joins x, recipes recipe — the latter harmlessly yielding an unknown mask).
  • Eager data binding in infer_argument_types — the bound data argument is inferred once in the caller scope before any masked expression, reused for DataMask/TidySelect scopes and the user-S3 dispatch probe; the removed leading_argument_is_masked classification is subsumed by the new rule, and the conditional formula-API behavior (extras evaluate in the caller when declared data is absent) is preserved.
  • Schema path in infer_schema_call — data now comes from the bound formal instead of args.first(); when the data formal is unbound the schema path bails to the general path, which avoids the old false RY010 of inferring a masked expression as data (verified: dplyr::mutate(new = some_unbound_column) is silent). Joins and ExpressionValue results now use formal-mapped types via match_args_to_params, fixing named x =/y = join order and with(expr = ..., data = ...) return values.
  • Tests and oracle fixtures — the four new tests assert exact codes, message contents, and result schemas/columns, so they fail if the binding regresses; the two oracle fixtures pin the R-side behavior of named data arguments.

The stub contract documented on FunctionSig::data_mask_source ("Absent means the traditional first-argument data mask") now matches the implementation exactly.

Pullfrog  | View workflow run | Using openai-compatible/glm-5.3𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — commit 4c97468, the delta since the prior review at 3bc3cf9, with the full-PR diff re-read for context. Re-verified locally: all 767 ry-checker unit tests plus the integration suites pass, cargo clippy --all-targets -- -D warnings and fmt --check are clean, and I reverted the new guard to confirm the added test genuinely bites — without it, tidyselect::vars_rename(c('a', 'b'), B = .data$b) fails with RY061 ("$ operator is invalid for atomic vectors of mode character").

  • Kept the .data pronoun non-atomic in dplyr_data_mask_scope — the pronoun now adopts the data-source type only when it has known columns and is list-like or a data frame; otherwise it stays RType::unknown(). This closes a false RY061 from the first-formal fallback binding a character vector of column names (tidyselect vars_rename/vars-style APIs), where .data$b legitimately names a column rather than $-accessing the atomic vector. The List || data.frame disjunction mirrors the pre-existing schema_is_complete predicate in the same function, so known-schema frames keep their pronoun and unknown-column frames lose nothing (member access there was already conservative).
  • Test and oracle pin — the new unit test asserts no RY060/RY061 for the vars_rename call and was verified to fail without the guard; the added named_data_masks.R lines pin R's character-mask semantics end to end (.data$b resolves to the column name, result c(a = "a", B = "b")), with suppressWarnings absorbing version-dependent deprecation noise.

Pullfrog  | View workflow run | Using openai-compatible/glm-5.3𝕏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Data-mask follow-ups: TidySelect leading-argument guard, and out-of-order named data misses genuine RY060

1 participant