fslang-spec icon indicating copy to clipboard operation
fslang-spec copied to clipboard

Precedence and associativity of the @ operator is undocumented

Open roboz0r opened this issue 3 months ago • 7 comments

The @ symbol is permitted as the first character of a custom operator.

let (@+) a b = a + b
1 @+ 2

https://github.com/fsharp/fslang-spec/blob/a3b1d61ae54eee380803cd6800c4d02c246bde92/spec/lexical-analysis.md?plain=1#L331

The precedence table, doesn't list the @ operator to define its precedence and associativity.

https://github.com/fsharp/fslang-spec/blob/a3b1d61ae54eee380803cd6800c4d02c246bde92/spec/basic-grammar-elements.md?plain=1#L246-L284

The categorization table doesn't list @ operator.

https://github.com/fsharp/fslang-spec/blob/a3b1d61ae54eee380803cd6800c4d02c246bde92/spec/basic-grammar-elements.md?plain=1#L190-L212

What would be the best way to determine this to update the spec?

roboz0r avatar Oct 01 '25 14:10 roboz0r

Operators starting with @ or ^ (INFIX_AT_HAT_OP) are right-associative and are between the relational operators (&, |, <, >, =, $) and :: in precedence:

%right LARROW
%right COLON_EQUALS
%nonassoc pat_tuple expr_tuple
%left COMMA
%nonassoc open_range_expr
%left DOT_DOT /* for matrix.[1..2, 3..4] the ".." has higher precedence than expression "2 COMMA 3" */
%nonassoc interpolation_fill /* "...{3,N4}..." .NET style fill has higher precedence than "e COMMA e" */
%nonassoc paren_pat_colon
%nonassoc paren_pat_attribs
%left OR BAR_BAR JOIN_IN
%left AND
%left AND_BANG
%left AMP AMP_AMP
%nonassoc pat_conj
%nonassoc expr_not
%left COLON_GREATER COLON_QMARK_GREATER
%left INFIX_COMPARE_OP DOLLAR LESS GREATER EQUALS INFIX_BAR_OP INFIX_AMP_OP
%right INFIX_AT_HAT_OP
%right COLON_COLON
%nonassoc pat_isinst
%left COLON_QMARK
%left PLUS_MINUS_OP MINUS expr_prefix_plus_minus ADJACENT_PREFIX_OP
%left INFIX_STAR_DIV_MOD_OP STAR PERCENT_OP
%right INFIX_STAR_STAR_OP
%left QMARK_QMARK
%left head_expr_adjacent_minus
%left expr_app expr_assert expr_lazy LAZY ASSERT
%left arg_expr_adjacent_minus
%left expr_args
%right matching_bar
%left pat_app
%left pat_args
%left PREFIX_OP
%right dot_lambda
%left DOT QMARK
%left HIGH_PRECEDENCE_BRACK_APP
%left HIGH_PRECEDENCE_PAREN_APP
%left HIGH_PRECEDENCE_TYAPP

brianrourkeboll avatar Oct 01 '25 15:10 brianrourkeboll

Thanks! looks like I can adapt that listing and put it straight into the spec

roboz0r avatar Oct 01 '25 15:10 roboz0r

Looking a bit more into pars.fsy it seems there are many additional precedence rules introduced to resolve conflicts.

It is possible to eliminate conflicts by giving precedence to rules and tokens. ... Dummy terminals (like prec_type_prefix) can assign precedence to a rule.

Should these "dummy terminals" be added to the precedence table or are they an artifact of the way FsLexYacc processes things that is better described elsewhere in the spec document?

I also note the spec table lists precedence highest to lowest but pars.fsy defines precedence lowest to highest. Should the spec be reversed to also list precedence in lowest to highest order for easier cross-checking?

roboz0r avatar Oct 02 '25 14:10 roboz0r

I had a go at putting everything into the table, and applied inline code formatting to the operators. What you you think?

