experimental type aliases

This commit is contained in:
2023-06-29 14:07:10 -06:00
parent f6027cc00c
commit 66a6a4d396
7 changed files with 61 additions and 34 deletions

View File

@@ -186,7 +186,7 @@ class EmbeddedScript {
ret: null,
args: [
{
type: Helpers.parseComplexType("(()->Void)->Void", null),
type: Helpers.parseComplexType("(()->Void)->Void", k, null),
name: "c"
}
],
@@ -207,10 +207,10 @@ class EmbeddedScript {
name: "fork",
access: [APublic],
kind: FFun({
ret: Helpers.parseComplexType("Array<EmbeddedScript>", null),
ret: Helpers.parseComplexType("Array<EmbeddedScript>", k, null),
args: [
{
type: Helpers.parseComplexType("Array<Command>", null),
type: Helpers.parseComplexType("Array<Command>", k, null),
name: "commands"
}
],

View File

@@ -103,7 +103,7 @@ class FieldForms {
checkPrintFieldsCalledWarning(name, wholeExp, k);
var access = fieldAccess(formName, name, args[0]);
var type = Helpers.explicitType(args[0]);
var type = Helpers.explicitType(args[0], k);
k.addVarInScope(
{name: name, type: type},
false,

View File

@@ -49,8 +49,10 @@ class Helpers {
return s.charAt(0) == s.charAt(0).toUpperCase();
}
public static function parseTypePath(path:String, ?from:ReaderExp):TypePath {
return switch (parseComplexType(path, from)) {
public static function parseTypePath(path:String, k:KissState, ?from:ReaderExp):TypePath {
if (k.typeAliases.exists(path))
path = k.typeAliases[path];
return switch (parseComplexType(path, k, from)) {
case TPath(path):
path;
default:
@@ -63,7 +65,10 @@ class Helpers {
};
}
public static function parseComplexType(path:String, ?from:ReaderExp, mustResolve=false):ComplexType {
public static function parseComplexType(path:String, k:KissState, ?from:ReaderExp, mustResolve=false):ComplexType {
if (k.typeAliases.exists(path))
path = k.typeAliases[path];
// Trick Haxe into parsing it for us:
var typeCheckStr = 'var thing:$path;';
var errorMessage = 'Haxe could not parse a complex type from `$path` in `${typeCheckStr}`';
@@ -102,20 +107,22 @@ class Helpers {
}
}
public static function explicitTypeString(nameExp:ReaderExp):String {
public static function explicitTypeString(nameExp:ReaderExp, k:KissState):String {
return switch (nameExp.def) {
case MetaExp(_, innerExp):
explicitTypeString(innerExp);
explicitTypeString(innerExp, k);
case TypedExp(type, _) if (k.typeAliases.exists(type)):
k.typeAliases[type];
case TypedExp(type, _):
type;
default: null;
};
}
public static function explicitType(nameExp:ReaderExp):ComplexType {
var string = explicitTypeString(nameExp);
public static function explicitType(nameExp:ReaderExp, k:KissState):ComplexType {
var string = explicitTypeString(nameExp, k);
if (string == null) return null;
return Helpers.parseComplexType(string, nameExp);
return Helpers.parseComplexType(string, k, nameExp);
}
public static function varName(formName:String, nameExp:ReaderExp, nameType = "variable") {
@@ -129,17 +136,19 @@ class Helpers {
};
}
public static function makeTypeParam(param:ReaderExp, ?constraints:Array<ComplexType> = null):TypeParamDecl {
public static function makeTypeParam(param:ReaderExp, k:KissState, ?constraints:Array<ComplexType> = null):TypeParamDecl {
if (constraints == null) constraints = [];
switch (param.def) {
case Symbol(name):
if (k.typeAliases.exists(name))
name = k.typeAliases[name];
return {
name: name,
constraints: constraints
};
case TypedExp(type, param):
constraints.push(parseComplexType(type));
return makeTypeParam(param, constraints);
constraints.push(parseComplexType(type, k));
return makeTypeParam(param, k, constraints);
default:
throw KissError.fromExp(param, "expected <GenericTypeName> or :<Constraint> <GenericTypeName>");
}
@@ -152,7 +161,7 @@ class Helpers {
"";
};
var params = [for (p in typeParams) makeTypeParam(p)];
var params = [for (p in typeParams) makeTypeParam(p, k)];
var numArgs = 0;
// Once the &opt meta appears, all following arguments are optional until &rest
@@ -171,7 +180,7 @@ class Helpers {
throw KissError.fromExp(funcArg, "lambda does not support &rest arguments");
}
var typeOfRestArg = explicitTypeString(funcArg);
var typeOfRestArg = explicitTypeString(funcArg, k);
var isDynamicArray = switch (typeOfRestArg) {
case "Array<Dynamic>" | "kiss.List<Dynamic>" | "List<Dynamic>":
true;
@@ -219,7 +228,7 @@ class Helpers {
},
type: switch (funcArg.def) {
case TypedExp(type, _):
Helpers.parseComplexType(type, funcArg);
Helpers.parseComplexType(type, k, funcArg);
default: null;
},
opt: opt
@@ -269,7 +278,7 @@ class Helpers {
// But setting null arguments to default values is so common, and arguments are not settable references,
// so function args are not immutable.
return {
ret: if (name != null) Helpers.explicitType(name) else null,
ret: if (name != null) Helpers.explicitType(name, k) else null,
args: args,
expr: expr,
params: params
@@ -470,14 +479,14 @@ class Helpers {
}
// hscript.Interp is very finicky about some edge cases.
// This function handles them
private static function mapForInterp(expr:Expr):Expr {
private static function mapForInterp(expr:Expr, k:KissState):Expr {
return expr.map(subExp -> {
switch (subExp.expr) {
case ETry(e, catches):
catches = [for (c in catches) {
// hscript.Parser expects :Dynamic after the catch varname
{
type: Helpers.parseComplexType("Dynamic"),
type: Helpers.parseComplexType("Dynamic", k),
name: c.name,
expr: c.expr
};
@@ -486,14 +495,14 @@ class Helpers {
pos: subExp.pos,
expr: ETry(e, catches)
};
default: mapForInterp(subExp);
default: mapForInterp(subExp, k);
}
});
}
static var parser = new Parser();
static function compileTimeHScript(exp:ReaderExp, k:KissState) {
var hscriptExp = mapForInterp(k.forMacroEval().convert(exp));
var hscriptExp = mapForInterp(k.forMacroEval().convert(exp), k);
var code = hscriptExp.toString(); // tink_macro to the rescue
#if macrotest
Prelude.print("Compile-time hscript: " + code);

View File

@@ -51,6 +51,7 @@ typedef KissState = {
loadedFiles:Map<String, Null<ReaderExp>>,
callAliases:Map<String, ReaderExpDef>,
identAliases:Map<String, ReaderExpDef>,
typeAliases:Map<String, String>,
fieldList:Array<Field>,
// TODO This map was originally created to track whether the programmer wrote their own main function, but could also
// be used to allow macros to edit fields that were already defined (for instance, to decorate a function or add something
@@ -182,6 +183,7 @@ class Kiss {
/* concat used to live here as an alias but now it is in a macro that also
applies (the Array<Dynamic>) to the result */
],
typeAliases: new Map(),
fieldList: [],
fieldDict: new Map(),
loadingDirectory: "",

View File

@@ -654,17 +654,24 @@ class Macros {
// Having this floating out here is sketchy, but should work out fine because the variable is always re-set
// through the next function before being used in defalias or undefalias
var aliasMap:Map<String, ReaderExpDef> = null;
var aliasMap:Map<String, Dynamic> = null;
var extractString = false;
function getAliasName(k:KissState, nameExpWithMeta:ReaderExp, formName:String):String {
var error = KissError.fromExp(nameExpWithMeta, 'first argument to $formName should be &call [alias] or &ident [alias]');
var nameExp = switch (nameExpWithMeta.def) {
case MetaExp("call", nameExp):
extractString = false;
aliasMap = k.callAliases;
nameExp;
case MetaExp("ident", nameExp):
extractString = false;
aliasMap = k.identAliases;
nameExp;
case MetaExp("type", nameExp):
aliasMap = k.typeAliases;
extractString = true;
nameExp;
default:
throw error;
};
@@ -676,12 +683,21 @@ class Macros {
};
}
k.doc("defalias", 2, 2, "(defAlias <<&call or &ident> whenItsThis> <makeItThis>)");
k.doc("defalias", 2, 2, "(defAlias <<&call or &ident or &type> whenItsThis> <makeItThis>)");
macros["defalias"] = (wholeExp:ReaderExp, exps:Array<ReaderExp>, k:KissState) -> {
k.stateChanged = true;
var name = getAliasName(k, exps[0], "defAlias");
aliasMap[name] = exps[1].def;
var into = exps[1].def;
if (extractString)
aliasMap[name] = switch (into) {
case Symbol(typeName):
typeName;
default:
throw KissError.fromExp(wholeExp, "type alias must be of a plain symbol");
}
else
aliasMap[name] = into;
return null;
};
renameAndDeprecate("defalias", "defAlias");
@@ -774,7 +790,7 @@ class Macros {
var firstValue = bindingList.shift();
var b = wholeExp.expBuilder();
var firstNameSymbol = b.symbol(firstNameString);
var firstNameType = Helpers.explicitTypeString(firstName);
var firstNameType = Helpers.explicitTypeString(firstName, k);
var rejectionHandlerArgsAndBody = [];
var usingDefaultHandler = false;
@@ -1340,7 +1356,7 @@ class Macros {
var b = wholeExp.expBuilder();
var name = exps[0];
var nameString = Prelude.symbolNameValue(name, true, false);
var type = Helpers.explicitTypeString(name);
var type = Helpers.explicitTypeString(name, k);
var initialValue = exps[1];
var filename = if (savedVarFilename != null) {
if (!savedVarFilename.endsWith(".json"))

View File

@@ -359,7 +359,7 @@ class Main {
v;
}
#if macro
var type = Context.resolveType(Helpers.parseComplexType(theInterface), Context.currentPos());
var type = Context.resolveType(Helpers.parseComplexType(theInterface, Kiss.defaultKissState()), Context.currentPos());
switch (type) {
case TInst(classTypeRef, params):
var classType = classTypeRef.get();

View File

@@ -116,7 +116,7 @@ class SpecialForms {
default: k.convert(args[0]).toString();
};
ENew(Helpers.parseTypePath(classType, args[0]), args.slice(1).map(k.convert)).withMacroPosOf(wholeExp);
ENew(Helpers.parseTypePath(classType, k, args[0]), args.slice(1).map(k.convert)).withMacroPosOf(wholeExp);
};
k.doc("set", 2, 2, "(set <variable> <value>)");
@@ -162,7 +162,7 @@ class SpecialForms {
name: varName(nameExp),
type: switch (nameExp.def) {
case TypedExp(type, _):
Helpers.parseComplexType(type, nameExp);
Helpers.parseComplexType(type, k, nameExp);
default: null;
},
isFinal: isFinal && !k.hscript,
@@ -456,7 +456,7 @@ class SpecialForms {
};
if (pkg.length > 0)
type = pkg + "." + type;
ECheckType(k.convert(args[1]), Helpers.parseComplexType(type, wholeExp, !type.contains("<"))).withMacroPosOf(wholeExp);
ECheckType(k.convert(args[1]), Helpers.parseComplexType(type, k, wholeExp, !type.contains("<"))).withMacroPosOf(wholeExp);
};
k.doc("try", 1, null, "(try <thing> <catches...>)");
@@ -479,7 +479,7 @@ class SpecialForms {
},
type: switch (catchArgs[0].def) {
case ListExp([{pos: _, def: TypedExp(type, _)}]):
Helpers.parseComplexType(type, catchArgs[0]);
Helpers.parseComplexType(type, k, catchArgs[0]);
default: null;
},
expr: k.convert(CallExp(Symbol("begin").withPos(catchArgs[1].pos), catchArgs.slice(1)).withPos(catchArgs[1].pos))
@@ -539,7 +539,7 @@ class SpecialForms {
if (args.length > 1) {
switch (args[1].def) {
case Symbol(typePath):
t = Helpers.parseComplexType(typePath, wholeExp, !typePath.contains("<"));
t = Helpers.parseComplexType(typePath, k, wholeExp, !typePath.contains("<"));
default:
throw KissError.fromExp(wholeExp, 'second argument to cast should be a type path symbol');
}