Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#### :nail_care: Polish

- Allow builds while watchers are running. https://github.com/rescript-lang/rescript/pull/8349
- Restore parsing of the legacy `(. ...)` uncurried syntax for backwards compatibility with libraries still on older ReScript versions; emit a deprecation warning when it is used. Rewatch also surfaces this specific deprecation when it originates from an external dependency so users can report breakage upstream.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
- Restore parsing of the legacy `(. ...)` uncurried syntax for backwards compatibility with libraries still on older ReScript versions; emit a deprecation warning when it is used. Rewatch also surfaces this specific deprecation when it originates from an external dependency so users can report breakage upstream.
- Restore parsing of the legacy `(. ...)` uncurried syntax for backwards compatibility with libraries still on older ReScript versions; emit a deprecation warning when it is used. Rewatch also surfaces this specific deprecation when it originates from an external dependency so users can report breakage upstream. https://github.com/rescript-lang/rescript/pull/8383


#### :house: Internal

Expand Down
62 changes: 58 additions & 4 deletions compiler/syntax/src/res_core.ml
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,19 @@ let tagged_template_literal_attr =

let spread_attr = (Location.mknoloc "res.spread", Parsetree.PStr [])

(* Emit a deprecation warning when the legacy [(. ...)] uncurried syntax is
encountered. Uncurried is the default since ReScript v11, so the leading
dot is no longer meaningful; we still accept it so dependencies on older
libraries keep parsing. *)
let warn_uncurried_dot_syntax ~loc =
Location.prerr_warning loc
(Warnings.Deprecated
( "The `(. ...)` uncurried syntax is deprecated. Uncurried is now the \
default in ReScript — remove the leading dot.",
loc,
loc,
false ))

type argument = {label: Asttypes.arg_label; expr: Parsetree.expression}

type type_parameter = {
Expand Down Expand Up @@ -1858,6 +1871,9 @@ and parse_es6_arrow_expression ?(arrow_attrs = []) ?(arrow_start_pos = None)
{arrow_expr with pexp_loc = {arrow_expr.pexp_loc with loc_start = start_pos}}

(*
* dotted_parameter ::=
* | . parameter (* deprecated uncurried syntax *)
*
* parameter ::=
* | pattern
* | pattern : type
Expand All @@ -1875,10 +1891,14 @@ and parse_es6_arrow_expression ?(arrow_attrs = []) ?(arrow_start_pos = None)
*)
and parse_parameter p =
if
p.Parser.token = Token.Typ || p.token = Tilde
p.Parser.token = Token.Typ || p.token = Tilde || p.token = Dot
|| Grammar.is_pattern_start p.token
then
let start_pos = p.Parser.start_pos in
(if p.Parser.token = Token.Dot then
let dot_loc = mk_loc start_pos p.end_pos in
Parser.next p;
warn_uncurried_dot_syntax ~loc:dot_loc);
let attrs = parse_attributes p in
if p.Parser.token = Typ then (
Parser.next p;
Expand Down Expand Up @@ -1978,6 +1998,7 @@ and parse_parameter_list p =
* | _
* | lident
* | ()
* | (.) (* deprecated uncurried syntax *)
* | ( parameter {, parameter} [,] )
*)
and parse_parameters p : fundef_type_param option * fundef_term_param list =
Expand Down Expand Up @@ -2026,6 +2047,10 @@ and parse_parameters p : fundef_type_param option * fundef_term_param list =
] )
| Lparen ->
Parser.next p;
(if p.Parser.token = Token.Dot then
let dot_loc = mk_loc p.start_pos p.end_pos in
Parser.next p;
warn_uncurried_dot_syntax ~loc:dot_loc);
let type_params, term_params = parse_parameter_list p in
let term_params =
if term_params <> [] then term_params else [unit_term_parameter ()]
Expand Down Expand Up @@ -3964,13 +3989,31 @@ and parse_switch_expression p =
* | ~ label-name = ? _ (* syntax sugar *)
* | ~ label-name = ? expr : type
*
* dotted_argument ::=
* | . argument (* deprecated uncurried syntax *)
*)
and parse_argument p : argument option =
if
p.Parser.token = Token.Tilde
|| p.token = Underscore
|| p.token = Dot || p.token = Underscore
|| Grammar.is_expr_start p.token
then parse_argument2 p
then
match p.Parser.token with
| Dot ->
let dot_loc = mk_loc p.start_pos p.end_pos in
Parser.next p;
warn_uncurried_dot_syntax ~loc:dot_loc;
(match p.token with
(* apply(.) — legacy uncurried unit call *)
| Rparen ->
let unit_expr =
Ast_helper.Exp.construct
(Location.mknoloc (Longident.Lident "()"))
None
in
Some {label = Asttypes.Nolabel; expr = unit_expr}
| _ -> parse_argument2 p)
| _ -> parse_argument2 p
else None