Operator or expression Associativity Comments
prec-interaction-empty Not associative TODO
f<types> Left High-precedence type application; see §
f(x) Left High-precedence paren application; see §
f[x] Left High-precedence bracket application; see §
. ? Left
_.M(x) Right
prefix-op Left Applies to prefix uses of these symbols
pat-args Left TODO
pat-app Left TODO
| rule Right Pattern matching rules
f x Left
arg-expr-adjacent-minus Left TODO
expr-app expr-assert expr-lazy lazy assert Left
head-expr-adjacent-minus Left TODO
?? Left
**OP Right
*OP /OP mod * %OP Left TODO mod?
-OP +OP expr-prefix-plus-minus Left Applies to infix uses of these symbols TODO
:? Left
pat-isinst Not associative TODO
:: Right
^OP @OP Right
!=OP <OP >OP = |OP &OP $ Left Applies to infix uses of these symbols
:> :?> Left
expr-not Not associative TODO
pat-conj Not associative TODO
& && Left
and! Left
and Left
or || in Left in when used in a join in a query expression
paren-pat-attribs Not associative TODO
paren-pat-colon Not associative TODO
interpolation-fill Not associative TODO ...{3,N4}... .NET style fill has higher precedence than e, e
.. Left for matrix.[1..2, 3..4] the .. has higher precedence than expression 2, 3
open-range-expr Not associative TODO
, Left
pat-tuple expr-tuple Not associative TODO
:= Right
<- Right
interface Not associative
prec-interfaces-prefix Not associative TODO lower than INTERFACE
open Not associative
prec-no-more-attr-bindings Not associative TODO lower than AND
prec-atomtyp-get-path Not associative TODO lower than LESS
prec-atomtyp-path Not associative TODO lower than LESS
else Not associative
if Not associative lower than ELSE to disambiguate if _ then if _ then _ else _
decl-match decl-do Not associative TODO
function fun match try do Not associative
decl-let Not associative
expr-let Not associative
let new Not associative LET, NEW higher than SEMICOLON
prec-opt-attributes-none Not associative TODO
ident [ Not associative TODO
-> Right
prec-toptuptyptail-prefix Not associative TODO lower than STAR, IDENT, RARROW etc.
prec-tuptyptail-prefix Not associative TODO lower than STAR, IDENT, RARROW etc.
prec-tuptyp-prefix Not associative TODO lower than STAR, IDENT, RARROW etc.
prec-typ-prefix Not associative TODO lower than STAR, IDENT, RARROW etc.
true false _ null Not associative
( { [| Not associative
Interpolated strings Not associative
Numeric Literals string Not associative The string keyword
prec-atompat-pathop Not associative TODO
prec-defn-sep Right TODO
; prec-semiexpr-sep OBLOCKSEP Right
| Left
| null Not associative TODO
prec-then-if Not associative TODO
prec-then-before Left a then b as an object constructor is very low precedence Lower than if a then b
prec-pat-pat-action Not associative TODO
when Right
) RPAREN_COMING_SOON RPAREN_IS_HERE Not associative TODO
prec-wheretyp-prefix Not associative TODO lower than WHEN and RPAREN
as Right
prec-atomexpr-lparen-error Not associative TODO lower than WHEN and RPAREN
prec-args-error Not associative TODO lower than WHEN and RPAREN

roboz0r avatar Oct 02 '25 14:10 roboz0r

Should these "dummy terminals" be added to the precedence table

I don't think so, although I guess it depends exactly what the goal of the updated spec is. If it's to be comprehensive enough to enable a wholesale reimplementation of the parser, then I guess many such details would need to be included. But if the main goal of updating this part of the spec is so that users of the language can more easily understand the grammar (and the precedence and associativity of custom operators, etc.), then I think keeping only the higher-level items (operators, keywords, separators) makes more sense.

brianrourkeboll avatar Oct 02 '25 16:10 brianrourkeboll

My instinct is to go in the direction of "enable a wholesale reimplementation", but depends how much interest and effort it's going to take

roboz0r avatar Oct 04 '25 02:10 roboz0r

My instinct is to go in the direction of "enable a wholesale reimplementation"

In principle, I agree. That's what a spec is for. It should specify the language completely and compilers can implement it.

But we must also be realistic. There is just a single F# compiler, and de facto it is in the lead and the spec has to follow.

Why do we still need a spec?

  • For advanced users of the language to find details that the user documentation can not provide.
  • To decide if an issue is "by design" or a bug.
  • To help new contributors understand the compiler.
  • To improve the quality of new features. By writing down precisely what the feature changes, you can avoid a lot of issues that otherwise go unnoticed.

For now, I see the following priorities for our work on the F# spec.

  1. Add the existing RFCs to the spec.
  2. Ensure that new RFCs are more explicit and correct about the necessary spec changes. And that all breaking changes have a RFC (we had quite a few in the last years that had not).
  3. Fix obvious mistakes in the spec (like the missing @ operator).

I believe we should avoid spreading ourselves still thinner by tackling other improvements of the spec at this point. And rather focus on the above items for now.

Martin521 avatar Oct 04 '25 07:10 Martin521