Skip to content

Commit 70a9165

Browse files
committed
adding implicit this for method contracts
1 parent d180931 commit 70a9165

File tree

6 files changed

+109
-53
lines changed

6 files changed

+109
-53
lines changed

Gruntfile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module.exports = function(grunt) {
2020
contracts: {
2121
options: {
2222
readableNames: true,
23-
modules: ["sparkler/macros", "es6-macros"]
23+
modules: ["sparkler/macros", "es6-macros", "./src/helper-macros.js"]
2424
},
2525
src: "src/contracts.js",
2626
dest: "build/contracts.js"

macros/disabled.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ let import = macro {
8282
throw new Error(msg);
8383
}
8484
function makeCoffer(name) {
85-
return new Contract(name, 'coffer', function (blame, unwrapTypeVar) {
85+
return new Contract(name, 'coffer', function (blame, unwrapTypeVar, projOptions) {
8686
return function (val) {
8787
var locationMsg = 'in the type variable ' + name + ' of';
8888
if (unwrapTypeVar) {
@@ -219,7 +219,7 @@ let import = macro {
219219
var rngStr = options && options.namesStr ? options.namesStr[options.namesStr.length - 1] + ': ' + rng : rng;
220220
var thisName = options && options.thisContract ? '\n | this: ' + options.thisContract : '';
221221
var contractName = domName + ' -> ' + rngStr + thisName + (options && options.dependencyStr ? ' | ' + options.dependencyStr : '');
222-
var c = new Contract(contractName, 'fun', function (blame, unwrapTypeVar) {
222+
var c = new Contract(contractName, 'fun', function (blame, unwrapTypeVar, projOptions) {
223223
return function (f) {
224224
blame = blame.addParents(contractName);
225225
if (typeof f !== 'function') {
@@ -244,8 +244,15 @@ let import = macro {
244244
}
245245
checkedArgs = checkedArgs.concat(args.slice(i));
246246
var checkedThis = thisVal;
247-
if (options && options.thisContract) {
248-
var thisProj = options.thisContract.proj(blame.swap().addLocation('the this value of'));
247+
if (options && options.thisContract || projOptions && projOptions.overrideThisContract) {
248+
var thisContract = function () {
249+
if (projOptions && projOptions.overrideThisContract) {
250+
return projOptions.overrideThisContract;
251+
} else {
252+
return options.thisContract;
253+
}
254+
}.bind(this)();
255+
var thisProj = thisContract.proj(blame.swap().addLocation('the this value of'));
249256
checkedThis = thisProj(thisVal);
250257
}
251258
assert(rng instanceof Contract, 'The range is not a contract');
@@ -375,8 +382,15 @@ let import = macro {
375382
if (!(objContract[key].type === 'optional' && obj[key] === undefined)) {
376383
// self contracts use the original object contract
377384
var c$2 = objContract[key];
385+
var propProjOptions = function () {
386+
if (objContract[key].type === 'fun') {
387+
return { overrideThisContract: this };
388+
} else {
389+
return {};
390+
}
391+
}.bind(this)();
378392
// var c = objContract[key].type === "self" ? this : objContract[key];
379-
var propProj = c$2.proj(blame.addLocation('the ' + key + ' property of'));
393+
var propProj = c$2.proj(blame.addLocation('the ' + key + ' property of'), false, propProjOptions);
380394
var checkedProperty = propProj(obj[key]);
381395
obj[key] = checkedProperty;
382396
}

macros/index.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ let import = macro {
8282
throw new Error(msg);
8383
}
8484
function makeCoffer(name) {
85-
return new Contract(name, 'coffer', function (blame, unwrapTypeVar) {
85+
return new Contract(name, 'coffer', function (blame, unwrapTypeVar, projOptions) {
8686
return function (val) {
8787
var locationMsg = 'in the type variable ' + name + ' of';
8888
if (unwrapTypeVar) {
@@ -219,7 +219,7 @@ let import = macro {
219219
var rngStr = options && options.namesStr ? options.namesStr[options.namesStr.length - 1] + ': ' + rng : rng;
220220
var thisName = options && options.thisContract ? '\n | this: ' + options.thisContract : '';
221221
var contractName = domName + ' -> ' + rngStr + thisName + (options && options.dependencyStr ? ' | ' + options.dependencyStr : '');
222-
var c = new Contract(contractName, 'fun', function (blame, unwrapTypeVar) {
222+
var c = new Contract(contractName, 'fun', function (blame, unwrapTypeVar, projOptions) {
223223
return function (f) {
224224
blame = blame.addParents(contractName);
225225
if (typeof f !== 'function') {
@@ -244,8 +244,15 @@ let import = macro {
244244
}
245245
checkedArgs = checkedArgs.concat(args.slice(i));
246246
var checkedThis = thisVal;
247-
if (options && options.thisContract) {
248-
var thisProj = options.thisContract.proj(blame.swap().addLocation('the this value of'));
247+
if (options && options.thisContract || projOptions && projOptions.overrideThisContract) {
248+
var thisContract = function () {
249+
if (projOptions && projOptions.overrideThisContract) {
250+
return projOptions.overrideThisContract;
251+
} else {
252+
return options.thisContract;
253+
}
254+
}.bind(this)();
255+
var thisProj = thisContract.proj(blame.swap().addLocation('the this value of'));
249256
checkedThis = thisProj(thisVal);
250257
}
251258
assert(rng instanceof Contract, 'The range is not a contract');
@@ -375,8 +382,15 @@ let import = macro {
375382
if (!(objContract[key].type === 'optional' && obj[key] === undefined)) {
376383
// self contracts use the original object contract
377384
var c$2 = objContract[key];
385+
var propProjOptions = function () {
386+
if (objContract[key].type === 'fun') {
387+
return { overrideThisContract: this };
388+
} else {
389+
return {};
390+
}
391+
}.bind(this)();
378392
// var c = objContract[key].type === "self" ? this : objContract[key];
379-
var propProj = c$2.proj(blame.addLocation('the ' + key + ' property of'));
393+
var propProj = c$2.proj(blame.addLocation('the ' + key + ' property of'), false, propProjOptions);
380394
var checkedProperty = propProj(obj[key]);
381395
obj[key] = checkedProperty;
382396
}

src/contracts.js

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,7 @@
112112
}
113113

114114
function makeCoffer(name) {
115-
return new Contract(name, "coffer", function(blame, otherthing, projOptions) {
116-
// if (projOptions && (projOptions.unwrapTypeVar !== unwrapTypeVar)) {
117-
// throw new Error("does not match");
118-
// }
119-
var unwrapTypeVar = if projOptions then projOptions.unwrapTypeVar else false
115+
return new Contract(name, "coffer", function(blame, unwrapTypeVar, projOptions) {
120116
return function(val) {
121117
var locationMsg = "in the type variable " + name + " of";
122118
if (unwrapTypeVar) {
@@ -300,11 +296,7 @@
300296
var contractName = domName + " -> " + rngStr + thisName +
301297
(options && options.dependencyStr ? " | " + options.dependencyStr : "");
302298

303-
var c = new Contract(contractName, "fun", function(blame, otherthing, projOptions) {
304-
// if (projOptions && (projOptions.unwrapTypeVar !== unwrapTypeVar)) {
305-
// throw new Error("does not match");
306-
// }
307-
var unwrapTypeVar = if projOptions then projOptions.unwrapTypeVar else false
299+
var c = new Contract(contractName, "fun", function(blame, unwrapTypeVar, projOptions) {
308300

309301
return function(f) {
310302
blame = blame.addParents(contractName);
@@ -325,7 +317,7 @@
325317
var location = "the " + addTh(i+1) + " argument of";
326318
var unwrapForProj = dom[i].type === "fun" ? !unwrapTypeVar : unwrapTypeVar;
327319
var domProj = dom[i].proj(blame.swap()
328-
.addLocation(location), unwrapForProj, {unwrapTypeVar: unwrapForProj});
320+
.addLocation(location), unwrapForProj);
329321

330322
checkedArgs.push(domProj(args[i]));
331323

@@ -339,17 +331,20 @@
339331
}
340332
checkedArgs = checkedArgs.concat(args.slice(i));
341333
var checkedThis = thisVal;
342-
if(options && options.thisContract) {
343-
var thisProj = options.thisContract.proj(blame.swap()
344-
.addLocation("the this value of"));
334+
if((options && options.thisContract) || (projOptions && projOptions.overrideThisContract)) {
335+
var thisContract = if projOptions && projOptions.overrideThisContract
336+
then projOptions.overrideThisContract
337+
else options.thisContract
338+
var thisProj = thisContract.proj(blame.swap()
339+
.addLocation("the this value of"));
345340
checkedThis = thisProj(thisVal);
346341
}
347342

348343
assert(rng instanceof Contract, "The range is not a contract");
349344

350345
var rawResult = target.apply(checkedThis, checkedArgs);
351346
var rngUnwrap = rng.type === "fun" ? unwrapTypeVar : !unwrapTypeVar;
352-
var rngProj = rng.proj(blame.addLocation("the return of"), rngUnwrap, {unwrapTypeVar: rngUnwrap});
347+
var rngProj = rng.proj(blame.addLocation("the return of"), rngUnwrap);
353348
var rngResult = rngProj(rawResult);
354349
if (options && options.dependency && typeof options.dependency === "function") {
355350
var depResult = options.dependency.apply(this, depArgs.concat(rngResult));
@@ -392,7 +387,7 @@
392387
var contractName = "opt " + contract;
393388
return new Contract(contractName, "optional", function(blame, unwrapTypeVar) {
394389
return function(val) {
395-
var proj = contract.proj(blame, unwrapTypeVar, {unwrapTypeVar: unwrapTypeVar});
390+
var proj = contract.proj(blame, unwrapTypeVar);
396391
return proj(val);
397392
};
398393
});
@@ -406,7 +401,7 @@
406401

407402
return new Contract(contractName, "repeat", function(blame, unwrapTypeVar) {
408403
return function (val) {
409-
var proj = contract.proj(blame, unwrapTypeVar, {unwrapTypeVar: unwrapTypeVar});
404+
var proj = contract.proj(blame, unwrapTypeVar);
410405
return proj(val);
411406
};
412407
});
@@ -440,8 +435,7 @@
440435
var fieldProj = arrContract[ctxIdx].proj(blame.addLocation("the " +
441436
addTh(arrIdx) +
442437
" field of"),
443-
unwrapForProj,
444-
{unwrapTypeVar: unwrapForProj});
438+
unwrapForProj);
445439
var checkedField = fieldProj(arr[arrIdx]);
446440
arr[arrIdx] = checkedField;
447441

@@ -454,8 +448,7 @@
454448
var repeatProj = arrContract[ctxIdx].proj(blame.addLocation("the " +
455449
addTh(arrIdx) +
456450
" field of"),
457-
unwrapForProj,
458-
{unwrapTypeVar: unwrapForProj});
451+
unwrapForProj);
459452
arr[arrIdx] = repeatProj(arr[arrIdx]);
460453
}
461454
}
@@ -511,10 +504,13 @@
511504
if (!(objContract[key].type === "optional" && obj[key] === undefined)) {
512505
// self contracts use the original object contract
513506
var c = objContract[key];
507+
var propProjOptions = if objContract[key].type === "fun"
508+
then {overrideThisContract: this}
509+
else {}
514510
// var c = objContract[key].type === "self" ? this : objContract[key];
515511
var propProj = c.proj(blame.addLocation("the " +
516512
key +
517-
" property of"));
513+
" property of"), false, propProjOptions);
518514
var checkedProperty = propProj(obj[key]);
519515
obj[key] = checkedProperty;
520516
}

src/helper-macros.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
let if = macro {
2+
rule { $cond:expr then $then:expr else $else:expr } => {
3+
(function() {
4+
if ($cond) {
5+
return $then
6+
} else {
7+
return $else
8+
}
9+
}.bind(this))();
10+
}
11+
// fall through
12+
rule { $rest ...} => { if $rest ...}
13+
}
14+
15+
export if;

test/test_contracts.js

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -712,25 +712,42 @@ blaming: (calling context for f)
712712
`
713713
})
714714

715-
// it("should allow you to use method contracts on objects", function() {
716-
// @ let List = Null or {
717-
// a: Num,
718-
// cons: List
719-
// }
720-
721-
// @ (Obj) -> Num
722-
// function foo(o) { return o.b.a; }
723-
724-
// var o = {
725-
// a: 42,
726-
// b: {
727-
// a: 100,
728-
// b: null
729-
// }
730-
// };
731-
// o.b.b = o;
732-
733-
// foo(o)
734-
// })
715+
it("should implicitly bind the this macro for method contracts", function() {
716+
@ let Obj = {
717+
a: Num,
718+
f: () -> Num
719+
}
720+
721+
@ (Obj) -> Num
722+
function foo(o) { return o.f(); }
723+
724+
725+
var obj = {
726+
a: 42,
727+
f: function() { return this.a; }
728+
};
729+
730+
(foo(obj)).should.equal(42);
731+
732+
@ (Obj) -> Num
733+
function badFoo(o) {
734+
var f = o.f;
735+
return f();
736+
}
737+
738+
blame of {
739+
badFoo(obj)
740+
} should be `badFoo: contract violation
741+
expected: an object with at least 2 keys
742+
given: undefined
743+
in: the this value of
744+
the f property of
745+
the 1st argument of
746+
({a: Num, f: () -> Num}) -> Num
747+
function badFoo guarded at line: 733
748+
blaming: function badFoo
749+
`
750+
751+
});
735752

736753
});

0 commit comments

Comments
 (0)