and parse_argument2 p : argument option =
Expand Down Expand Up @@ -4668,6 +4711,9 @@ and parse_type_alias p typ =
* note:
* | attrs ~ident: type_expr -> attrs are on the arrow
* | attrs type_expr -> attrs are here part of the type_expr
*
* dotted_type_parameter ::=
* | . type_parameter (* deprecated uncurried syntax *)
*)
and parse_type_parameter ?current_type_name_path ?inline_types_context
?positional_type_name_path p =
Expand All @@ -4678,8 +4724,16 @@ and parse_type_parameter ?current_type_name_path ?inline_types_context
[doc_comment_to_attribute loc s]
| _ -> []
in
if p.Parser.token = Token.Tilde || Grammar.is_typ_expr_start p.token then
if
p.Parser.token = Token.Tilde
|| p.token = Dot
|| Grammar.is_typ_expr_start p.token
then
let start_pos = p.Parser.start_pos in
(if p.Parser.token = Token.Dot then
let dot_loc = mk_loc start_pos p.end_pos in
Parser.next p;
warn_uncurried_dot_syntax ~loc:dot_loc);
let attrs = doc_attr @ parse_attributes p in
match p.Parser.token with
| Tilde -> (
Expand Down
6 changes: 3 additions & 3 deletions compiler/syntax/src/res_grammar.ml
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ let is_pattern_start = function
| _ -> false

let is_parameter_start = function
| Token.Typ | Tilde -> true
| Token.Typ | Tilde | Dot -> true
| token when is_pattern_start token -> true
| _ -> false

Expand Down Expand Up @@ -206,7 +206,7 @@ let is_typ_expr_start = function
| _ -> false

let is_type_parameter_start = function
| Token.Tilde -> true
| Token.Tilde | Dot -> true
| token when is_typ_expr_start token -> true
| _ -> false

Expand Down Expand Up @@ -239,7 +239,7 @@ let is_record_row_string_key_start = function
| _ -> false

let is_argument_start = function
| Token.Tilde | Underscore -> true
| Token.Tilde | Dot | Underscore -> true
| t when is_expr_start t -> true
| _ -> false

Expand Down
57 changes: 55 additions & 2 deletions rewatch/src/build/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -940,10 +940,13 @@ fn compile_file(

if helpers::contains_ascii_characters(&err) {
if package.is_local_dep {
// suppress warnings of external deps
Ok(Some(err))
} else {
Ok(None)
// Warnings from external deps are suppressed by default —
// users can't act on them. A small allow-list of critical
// deprecations still gets through so breakage signals are
// visible (and can be reported upstream).
Ok(retain_critical_external_warnings(&err))
}
} else {
Ok(None)
Expand All @@ -952,6 +955,33 @@ fn compile_file(
}
}

/// Filter a bsc stderr capture to the warning blocks the user needs to see
/// even when they originate in an external dependency.
///
/// Currently preserved:
/// - Warning 3 deprecations mentioning the legacy `(. ...)` uncurried syntax.
/// These indicate source that parses today but is scheduled for removal, so
/// consumers need to hear about them even when the code isn't theirs.
pub(super) fn retain_critical_external_warnings(stderr: &str) -> Option<String> {
const UNCURRIED_DOT_MARKER: &str = "`(. ...)` uncurried syntax";
if !stderr.contains(UNCURRIED_DOT_MARKER) {
return None;
}
// bsc prints each warning as its own block separated by a blank-line pair
// (three consecutive newlines). Split on that boundary, keep the blocks
// that mention the marker, and re-join with the same separator so the
// output is indistinguishable from the original.
let kept: Vec<&str> = stderr
.split("\n\n\n")
.filter(|block| block.contains(UNCURRIED_DOT_MARKER))
Comment on lines +1132 to +1133

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize line endings before splitting warning blocks

The filter assumes warning blocks are separated by "\n\n\n", but on Windows stderr commonly uses CRLF ("\r\n"), so this split does not break the stream into blocks. When an external dependency emits both the uncurried-dot deprecation and unrelated warnings, retain_critical_external_warnings will keep the entire stderr payload instead of only the critical block, effectively disabling suppression for those packages in Windows builds (this helper is used by both parse and compile warning paths).

Useful? React with 👍 / 👎.

.collect();
if kept.is_empty() {
None
} else {
Some(kept.join("\n\n\n"))
}
}

pub fn mark_modules_with_deleted_deps_dirty(build_state: &mut BuildState) {
build_state.modules.iter_mut().for_each(|(_, module)| {
if !module.deps.is_disjoint(&build_state.deleted_modules) {
Expand Down Expand Up @@ -1048,3 +1078,26 @@ pub fn mark_modules_with_expired_deps_dirty(build_state: &mut BuildCommandState)
}
});
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn retain_critical_external_warnings_returns_none_without_marker() {
let input = "\n Warning number 26\n foo.res:1:1\n\n unused variable x.\n";
assert_eq!(retain_critical_external_warnings(input), None);
}

#[test]
fn retain_critical_external_warnings_keeps_uncurried_dot_block() {
let input = concat!(
"\n Warning number 26\n foo.res:1:1\n\n unused variable x.\n",
"\n\n\n Warning number 3\n bar.res:5:10\n\n ",
"deprecated: The `(. ...)` uncurried syntax is deprecated.\n",
);
let kept = retain_critical_external_warnings(input).expect("uncurried-dot warning should survive");
assert!(kept.contains("`(. ...)` uncurried syntax"));
assert!(!kept.contains("unused variable"));
}
}
26 changes: 24 additions & 2 deletions rewatch/src/build/parse.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::build_types::*;
use super::compile::retain_critical_external_warnings;
use super::logs;
use super::namespaces;
use crate::build::packages::Package;
Expand Down Expand Up @@ -139,7 +140,18 @@ pub fn generate_asts(
logs::append(package, &stderr_warnings);
stderr.push_str(&stderr_warnings);
}
Ok((_path, Some(_))) | Ok((_path, None)) => {
Ok((_path, Some(stderr_warnings))) => {
source_file.implementation.parse_state = ParseState::Success;
source_file.implementation.parse_dirty = false;
// External dep: surface only the critical warnings
// (e.g. legacy `(. ...)` uncurried syntax) so
// downstream users can report breakage upstream.
if let Some(kept) = retain_critical_external_warnings(&stderr_warnings) {
logs::append(package, &kept);
stderr.push_str(&kept);
}
}
Ok((_path, None)) => {
source_file.implementation.parse_state = ParseState::Success;
source_file.implementation.parse_dirty = false;
}
Expand Down Expand Up @@ -167,7 +179,17 @@ pub fn generate_asts(
logs::append(package, &stderr_warnings);
stderr.push_str(&stderr_warnings);
}
Ok(Some((_, None))) | Ok(Some((_, Some(_)))) => {
Ok(Some((_, Some(stderr_warnings)))) => {
if let Some(interface) = source_file.interface.as_mut() {
interface.parse_state = ParseState::Success;
interface.parse_dirty = false;
}
if let Some(kept) = retain_critical_external_warnings(&stderr_warnings) {
logs::append(package, &kept);
stderr.push_str(&kept);
}
}
Ok(Some((_, None))) => {
if let Some(interface) = source_file.interface.as_mut() {
interface.parse_state = ParseState::Success;
interface.parse_dirty = false;
Expand Down
82 changes: 82 additions & 0 deletions rewatch/tests/compile/18-external-dep-uncurried-dot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/bash
# Verifies that the legacy `(. args)` uncurried-syntax deprecation warning
# is surfaced even when it originates from an external (node_modules)
# dependency. Unrelated warnings from external deps remain suppressed.

cd $(dirname $0)
source "../utils.sh"

bold "Test: Uncurried-dot deprecation warning surfaces from external deps"

fixture=$(mktemp -d 2>/dev/null || mktemp -d -t rewatch-ext-uncur)
trap "rm -rf '$fixture'" EXIT

mkdir -p "$fixture/src"
mkdir -p "$fixture/node_modules/legacy-pkg/src"

cat > "$fixture/package.json" <<'EOF'
{
"name": "host",
"version": "0.0.1"
}
EOF

cat > "$fixture/rescript.json" <<'EOF'
{
"name": "host",
"sources": { "dir": "src" },
"dependencies": ["legacy-pkg"],
"package-specs": { "module": "commonjs", "in-source": true },
"suffix": ".bs.js"
}
EOF

cat > "$fixture/src/Main.res" <<'EOF'
let _ = LegacyPkg.add(1, 2)
EOF

cat > "$fixture/node_modules/legacy-pkg/package.json" <<'EOF'
{ "name": "legacy-pkg", "version": "0.0.1" }
EOF

cat > "$fixture/node_modules/legacy-pkg/rescript.json" <<'EOF'
{
"name": "legacy-pkg",
"sources": { "dir": "src" },
"package-specs": { "module": "commonjs", "in-source": true },
"suffix": ".bs.js"
}
EOF

# Includes: the uncurried-dot deprecation (must surface) and an unused value
# (warning 26, must stay suppressed because it's in an external dep).
cat > "$fixture/node_modules/legacy-pkg/src/LegacyPkg.res" <<'EOF'
let unusedInDep = 99
let add = (. a, b) => a + b
EOF

cd "$fixture"
stderr_output=$(rewatch 2>&1 1>/dev/null)
build_status=$?

if [ $build_status -ne 0 ]; then
error "Build failed"
printf "%s\n" "$stderr_output" >&2
exit 1
fi

if echo "$stderr_output" | grep -qF '`(. ...)` uncurried syntax'; then
success "Uncurried-dot deprecation warning is shown for external dep"
else
error "Expected '(. ...)' uncurried-syntax deprecation in stderr"
printf "%s\n" "$stderr_output" >&2
exit 1
fi

if echo "$stderr_output" | grep -qF 'unused value'; then
error "Unrelated warnings from external deps should be suppressed"
printf "%s\n" "$stderr_output" >&2
exit 1
else
success "Unrelated external-dep warnings are still suppressed"
fi
1 change: 1 addition & 0 deletions rewatch/tests/suite.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ fi
./compile/12-compile-dev-dependencies.sh &&
./compile/13-no-infinite-loop-with-cycle.sh &&
./compile/17-prod-flag.sh &&
./compile/18-external-dep-uncurried-dot.sh &&
./compile/14-no-testrepo-changes.sh &&
./compile/15-no-new-files.sh &&
./compile/16-snapshots-unchanged.sh &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@

Warning number 3
syntax_tests/data/parsing/errors/other/regionMissingComma.res:2:31

1 │ external make: (
2 │ ~style: ReactDOMRe.Style.t=?.
3 │ ~image: bool=?,
4 │ ) => React.element = "ModalContent"

deprecated: The `(. ...)` uncurried syntax is deprecated. Uncurried is now the default in ReScript — remove the leading dot.


Syntax error!
syntax_tests/data/parsing/errors/other/regionMissingComma.res:2:31

Expand Down
Loading
Loading