mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 09:40:21 +00:00
990 lines
23 KiB
ReasonML
990 lines
23 KiB
ReasonML
/* Copyright (c) 2015-present, Facebook, Inc. All rights reserved. */
|
|
[@@@autoFormat let wrap = 80; let shift = 2];
|
|
|
|
Modules.run ();
|
|
|
|
Polymorphism.run ();
|
|
|
|
Variants.run ();
|
|
|
|
BasicStructures.run ();
|
|
|
|
TestUtils.printSection "General Syntax";
|
|
|
|
/* Won't work! */
|
|
/* let matchingFunc a = match a with */
|
|
/* `Thingy x => (print_string "matched thingy x"); x */
|
|
/* | `Other x => (print_string "matched other x"); x;; */
|
|
/* */
|
|
let matchingFunc a =>
|
|
switch a {
|
|
| `Thingy x =>
|
|
print_string "matched thingy x";
|
|
let zz = 10;
|
|
zz
|
|
| `Other x =>
|
|
print_string "matched other x";
|
|
x
|
|
};
|
|
|
|
type firstTwoShouldBeGroupedInParens =
|
|
(int => int) => int => int;
|
|
|
|
type allParensCanBeRemoved =
|
|
int => int => int => int;
|
|
|
|
type firstTwoShouldBeGroupedAndFirstThree =
|
|
((int => int) => int) => int;
|
|
|
|
/* Same thing now but with type constructors instead of each int */
|
|
type firstTwoShouldBeGroupedInParens =
|
|
(list int => list int) => list int => list int;
|
|
|
|
type allParensCanBeRemoved =
|
|
list int => list int => list int => list int;
|
|
|
|
type firstTwoShouldBeGroupedAndFirstThree =
|
|
((list int => list int) => list int) =>
|
|
list int;
|
|
|
|
type myRecordType = {
|
|
firstTwoShouldBeGroupedInParens:
|
|
(int => int) => int => int,
|
|
allParensCanBeRemoved:
|
|
int => int => int => int,
|
|
firstTwoShouldBeGroupedAndFirstThree:
|
|
((int => int) => int) => int
|
|
};
|
|
|
|
type firstNamedArgShouldBeGroupedInParens =
|
|
first::(int => int) => second::int => int;
|
|
|
|
type allParensCanBeRemoved =
|
|
first::int => second::int => third::int => int;
|
|
|
|
type firstTwoShouldBeGroupedAndFirstThree =
|
|
first::((int => int) => int) => int;
|
|
|
|
/* Same thing now, but with type constructors instead of int */
|
|
type firstNamedArgShouldBeGroupedInParens =
|
|
first::(list int => list int) =>
|
|
second::list int =>
|
|
list int;
|
|
|
|
type allParensCanBeRemoved =
|
|
first::list int =>
|
|
second::list int =>
|
|
third::list int =>
|
|
list int;
|
|
|
|
type firstTwoShouldBeGroupedAndFirstThree =
|
|
first::((list int => list int) => list int) =>
|
|
list int;
|
|
|
|
type firstNamedArgShouldBeGroupedInParens =
|
|
first::(int => int)? =>
|
|
second::int list? =>
|
|
int;
|
|
|
|
/* The arrow necessitates parens around the next two args. The ? isn't what
|
|
* makes the parens necessary. */
|
|
type firstNamedArgShouldBeGroupedInParensAndSecondNamedArg =
|
|
first::(int => int)? =>
|
|
second::(int => int)? =>
|
|
int;
|
|
|
|
type allParensCanBeRemoved =
|
|
first::int? =>
|
|
second::int? =>
|
|
third::int? =>
|
|
int;
|
|
|
|
type firstTwoShouldBeGroupedAndFirstThree =
|
|
first::((int => int) => int) => int;
|
|
|
|
type noParens =
|
|
one::int => int => int => two::int => int;
|
|
|
|
type noParensNeeded =
|
|
one::int => int => int => two::int => int;
|
|
|
|
type firstNamedArgNeedsParens =
|
|
one::(int => int => int) => two::int => int;
|
|
|
|
/* Now, let's try type aliasing */
|
|
/* Unless wrapped in parens, types between arrows may not be aliased, may not
|
|
* themselves be arrows. */
|
|
type parensRequiredAroundFirstArg =
|
|
(list int as 'a) => int as 'a;
|
|
|
|
type parensRequiredAroundReturnType =
|
|
(list int as 'a) => (int as 'a);
|
|
|
|
type parensRequiredAroundReturnType =
|
|
(list int as 'a) => (int as 'a) as 'b;
|
|
|
|
type noParensNeededWhenInTuple =
|
|
(list int as 'a, list int as 'b) as 'entireThing;
|
|
|
|
type myTypeDef 'a = list 'a;
|
|
|
|
type instatiatedTypeDef = myTypeDef int => int;
|
|
|
|
/* Test a type attribute for good measure */
|
|
/* We should clean up all of the attribute tagging eventually, but for now,
|
|
* let's make it super ugly to get out of the way of all the formatting/parsing
|
|
* implementations (fewer conflicts during parsing, fewer edge cases during
|
|
* printing).
|
|
*/
|
|
type something = (
|
|
int,
|
|
int [@lookAtThisAttribute]
|
|
);
|
|
|
|
type longWrappingTypeDefinitionExample =
|
|
M_RK__G.Types.instance
|
|
(TGRecognizer.tGFields unit unit)
|
|
(TGRecognizer.tGMethods unit unit);
|
|
|
|
type semiLongWrappingTypeDefinitionExample =
|
|
M_RK__Gesture.Types.instance
|
|
TGRecognizerFinal.tGFields
|
|
TGRecognizerFinal.tGMethods;
|
|
|
|
type semiLongWrappingTypeWithConstraint =
|
|
M_RK__Gesture.Types.instance
|
|
'a
|
|
TGRecognizerFinal.tGFields
|
|
TGRecognizerFinal.tGMethods
|
|
constraint 'a = (unit, unit);
|
|
|
|
type onelineConstrain = 'a constraint 'a = int;
|
|
|
|
/* This must be in trunk but not in this branch of OCaml */
|
|
/* type withNestedRecords = MyConstructor {myField: int} */
|
|
type colors =
|
|
| Red int
|
|
| Black int
|
|
| Green int;
|
|
|
|
/* Another approach is to require declared variants to wrap any record */
|
|
/* type myRecord = MyRecord {name: int}; */
|
|
/* let myValue = MyRecord {name: int}; */
|
|
/* This would force importing of the module */
|
|
/* This would also lend itself naturally to pattern matching - and avoid having
|
|
to use `.` operator at all since you normally destructure. */
|
|
type nameBlahType = {nameBlah: int};
|
|
|
|
let myRecord = {nameBlah: 20};
|
|
|
|
let myRecordName = myRecord.nameBlah;
|
|
|
|
let {nameBlah}: nameBlahType = {nameBlah: 20};
|
|
|
|
print_int nameBlah;
|
|
|
|
let {nameBlah: aliasedToThisVar}: nameBlahType = {
|
|
nameBlah: 20
|
|
};
|
|
|
|
print_int aliasedToThisVar;
|
|
|
|
let desiredFormattingForWrappedLambda:
|
|
int => int => int => nameBlahType =
|
|
/*
|
|
|
|
fun is
|
|
pre- /firstarg\
|
|
fix /-coupled--\
|
|
|-\ /-to-prefix--\ */
|
|
fun curriedArg anotherArg lastArg => {
|
|
nameBlah: 10
|
|
};
|
|
|
|
type longerInt = int;
|
|
|
|
let desiredFormattingForWrappedLambdaWrappedArrow:
|
|
longerInt =>
|
|
longerInt =>
|
|
longerInt =>
|
|
nameBlahType =
|
|
/*
|
|
|
|
fun is
|
|
pre- /firstarg\
|
|
fix /-coupled--\
|
|
|-\ /-to-prefix--\ */
|
|
fun curriedArg anotherArg lastArg => {
|
|
nameBlah: 10
|
|
};
|
|
|
|
let desiredFormattingForWrappedLambdaReturnOnNewLine
|
|
/*
|
|
|
|
fun is
|
|
pre- /firstarg\
|
|
fix /-coupled--\
|
|
|-\ /-to-prefix--\ */
|
|
curriedArg
|
|
anotherArg
|
|
lastArg => {
|
|
nameBlah: 10
|
|
};
|
|
|
|
/*
|
|
let is
|
|
pre-
|
|
fix /-function binding name---\
|
|
|-\ / is coupled to prefix \ */
|
|
let desiredFormattingForWrappedSugar
|
|
curriedArg
|
|
anotherArg
|
|
lastArg => {
|
|
nameBlah: 10
|
|
};
|
|
|
|
/*
|
|
let is
|
|
pre-
|
|
fix /-function binding name---\
|
|
|-\ / is coupled to prefix \ */
|
|
let desiredFormattingForWrappedSugarReturnOnNewLine
|
|
curriedArg
|
|
anotherArg
|
|
lastArg => {
|
|
nameBlah: 10
|
|
};
|
|
|
|
/*
|
|
let : type t1 t2. t1 * t2 list -> t1 = ...
|
|
let rec f : 't1 't2. 't1 * 't2 list -> 't1 =
|
|
fun (type t1) (type t2) -> (... : t1 * t2 list -> t1)
|
|
*/
|
|
type point = {x: int, y: int};
|
|
|
|
type point3D = {x: int, y: int, z: int};
|
|
|
|
let point2D = {x: 20, y: 30};
|
|
|
|
let point3D: point3D = {
|
|
x: 10,
|
|
y: 11,
|
|
z: 80 /* Optional Comma */
|
|
};
|
|
|
|
let printPoint (p: point) => {
|
|
print_int p.x;
|
|
print_int p.y
|
|
};
|
|
|
|
let addPoints (p1: point, p2: point) => {
|
|
x: p1.x + p2.x,
|
|
y: p1.y + p2.y
|
|
};
|
|
|
|
let res1 = printPoint point2D;
|
|
|
|
let res2 =
|
|
printPoint {x: point3D.x, y: point3D.y};
|
|
|
|
/*
|
|
When () were used to indicate sequences, the parser used seq_expr not only
|
|
for grouping sequences, but also to form standard precedences.
|
|
/------- sequence_expr ------\
|
|
let res3 = printPoint (addPoints (point2D, point3D));
|
|
|
|
Interestingly, it knew that tuples aren't sequences.
|
|
|
|
To move towards semi delimited, semi-terminated, braces-grouped sequences:
|
|
while allowing any non-sequence expression to be grouped on parens, we make
|
|
an explicit rule that allows one single non-semi ended expression to be
|
|
grouped in parens.
|
|
|
|
Actually: We will allow an arbitrary number of semi-delimited expressions to
|
|
be wrapped in parens, but the braces grouped semi delimited (sequence)
|
|
expressions must *also* be terminated with a semicolon.
|
|
|
|
This allows the parser to distinguish between
|
|
|
|
let x = {a}; /* Record {a:a} */
|
|
let x = {a;}; /* Single item sequence returning identifier {a} */
|
|
*/
|
|
let res3 =
|
|
printPoint (
|
|
addPoints (
|
|
point2D,
|
|
{x: point3D.x, y: point3D.y}
|
|
)
|
|
);
|
|
|
|
type person = {age: int, name: string};
|
|
|
|
type hiredPerson = {
|
|
age: string,
|
|
name: string,
|
|
dateHired: int
|
|
};
|
|
|
|
let o: person = {name: "bob", age: 10};
|
|
|
|
/* Parens needed? Nope! */
|
|
let o: person = {name: "bob", age: 10};
|
|
|
|
let printPerson (p: person) => {
|
|
let q: person = p;
|
|
p.name ^ p.name
|
|
};
|
|
|
|
/* let dontParseMeBro x y:int = x = y;*/
|
|
/* With this unification, anywhere eyou see `= fun` you can just ommit it */
|
|
let blah a => a; /* Done */
|
|
|
|
let blah a => a; /* Done (almost) */
|
|
|
|
let blah a b => a; /* Done */
|
|
|
|
let blah a b => a; /* Done (almost) */
|
|
|
|
/* More than one consecutive pattern must have a single case */
|
|
type blah = {blahBlah: int};
|
|
|
|
let blah a {blahBlah} => a;
|
|
|
|
let blah a {blahBlah} => a;
|
|
|
|
let module TryToExportTwice = {
|
|
let myVal = "hello";
|
|
};
|
|
|
|
/*
|
|
Unifying top level module syntax with local module syntax is probably a bad
|
|
idea at the moment because it makes it more difficult to continue to support
|
|
`let .. in` bindings. We can distinguish local modules for `let..in` that
|
|
just happen to be defined at the top level (but not exported).
|
|
|
|
let MyModule = {let myVal = 20;} in
|
|
MyModule.x
|
|
|
|
Wait, where would this ever be valid, even if we continued to support
|
|
`let..in`?
|
|
*/
|
|
let onlyDoingThisTopLevelLetToBypassTopLevelSequence = {
|
|
let x = {
|
|
print_int 1;
|
|
print_int 20 /* Missing trailing SEMI */
|
|
};
|
|
let x = {
|
|
print_int 1;
|
|
print_int 20; /* Ensure missing middle SEMI reported well */
|
|
print_int 20
|
|
};
|
|
let x = {
|
|
print_int 1;
|
|
print_int 20;
|
|
10
|
|
/* Comment in final position */
|
|
}; /* Missing final SEMI */
|
|
x + x
|
|
};
|
|
|
|
type hasA = {a: int};
|
|
|
|
let a = 10;
|
|
|
|
let returnsASequenceExpressionWithASingleIdentifier
|
|
() => a;
|
|
|
|
let thisReturnsA () => a;
|
|
|
|
let thisReturnsAAsWell () => a;
|
|
|
|
let recordVal: int = (thisReturnsARecord ()).a;
|
|
|
|
Printf.printf
|
|
"\nproof that thisReturnsARecord: %n\n"
|
|
recordVal;
|
|
|
|
Printf.printf
|
|
"\nproof that thisReturnsA: %n\n"
|
|
(thisReturnsA ());
|
|
|
|
/* Pattern matching */
|
|
let blah arg =>
|
|
switch arg {
|
|
/* Comment before Bar */
|
|
| /* Comment between bar/pattern */ Red _ => 1
|
|
/* Comment Before non-first bar */
|
|
| /* Comment betwen bar/pattern */ Black _ => 0
|
|
| Green _ => 0
|
|
};
|
|
|
|
/* Any function that pattern matches a multicase match is interpretted as a
|
|
* single arg that is then matched on. Instead of the above `blah` example:*/
|
|
let blah =
|
|
fun
|
|
| Red _ => 1
|
|
| Black _ => 0
|
|
| Green _ => 1;
|
|
|
|
/* `fun a => a` is read as "a function that maps a to a". Then the */
|
|
/* above example is read: "a function that 'either maps' Red to.. or maps .." */
|
|
/* Thc00f564e first bar is read as "either maps" */
|
|
/* Curried form is not supported:
|
|
let blah x | Red _ => 1 | Black _ => 0;
|
|
Theres no sugar rule for dropping => fun, only = fun
|
|
*/
|
|
/* let blahCurriedX x => fun /* See, nothing says we can drop the => fun */ */
|
|
/* |(Red x | Black x | Green x) => 1 /* With some effort, we can ammend the sugar rule that would */ */
|
|
/* | Black x => 0 /* Allow us to drop any => fun.. Just need to make pattern matching */ */
|
|
/* | Green x => 0; /* Support that */ */
|
|
/* */
|
|
let blahCurriedX x =>
|
|
fun
|
|
| Red x
|
|
| Black x
|
|
| Green x =>
|
|
1 /* With some effort, we can ammend the sugar rule that would */
|
|
| Black x => 0 /* Allow us to drop any => fun.. Just need to make pattern matching */
|
|
| Green x => 0; /* Support that */
|
|
|
|
let sameThingInLocal = {
|
|
let blahCurriedX x =>
|
|
fun
|
|
| Red x
|
|
| Black x
|
|
| Green x =>
|
|
1 /* With some effort, we can ammend the sugar rule that would */
|
|
| Black x => 0 /* Allow us to drop any => fun.. Just need to make pattern matching */
|
|
| Green x => 0; /* Support that */
|
|
blahCurriedX
|
|
};
|
|
|
|
/* This should be parsed/printed exactly as the previous */
|
|
let blahCurriedX x =>
|
|
fun
|
|
| Red x
|
|
| Black x
|
|
| Green x => 1
|
|
| Black x => 0
|
|
| Green x => 0;
|
|
|
|
/* Any time there are multiple match cases we require a leading BAR */
|
|
let v = Red 10;
|
|
|
|
let Black x | Red x | Green x = v; /* So this NON-function still parses */
|
|
|
|
/* This doesn't parse, however (and it doesn't in OCaml either):
|
|
let | Black x | Red x | Green x = v;
|
|
*/
|
|
print_int x;
|
|
|
|
/* Scoping: Let sequences. Familiar syntax for lexical ML style scope and
|
|
sequences. */
|
|
let res = {
|
|
let a = "a starts out as";
|
|
{
|
|
print_string a;
|
|
let a = 20;
|
|
print_int a
|
|
};
|
|
print_string a
|
|
};
|
|
|
|
let res = {
|
|
let a = "first its a string";
|
|
let a = 20;
|
|
print_int a;
|
|
print_int a;
|
|
print_int a
|
|
};
|
|
|
|
let res = {
|
|
let a = "a is always a string";
|
|
print_string a;
|
|
let b = 30;
|
|
print_int b
|
|
};
|
|
|
|
/* let result = LyList.map (fun | [] => true | _ => false) []; */
|
|
/* OTHERWISE: You cannot tell if a is the first match case falling through or
|
|
* a curried first arg */
|
|
/* let blah = fun a | patt => 0 | anotherPatt => 1; */
|
|
/* let blah a patt => 0 | anotherPatt => 1; */
|
|
/*simple pattern EQUALGREATER expr */
|
|
let blah a {blahBlah} => a;
|
|
|
|
/* match_case */
|
|
/* pattern EQUALGREATER expr */
|
|
let blah =
|
|
fun
|
|
| Red _ => 1
|
|
| Black _ => 0
|
|
| Green _ => 0;
|
|
|
|
/* Won't work! */
|
|
/* let arrowFunc = fun a b => print_string "returning aplusb from arrow"; a + b;; */
|
|
let arrowFunc a b => {
|
|
print_string "returning aplusb from arrow";
|
|
a + b
|
|
};
|
|
|
|
let add a b => {
|
|
let extra = {
|
|
print_string "adding";
|
|
0
|
|
};
|
|
let anotherExtra = 0;
|
|
extra + a + b + anotherExtra
|
|
};
|
|
|
|
print_string (string_of_int (add 4 34));
|
|
|
|
let dummy _ => 10;
|
|
|
|
dummy res1;
|
|
|
|
dummy res2;
|
|
|
|
dummy res3;
|
|
|
|
/* Some edge cases */
|
|
let myFun firstArg (Red x | Black x | Green x) =>
|
|
firstArg + x;
|
|
|
|
let matchesWithWhen a =>
|
|
switch a {
|
|
| Red x when 1 > 0 => 10
|
|
| Red _ => 10
|
|
| Black x => 10
|
|
| Green x => 10
|
|
};
|
|
|
|
let matchesWithWhen =
|
|
fun
|
|
| Red x when 1 > 0 => 10
|
|
| Red _ => 10
|
|
| Black x => 10
|
|
| Green x => 10;
|
|
|
|
let matchesOne (`Red x) => 10;
|
|
|
|
/*
|
|
Typical OCaml would make you *wrap the functions in parens*! This is because it
|
|
can't tell if a semicolon is a sequence operator. Even if we had records use
|
|
commas to separate fields,
|
|
*/
|
|
type adders = {
|
|
addTwoNumbers: int => int => int,
|
|
addThreeNumbers: int => int => int => int,
|
|
addThreeNumbersTupled: (int, int, int) => int
|
|
};
|
|
|
|
let myRecordWithFunctions = {
|
|
addTwoNumbers: fun a b => a + b,
|
|
addThreeNumbers: fun a b c => a + b + c,
|
|
addThreeNumbersTupled: fun (a, b, c) =>
|
|
a + b + c
|
|
};
|
|
|
|
let result =
|
|
myRecordWithFunctions.addThreeNumbers 10 20 30;
|
|
|
|
let result =
|
|
myRecordWithFunctions.addThreeNumbersTupled (
|
|
10,
|
|
20,
|
|
30
|
|
);
|
|
|
|
let lookTuplesRequireParens = (1, 2);
|
|
|
|
/* let thisDoesntParse = 1, 2; */
|
|
let tupleInsideAParenSequence = {
|
|
print_string "look, a tuple inside a sequence";
|
|
let x = 10;
|
|
(x, x)
|
|
};
|
|
|
|
let tupleInsideALetSequence = {
|
|
print_string "look, a tuple inside a sequence";
|
|
let x = 10;
|
|
(x, x)
|
|
};
|
|
|
|
/* We *require* that function return types be wrapped in
|
|
parenthesis. In this example, there's no ambiguity */
|
|
let makeIncrementer (delta: int) :(int => int) =>
|
|
fun a => a + delta;
|
|
|
|
/* We could even force that consistency with let bindings - it's allowed
|
|
currently but not forced.
|
|
*/
|
|
let myAnnotatedValBinding: int = 10;
|
|
|
|
/* Class functions (constructors) and methods are unified in the same way */
|
|
class classWithNoArg = {
|
|
method x = 0;
|
|
method y = 0;
|
|
};
|
|
|
|
/* This parses but doesn't type check
|
|
class myClass init => object
|
|
method x => init
|
|
method y => init
|
|
end;
|
|
*/
|
|
let myFunc (a: int) (b: int) :(int, int) => (
|
|
a,
|
|
b
|
|
);
|
|
|
|
let myFunc (a: int) (b: int) :list int => [1];
|
|
|
|
let myFunc (a: int) (b: int) :point => {
|
|
x: a,
|
|
y: b
|
|
};
|
|
|
|
let myFunc (a: int, b: int) :point => {
|
|
x: a,
|
|
y: b
|
|
};
|
|
|
|
type myThing = (int, int);
|
|
|
|
type stillARecord = {name: string, age: int};
|
|
|
|
/* Rebase latest OCaml to get the following: And fixup
|
|
`generalized_constructor_arguments` according to master. */
|
|
/* type ('a, 'b) myOtherThing = Leaf {first:'a, second: 'b} | Null; */
|
|
type branch 'a 'b = {first: 'a, second: 'b};
|
|
|
|
type myOtherThing 'a 'b =
|
|
| Leaf (branch 'a 'b)
|
|
| Null;
|
|
|
|
type yourThing = myOtherThing int int;
|
|
|
|
/* Conveniently - this parses exactly how you would intend! No *need* to wrap
|
|
in an extra [], but it doesn't hurt */
|
|
/* FIXME type lookAtThesePolyVariants = list [`Red] ; */
|
|
/* FIXME type bracketsGroupMultipleParamsAndPrecedence = list (list (list [`Red])); */
|
|
/* FIXME type youCanWrapExtraIfYouWant = (list [`Red]); */
|
|
/* FIXME type hereAreMultiplePolyVariants = list [`Red | `Black]; */
|
|
/* FIXME type hereAreMultiplePolyVariantsWithOptionalWrapping = list ([`Red | `Black]); */
|
|
/*
|
|
/* Proposal: ES6 style lambdas: */
|
|
|
|
/* Currying */
|
|
let lookES6Style = (`Red x) (`Black y) => { };
|
|
let lookES6Style (`Red x) (`Black y) => { };
|
|
|
|
/* Matching the single argument */
|
|
let lookES6Style = oneArg => match oneArg with
|
|
| `Red x => x
|
|
| `Black x => x;
|
|
|
|
/* The "trick" to currying that we already have is basically the same - we just
|
|
* have to reword it a bit:
|
|
* From:
|
|
* "Any time you see [let x = fun ...] just replace it with [let x ...]"
|
|
* To:
|
|
* "Any time you see [let x = ... => ] just replace it with [let x ... => ]"
|
|
*/
|
|
let lookES6Style oneArg => match oneArg with
|
|
| `Red x => x
|
|
| `Black x => x;
|
|
|
|
*/
|
|
|
|
/** Current OCaml Named Arguments. Any aliasing is more than just aliasing!
|
|
OCaml allows full on pattern matching of named args. */
|
|
/*
|
|
A: let named ~a ~b = aa + bb in
|
|
B: let namedAlias ~a:aa ~b:bb = aa + bb in
|
|
C: let namedAnnot ~(a:int) ~(b:int) = a + b in
|
|
D: let namedAliasAnnot ~a:(aa:int) ~b:(bb:int) = aa + bb in
|
|
E: let optional ?a ?b = 10 in
|
|
F: let optionalAlias ?a:aa ?b:bb = 10 in
|
|
G: let optionalAnnot ?(a:int option) ?(b:int option) = 10 in
|
|
H: let optionalAliasAnnot ?a:(aa:int option) ?b:(bb:int option) = 10 in
|
|
/*
|
|
Look! When a default is provided, annotation causes inferred type of argument
|
|
to not be "option" since it's automatically destructured (because we know it
|
|
will always be available one way or another.)
|
|
*/
|
|
I: let defOptional ?(a=10) ?(b=10) = 10 in
|
|
J: let defOptionalAlias ?a:(aa=10) ?b:(bb=10) = 10 in
|
|
K: let defOptionalAnnot ?(a:int=10) ?(b:int=10) = 10 in
|
|
\ \
|
|
\label_let_pattern opt_default: no longer needed in SugarML
|
|
|
|
L: let defOptionalAliasAnnot ?a:(aa:int=10) ?b:(bb:int=10) = 10 in
|
|
\ \
|
|
\let_pattern: still a useful syntactic building block in SugarML
|
|
*/
|
|
|
|
/**
|
|
* In Reason, the syntax for named args uses double semicolon, since
|
|
* the syntax for lists uses ES6 style [], freeing up the ::.
|
|
*/
|
|
let a = 10;
|
|
|
|
let b = 20;
|
|
|
|
/*A*/
|
|
let named a::a b::b => a + b;
|
|
|
|
type named = a::int => b::int => int;
|
|
|
|
/*B*/
|
|
let namedAlias a::aa b::bb => aa + bb;
|
|
|
|
let namedAlias a::aa b::bb => aa + bb;
|
|
|
|
type namedAlias = a::int => b::int => int;
|
|
|
|
/*C*/
|
|
let namedAnnot a::(a: int) b::(b: int) => 20;
|
|
|
|
/*D*/
|
|
let namedAliasAnnot a::(aa: int) b::(bb: int) => 20;
|
|
|
|
/*E*/
|
|
let myOptional a::a=? b::b=? () => 10;
|
|
|
|
type named = a::int? => b::int? => unit => int;
|
|
|
|
/*F*/
|
|
let optionalAlias a::aa=? b::bb=? () => 10;
|
|
|
|
/*G*/
|
|
let optionalAnnot a::(a: int)=? b::(b: int)=? () => 10;
|
|
|
|
/*H*/
|
|
let optionalAliasAnnot
|
|
a::(aa: int)=?
|
|
b::(bb: int)=?
|
|
() => 10;
|
|
|
|
/*I: */
|
|
let defOptional a::a=10 b::b=10 () => 10;
|
|
|
|
type named = a::int? => b::int? => unit => int;
|
|
|
|
/*J*/
|
|
let defOptionalAlias a::aa=10 b::bb=10 () => 10;
|
|
|
|
/*K*/
|
|
let defOptionalAnnot
|
|
a::(a: int)=10
|
|
b::(b: int)=10
|
|
() => 10;
|
|
|
|
/*L*/
|
|
let defOptionalAliasAnnot
|
|
a::(aa: int)=10
|
|
b::(bb: int)=10
|
|
() => 10;
|
|
|
|
/*M: Invoking them - Punned */
|
|
let resNotAnnotated = named a::a b::b;
|
|
|
|
/*N:*/
|
|
let resAnnotated: int = named a::a b::b;
|
|
|
|
/*O: Invoking them */
|
|
let resNotAnnotated = named a::a b::b;
|
|
|
|
/*P: Invoking them */
|
|
let resAnnotated: int = named a::a b::b;
|
|
|
|
/*Q: Here's why "punning" doesn't work! */
|
|
/* Is b:: punned with a final non-named arg, or is b:: supplied b as one named arg? */
|
|
let b = 20;
|
|
|
|
let resAnnotated = named a::a b::b;
|
|
|
|
/*R: Proof that there are no ambiguities with return values being annotated */
|
|
let resAnnotated: ty = named a::a b;
|
|
|
|
/*S: Explicitly passed optionals are a nice way to say "use the default value"*/
|
|
let explictlyPassed =
|
|
myOptional a::?None b::?None;
|
|
|
|
/*T: Annotating the return value of the entire function call */
|
|
let explictlyPassedAnnotated: int =
|
|
myOptional a::?None b::?None;
|
|
|
|
/*U: Explicitly passing optional with identifier expression */
|
|
let a = None;
|
|
|
|
let explictlyPassed = myOptional a::?a b::?None;
|
|
|
|
let explictlyPassedAnnotated: int =
|
|
myOptional a::?a b::?None;
|
|
|
|
let nestedLet = {
|
|
let _ = 1;
|
|
()
|
|
};
|
|
|
|
let nestedLet = {
|
|
let _ = 1;
|
|
()
|
|
};
|
|
|
|
let nestedLet = {
|
|
let _ = 1;
|
|
()
|
|
};
|
|
|
|
let nestedLet = {
|
|
let _ = 1;
|
|
2
|
|
};
|
|
|
|
/*
|
|
* Showing many combinations of type annotations and named arguments.
|
|
*/
|
|
type typeWithNestedNamedArgs =
|
|
outerOne::(
|
|
innerOne::int => innerTwo::int => int
|
|
) =>
|
|
outerTwo::int =>
|
|
int;
|
|
|
|
type typeWithNestedOptionalNamedArgs =
|
|
outerOne::
|
|
(innerOne::int => innerTwo::int => int)? =>
|
|
outerTwo::int? =>
|
|
int;
|
|
|
|
type typeWithNestedOptionalNamedArgs =
|
|
outerOne::list string? => outerTwo::int? => int;
|
|
|
|
let x =
|
|
callSomeFunction
|
|
withArg::10 andOtherArg::wrappedArg;
|
|
|
|
let res = {
|
|
(constraintedSequenceItem: string);
|
|
(dontKnowWheYoudWantToActuallyDoThis: string)
|
|
};
|
|
|
|
let res = {
|
|
(
|
|
butTheyWillBePrintedWithAppropriateSpacing: string
|
|
);
|
|
(soAsToInstillBestDevelopmentPractices: string)
|
|
};
|
|
|
|
let x = [
|
|
(eachItemInListCanBeAnnotated: int),
|
|
(typeConstraints: float),
|
|
(
|
|
tupleConstraints: int,
|
|
andNotFunctionInvocations: int
|
|
)
|
|
];
|
|
|
|
let x = [
|
|
(butWeWillPrint: int),
|
|
(themAsSpaceSeparated: float),
|
|
(toInfluenceYour: int, developmentHabbits: int)
|
|
];
|
|
|
|
let newRecord = {
|
|
...(annotatedSpreadRecord: someRec),
|
|
x: y
|
|
};
|
|
|
|
let newRecord = {
|
|
...(annotatedSpreadRecord: someRec),
|
|
blah: 0,
|
|
foo: 1
|
|
};
|
|
|
|
let newRecord = {
|
|
...(
|
|
youCanEvenCallMethodsHereAndAnnotate them: someRec
|
|
),
|
|
blah: 0,
|
|
foo: 1
|
|
};
|
|
|
|
let newRecord = {
|
|
...(
|
|
youCanEvenCallMethodsHereAndAnnotate
|
|
them named::10: someRec
|
|
),
|
|
blah: 0,
|
|
foo: 1
|
|
};
|
|
|
|
let something: thing blah = aTypeAnnotation;
|
|
|
|
let something: thing blah = thisIsANamedArg;
|
|
|
|
let something: thing blah = aTypeAnnotation;
|
|
|
|
let something: blah = thisIsANamedArg thing;
|
|
|
|
let something: blah = typeAnnotation thing;
|
|
|
|
let newRecord = {
|
|
...(
|
|
heresAFunctionWithNamedArgs argOne::i: annotatedResult
|
|
),
|
|
soAsToInstill: 0,
|
|
developmentHabbits: 1
|
|
};
|
|
|
|
[@@@thisIsAThing];
|
|
|
|
let x = 10;
|
|
|
|
/* Ensure that the parenthesis are preserved here because they are
|
|
* important:
|
|
*/
|
|
let something =
|
|
fun
|
|
| None => (
|
|
fun
|
|
| [] => "emptyList"
|
|
| [_, ..._] => "nonEmptyList"
|
|
)
|
|
| Some _ => (
|
|
fun
|
|
| [] => "emptyList"
|
|
| [_, ..._] => "nonEmptyList"
|
|
);
|
|
|
|
/* A | B = X; */
|
|
let A | B = X;
|
|
|
|
/* A | (B | C) = X; */
|
|
let A | (B | C) = X;
|
|
|
|
/* (A | B) | (C | D) = X; */
|
|
let A | B | (C | D) = X;
|
|
|
|
/* A | B | (C | D) = X; */
|
|
let A | B | (C | D) = X;
|
|
|
|
/* (A | B) | C = X; */
|
|
let A | B | C = X;
|
|
|
|
/* A | B | C = X; */
|
|
let A | B | C = X;
|
|
|
|
|
|
/** External function declaration
|
|
*
|
|
*/
|
|
external f : int => int = "foo";
|
|
|
|
let x = {contents: 0};
|
|
|
|
let unitVal = x.contents = 210;
|