Allow type hints on functions and vars

This commit is contained in:
2020-11-18 15:52:30 -07:00
parent ff389ecf25
commit bb97388247
4 changed files with 59 additions and 7 deletions

View File

@@ -4,6 +4,7 @@ import haxe.macro.Expr;
import haxe.macro.Context; import haxe.macro.Context;
import kiss.Reader; import kiss.Reader;
import kiss.Types; import kiss.Types;
import kiss.Helpers;
using StringTools; using StringTools;
@@ -34,10 +35,10 @@ class FieldForms {
static function fieldName(formName:String, position:String, nameExp:ReaderExp) { static function fieldName(formName:String, position:String, nameExp:ReaderExp) {
return switch (nameExp) { return switch (nameExp) {
case Symbol(name): case Symbol(name) | TypedExp(_, Symbol(name)):
name; name;
default: default:
throw 'The first argument to $formName at $position should be a variable name'; throw 'The first argument to $formName at $position should be a variable name or typed variable name';
}; };
} }
@@ -52,8 +53,11 @@ class FieldForms {
return { return {
name: name, name: name,
access: access, access: access,
kind: FVar(null, // TODO allow type anotations kind: FVar(switch (args[0]) {
convert(args[1])), case TypedExp(type, _):
Helpers.parseTypePath(type);
default: null;
}, convert(args[1])),
pos: Context.currentPos() pos: Context.currentPos()
}; };
} }
@@ -70,10 +74,12 @@ class FieldForms {
return { return {
name: name, name: name,
access: access, access: access,
// TODO type parameter declarations
kind: FFun({ kind: FFun({
args: switch (args[1]) { args: switch (args[1]) {
case ListExp(funcArgs): case ListExp(funcArgs):
[ [
// TODO optional arguments, default values
for (funcArg in funcArgs) for (funcArg in funcArgs)
{ {
name: switch (funcArg) { name: switch (funcArg) {
@@ -82,7 +88,11 @@ class FieldForms {
default: default:
throw '$funcArg should be a symbol or typed symbol for a function argument'; throw '$funcArg should be a symbol or typed symbol for a function argument';
}, },
type: null type: switch (funcArg) {
case TypedExp(type, _):
Helpers.parseTypePath(type);
default: null;
}
} }
]; ];
case CallExp(_, _): case CallExp(_, _):
@@ -90,7 +100,10 @@ class FieldForms {
default: default:
throw '${args[1]} should be an argument list'; throw '${args[1]} should be an argument list';
}, },
ret: null, ret: switch (args[0]) {
case TypedExp(type, _): Helpers.parseTypePath(type);
default: null;
},
expr: { expr: {
pos: Context.currentPos(), pos: Context.currentPos(),
expr: EReturn(convert(CallExp(Symbol("begin"), args.slice(2)))) expr: EReturn(convert(CallExp(Symbol("begin"), args.slice(2))))

View File

@@ -3,6 +3,8 @@ package kiss;
import haxe.macro.Expr; import haxe.macro.Expr;
import haxe.macro.Context; import haxe.macro.Context;
using StringTools;
class Helpers { class Helpers {
public static function withPos(e:ExprDef):Expr { public static function withPos(e:ExprDef):Expr {
return { return {
@@ -10,4 +12,35 @@ class Helpers {
expr: e expr: e
}; };
} }
static function startsWithUpperCase(s:String) {
return s.charAt(0) == s.charAt(0).toUpperCase();
}
// TODO this doesn't parse generic typeparams yet
public static function parseTypePath(path:String):ComplexType {
var parts:List<String> = path.split(".");
var uppercaseParts:List<Bool> = parts.map(startsWithUpperCase);
for (isUpcase in uppercaseParts.slice(0, -2)) {
if (isUpcase) {
throw 'Type path $path should only have capitalized type and subtype';
}
}
var lastIsCap = uppercaseParts[-1];
var penultIsCap = uppercaseParts[-2];
return TPath(if (lastIsCap && penultIsCap) {
{
sub: parts[-1],
name: parts[-2],
pack: parts.slice(0, -2)
};
} else if (lastIsCap) {
{
name: parts[-1],
pack: parts.slice(0, -1)
};
} else {
throw 'Type path $path should end with a capitalized type';
});
}
} }

View File

@@ -127,4 +127,8 @@ class BasicTestCase extends Test {
seasonsGreetings += "ho "; seasonsGreetings += "ho ";
})); }));
} }
function testTypedDefvar() {
Assert.equals(8, BasicTestCase.myInt);
}
} }

View File

@@ -63,12 +63,14 @@
(defvar myIf7 (if "string" true false)) (defvar myIf7 (if "string" true false))
(defvar myIf8 (if "" true false)) (defvar myIf8 (if "" true false))
(defvar :Int myInt 8)
(defmacrofun doTwiceInt [intOp] (defmacrofun doTwiceInt [intOp]
,intOp ,intOp
,intOp) ,intOp)
// I think this causes doTwiceInt's runtime function to be typed as requiring Quote<Int> and returning Int // I think this causes doTwiceInt's runtime function to be typed as requiring Quote<Int> and returning Int
(defun incrementTwice [:Int val] (defun :Int incrementTwice [:Int val]
(doTwiceInt ++val)) (doTwiceInt ++val))
(defmacrofun doTwiceString [stringOp] (defmacrofun doTwiceString [stringOp]