From 49ee777c507748dafdb9cef2157813169ed0596f Mon Sep 17 00:00:00 2001 From: K Lange Date: Thu, 11 Mar 2021 20:44:39 +0900 Subject: [PATCH] Type annotations. --- src/chunk.h | 3 +- src/compiler.c | 57 ++++++++++++++++++++++++++++++--- src/memory.c | 1 + src/obj_dict.c | 1 + src/obj_function.c | 16 +++++++++ src/obj_list.c | 4 +-- src/obj_typing.c | 2 +- src/object.c | 1 + src/object.h | 2 ++ src/opcodes.h | 1 + src/scanner.c | 2 +- src/scanner.h | 1 + src/vm.c | 9 ++++++ test/testAnnotations.krk | 24 ++++++++++++++ test/testAnnotations.krk.expect | 9 ++++++ 15 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 test/testAnnotations.krk create mode 100644 test/testAnnotations.krk.expect diff --git a/src/chunk.h b/src/chunk.h index dec0041..8ed37d0 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -66,7 +66,8 @@ typedef enum { OP_INVOKE_CONTAINS, OP_BREAKPOINT, /* NEVER output this instruction in the compiler or bad things can happen */ OP_YIELD, - /* current highest: 44 */ + OP_ANNOTATE, + /* current highest: 45 */ OP_CALL = 64, OP_CLASS, diff --git a/src/compiler.c b/src/compiler.c index 180e3c0..c4c31cd 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -130,6 +130,8 @@ typedef struct Compiler { size_t localNameCapacity; struct IndexWithNext * properties; + struct Compiler * enclosed; + size_t annotationCount; } Compiler; typedef struct ClassCompiler { @@ -203,6 +205,8 @@ static void initCompiler(Compiler * compiler, FunctionType type) { compiler->loopLocalCount = 0; compiler->localNameCapacity = 0; compiler->properties = NULL; + compiler->enclosed = NULL; + compiler->annotationCount = 0; if (type != TYPE_MODULE) { current->function->name = krk_copyString(parser.previous.start, parser.previous.length); @@ -925,6 +929,26 @@ static void doUpvalues(Compiler * compiler, KrkFunction * function) { } } +static void typeHint(KrkToken name) { + current->enclosing->enclosed = current; + current = current->enclosing; + + if (!current->enclosed->annotationCount) { + KrkToken dictOf = syntheticToken("dictOf"); + size_t ind = identifierConstant(&dictOf); + EMIT_CONSTANT_OP(OP_GET_GLOBAL, ind); + } + + current->enclosed->annotationCount++; + + /* Emit name */ + emitConstant(OBJECT_VAL(krk_copyString(name.start, name.length))); + expression(); + + current = current->enclosed; + current->enclosing->enclosed = NULL; +} + static void function(FunctionType type, size_t blockWidth) { Compiler compiler; initCompiler(&compiler, type); @@ -944,6 +968,11 @@ static void function(FunctionType type, size_t blockWidth) { if (!isMethod(type)) { error("Invalid use of `self` as a function paramenter."); } + if (check(TOKEN_COLON)) { + KrkToken name = parser.previous; + match(TOKEN_COLON); + typeHint(name); + } continue; } if (match(TOKEN_ASTERISK) || check(TOKEN_POW)) { @@ -965,6 +994,11 @@ static void function(FunctionType type, size_t blockWidth) { /* Collect a name, specifically "args" or "kwargs" are commont */ ssize_t paramConstant = parseVariable("Expect parameter name."); defineVariable(paramConstant); + if (check(TOKEN_COLON)) { + KrkToken name = parser.previous; + match(TOKEN_COLON); + typeHint(name); + } /* Make that a valid local for this function */ size_t myLocal = current->localCount - 1; EMIT_CONSTANT_OP(OP_GET_LOCAL, myLocal); @@ -991,6 +1025,11 @@ static void function(FunctionType type, size_t blockWidth) { } ssize_t paramConstant = parseVariable("Expect parameter name."); defineVariable(paramConstant); + if (check(TOKEN_COLON)) { + KrkToken name = parser.previous; + match(TOKEN_COLON); + typeHint(name); + } if (match(TOKEN_EQUAL)) { /* * We inline default arguments by checking if they are equal @@ -1026,13 +1065,24 @@ static void function(FunctionType type, size_t blockWidth) { stopEatingWhitespace(); consume(TOKEN_RIGHT_PAREN, "Expected end of parameter list."); + if (match(TOKEN_ARROW)) { + typeHint(syntheticToken("return")); + } + consume(TOKEN_COLON, "Expected colon after function signature."); block(blockWidth,"def"); - KrkFunction * function = endCompiler(); + if (compiler.annotationCount) { + EMIT_CONSTANT_OP(OP_CALL,compiler.annotationCount * 2); + } size_t ind = krk_addConstant(currentChunk(), OBJECT_VAL(function)); EMIT_CONSTANT_OP(OP_CLOSURE, ind); doUpvalues(&compiler, function); + + if (compiler.annotationCount) { + emitByte(OP_ANNOTATE); + } + freeCompiler(&compiler); } @@ -1082,14 +1132,12 @@ static void method(size_t blockWidth) { } #define ATTACH_PROPERTY(propName,how,propValue) do { \ - emitBytes(OP_DUP, 0); \ KrkToken val_tok = syntheticToken(propValue); \ size_t val_ind = identifierConstant(&val_tok); \ EMIT_CONSTANT_OP(how, val_ind); \ KrkToken name_tok = syntheticToken(propName); \ size_t name_ind = identifierConstant(&name_tok); \ - EMIT_CONSTANT_OP(OP_SET_PROPERTY, name_ind); \ - emitByte(OP_POP); \ + EMIT_CONSTANT_OP(OP_CLASS_PROPERTY, name_ind); \ } while (0) static KrkToken classDeclaration() { @@ -2902,6 +2950,7 @@ KrkFunction * krk_compile(const char * src, char * fileName) { void krk_markCompilerRoots() { Compiler * compiler = current; while (compiler != NULL) { + if (compiler->enclosed) krk_markObject((KrkObj*)compiler->enclosed->function); krk_markObject((KrkObj*)compiler->function); compiler = compiler->enclosing; } diff --git a/src/memory.c b/src/memory.c index 773eaef..586f605 100644 --- a/src/memory.c +++ b/src/memory.c @@ -132,6 +132,7 @@ static void blackenObject(KrkObj * object) { for (size_t i = 0; i < closure->upvalueCount; ++i) { krk_markObject((KrkObj*)closure->upvalues[i]); } + krk_markValue(closure->annotations); break; } case OBJ_FUNCTION: { diff --git a/src/obj_dict.c b/src/obj_dict.c index 1524942..bc30008 100644 --- a/src/obj_dict.c +++ b/src/obj_dict.c @@ -386,6 +386,7 @@ void _createAndBind_dictClass(void) { BIND_METHOD(dict,setdefault); BIND_METHOD(dict,update); krk_defineNative(&dict->methods, ".__str__", FUNC_NAME(dict,__repr__)); + krk_defineNative(&dict->methods, ".__class_getitem__", KrkGenericAlias); krk_finalizeClass(dict); KRK_DOC(dict, "Mapping of arbitrary keys to values."); diff --git a/src/obj_function.c b/src/obj_function.c index 6fb0854..4c2d7b2 100644 --- a/src/obj_function.c +++ b/src/obj_function.c @@ -139,6 +139,12 @@ KRK_METHOD(function,__args__,{ return OBJECT_VAL(tuple); }) +KRK_METHOD(function,__annotations__,{ + METHOD_TAKES_NONE(); + if (!IS_CLOSURE(self)) return NONE_VAL(); + return AS_CLOSURE(self)->annotations; +}) + #undef CURRENT_CTYPE #define CURRENT_CTYPE KrkFunction* @@ -223,6 +229,12 @@ KRK_METHOD(method,__doc__,{ return FUNC_NAME(function,__doc__)(1,(KrkValue[]){OBJECT_VAL(self->method)},0); }) +KRK_METHOD(method,__annotations__,{ + METHOD_TAKES_NONE(); + return FUNC_NAME(function,__annotations__)(1,(KrkValue[]){OBJECT_VAL(self->method)},0); +}) + + KRK_FUNC(staticmethod,{ FUNCTION_TAKES_EXACTLY(1); CHECK_ARG(0,CLOSURE,KrkClosure*,method); @@ -232,6 +244,7 @@ KRK_FUNC(staticmethod,{ for (size_t i = 0; i < method->upvalueCount; ++i) { AS_CLOSURE(krk_peek(0))->upvalues[i] = method->upvalues[i]; } + AS_CLOSURE(krk_peek(0))->annotations = method->annotations; AS_CLOSURE(krk_peek(0))->isStaticMethod = 1; return krk_pop(); }) @@ -245,6 +258,7 @@ KRK_FUNC(classmethod,{ for (size_t i = 0; i < method->upvalueCount; ++i) { AS_CLOSURE(krk_peek(0))->upvalues[i] = method->upvalues[i]; } + AS_CLOSURE(krk_peek(0))->annotations = method->annotations; AS_CLOSURE(krk_peek(0))->isClassMethod = 1; return krk_pop(); }) @@ -266,6 +280,7 @@ void _createAndBind_functionClass(void) { BIND_PROP(function,__qualname__); BIND_PROP(function,__file__); BIND_PROP(function,__args__); + BIND_PROP(function,__annotations__); krk_defineNative(&function->methods, ".__repr__", FUNC_NAME(function,__str__)); krk_finalizeClass(function); @@ -277,6 +292,7 @@ void _createAndBind_functionClass(void) { BIND_PROP(method,__qualname__); BIND_PROP(method,__file__); BIND_PROP(method,__args__); + BIND_PROP(method,__annotations__); krk_defineNative(&method->methods, ".__repr__", FUNC_NAME(method,__str__)); krk_finalizeClass(method); diff --git a/src/obj_list.c b/src/obj_list.c index 1f2c217..dba95e6 100644 --- a/src/obj_list.c +++ b/src/obj_list.c @@ -472,8 +472,6 @@ static KrkValue _reversed(int argc, KrkValue argv[], int hasKw) { return krk_pop(); } -extern NativeFn GenericAlias; - _noexport void _createAndBind_listClass(void) { KrkClass * list = ADD_BASE_CLASS(vm.baseClasses->listClass, "list", vm.baseClasses->objectClass); @@ -505,7 +503,7 @@ void _createAndBind_listClass(void) { BIND_METHOD(list,sort); krk_defineNative(&list->methods, ".__delitem__", FUNC_NAME(list,pop)); krk_defineNative(&list->methods, ".__str__", FUNC_NAME(list,__repr__)); - krk_defineNative(&list->methods, ".__class_getitem__", GenericAlias); + krk_defineNative(&list->methods, ".__class_getitem__", KrkGenericAlias); krk_finalizeClass(list); KRK_DOC(list, "Mutable sequence of arbitrary values."); diff --git a/src/obj_typing.c b/src/obj_typing.c index d0f3727..cb7b754 100644 --- a/src/obj_typing.c +++ b/src/obj_typing.c @@ -64,4 +64,4 @@ KRK_FUNC(__class_getitem__,{ return finishStringBuilder(&sb); }) -NativeFn GenericAlias = FUNC_NAME(krk,__class_getitem__); +NativeFn KrkGenericAlias = FUNC_NAME(krk,__class_getitem__); diff --git a/src/object.c b/src/object.c index e9fe720..f1728a6 100644 --- a/src/object.c +++ b/src/object.c @@ -266,6 +266,7 @@ KrkClosure * krk_newClosure(KrkFunction * function) { closure->function = function; closure->upvalues = upvalues; closure->upvalueCount = function->upvalueCount; + closure->annotations = krk_dict_of(0,NULL,0); return closure; } diff --git a/src/object.h b/src/object.h index bf6e6c2..3c65a1a 100644 --- a/src/object.h +++ b/src/object.h @@ -141,6 +141,7 @@ typedef struct { size_t upvalueCount; unsigned char isClassMethod:1; unsigned char isStaticMethod:1; + KrkValue annotations; } KrkClosure; typedef void (*KrkCleanupCallback)(struct KrkInstance *); @@ -363,6 +364,7 @@ extern uint32_t krk_unicodeCodepoint(KrkString * string, size_t index); extern size_t krk_codepointToBytes(krk_integer_type value, unsigned char * out); /* Internal stuff. */ +extern NativeFn KrkGenericAlias; extern KrkFunction * krk_newFunction(void); extern KrkNative * krk_newNative(NativeFn function, const char * name, int type); extern KrkClosure * krk_newClosure(KrkFunction * function); diff --git a/src/opcodes.h b/src/opcodes.h index 9a50fc3..21364f6 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -40,6 +40,7 @@ SIMPLE(OP_CLEANUP_WITH) SIMPLE(OP_FILTER_EXCEPT) SIMPLE(OP_BREAKPOINT) SIMPLE(OP_YIELD) +SIMPLE(OP_ANNOTATE) CONSTANT(OP_DEFINE_GLOBAL,(void)0) CONSTANT(OP_CONSTANT,(void)0) CONSTANT(OP_GET_GLOBAL,(void)0) diff --git a/src/scanner.c b/src/scanner.c index 0da5a29..e24864d 100644 --- a/src/scanner.c +++ b/src/scanner.c @@ -353,7 +353,7 @@ KrkToken krk_scanToken() { case '=': return makeToken(match('=') ? TOKEN_EQUAL_EQUAL : TOKEN_EQUAL); case '<': return makeToken(match('=') ? TOKEN_LESS_EQUAL : (match('<') ? (match('=') ? TOKEN_LSHIFT_EQUAL : TOKEN_LEFT_SHIFT) : TOKEN_LESS)); case '>': return makeToken(match('=') ? TOKEN_GREATER_EQUAL : (match('>') ? (match('=') ? TOKEN_RSHIFT_EQUAL : TOKEN_RIGHT_SHIFT) : TOKEN_GREATER)); - case '-': return makeToken(match('=') ? TOKEN_MINUS_EQUAL : (match('-') ? TOKEN_MINUS_MINUS : TOKEN_MINUS)); + case '-': return makeToken(match('=') ? TOKEN_MINUS_EQUAL : (match('-') ? TOKEN_MINUS_MINUS : (match('>') ? TOKEN_ARROW : TOKEN_MINUS))); case '+': return makeToken(match('=') ? TOKEN_PLUS_EQUAL : (match('+') ? TOKEN_PLUS_PLUS : TOKEN_PLUS)); case '^': return makeToken(match('=') ? TOKEN_CARET_EQUAL : TOKEN_CARET); case '|': return makeToken(match('=') ? TOKEN_PIPE_EQUAL : TOKEN_PIPE); diff --git a/src/scanner.h b/src/scanner.h index d3dd92c..d64deef 100644 --- a/src/scanner.h +++ b/src/scanner.h @@ -28,6 +28,7 @@ typedef enum { TOKEN_BANG, TOKEN_GREATER, TOKEN_LESS, + TOKEN_ARROW, /* -> */ /* Comparisons */ TOKEN_GREATER_EQUAL, diff --git a/src/vm.c b/src/vm.c index 82962c4..3dab1ac 100644 --- a/src/vm.c +++ b/src/vm.c @@ -2202,6 +2202,15 @@ _resumeHook: (void)0; /* Do NOT restore the stack */ return result; } + case OP_ANNOTATE: { + if (!IS_CLOSURE(krk_peek(0))) { + krk_runtimeError(vm.exceptions->typeError, "Can not annotate '%s'.", krk_typeName(krk_peek(0))); + goto _finishException; + } + krk_swap(1); + AS_CLOSURE(krk_peek(1))->annotations = krk_pop(); + break; + } /* * Two-byte operands diff --git a/test/testAnnotations.krk b/test/testAnnotations.krk new file mode 100644 index 0000000..b37d493 --- /dev/null +++ b/test/testAnnotations.krk @@ -0,0 +1,24 @@ +def foo(a: int, b: float) -> list: + print("I am a function.") + return [42] + +print(foo(1,2.0)) +print(foo.__annotations__) + + +# Okay, now let's try some methods + +class Foo: + def amethod(self: Foo, anint: int, adict: dict[str,object]) -> None: + print("I am a method taking a dict.") + + @staticmethod + def astatic(abool: bool, astr: str): + print("I return a Foo? Amazing!") + return Foo() + +print(Foo().amethod.__annotations__) +print(Foo().amethod(1,{'two': 3})) + +print(Foo.astatic.__annotations__) +print(isinstance(Foo.astatic(True,"yes"),Foo)) diff --git a/test/testAnnotations.krk.expect b/test/testAnnotations.krk.expect new file mode 100644 index 0000000..d84f0fc --- /dev/null +++ b/test/testAnnotations.krk.expect @@ -0,0 +1,9 @@ +I am a function. +[42] +{'return': , 'a': , 'b': } +{'return': None, 'anint': , 'self': , 'adict': 'dict[str,object]'} +I am a method taking a dict. +None +{'astr': , 'abool': } +I return a Foo? Amazing! +True