def test_regression1_note_with_no_range() -> None: file_info = FileInfo( file_path="dummy.pytch", source_code="""\ let foo = let bar = 3 baz bar """, ) error = Error( file_info=file_info, code=ErrorCode.UNBOUND_NAME, severity=Severity.ERROR, message="I couldn't find a binding...", range=Range( start=Position(line=2, character=2), end=Position(line=2, character=5) ), notes=[ Note(file_info=file_info, message="Did you mean 'map' (a builtin)?"), Note( file_info=file_info, message="Did you mean 'bar', defined here?", range=Range( start=Position(line=1, character=6), end=Position(line=1, character=9), ), ), ], ) lines = lines_to_string(get_error_lines(error, ascii=True)) print(lines) assert ( lines == """\ UNBOUND_NAME[2000] in dummy.pytch, line 3, character 3: Error: I couldn't find a binding... +---------------------------------------------------+ | dummy.pytch | 1 | let foo = | 2 | let bar = 3 | | ^~~ Note: Did you mean 'bar', defined here? | 3 | baz | | ^~~ Error: I couldn't find a binding... | 4 | bar | +---------------------------------------------------+ | Note: Did you mean 'map' (a builtin)? | +---------------------------------------------------+ """ )
def function_application_infer( env: Env, ctx: TypingContext, ty: Ty, function_call_expr: FunctionCallExpr) -> Tuple[Env, TypingContext, Ty]: """The function-application relation ⇒⇒, discussed in Dunfield 2013.""" n_callee = function_call_expr.n_callee assert n_callee is not None, "should have been checked by parser" n_argument_list = function_call_expr.n_argument_list assert n_argument_list is not None, "should have been checked by parser" arguments = n_argument_list.arguments assert arguments is not None, "should have been checked by parser" if isinstance(ty, FunctionTy): if len(arguments) != len(ty.domain): if len(arguments) < len(ty.domain): error_code = ErrorCode.TOO_FEW_ARGUMENTS elif len(arguments) > len(ty.domain): error_code = ErrorCode.TOO_MANY_ARGUMENTS else: raise AssertionError("neither too many nor too few arguments") actual_num_arguments = count(len(arguments), "argument", "arguments") expected_num_arguments = count(len(ty.domain), "argument", "arguments") env = env.add_error( Error( file_info=env.file_info, code=error_code, severity=Severity.ERROR, message=( f"I was expecting you to pass {expected_num_arguments} " + f"here, but you passed {actual_num_arguments} instead." ), range=env.get_range_for_node(n_argument_list), notes=[ Note( file_info=env.file_info, message=(f"This is the function being called. " + f"It takes {expected_num_arguments}."), range=env.get_range_for_node(n_callee), ) ], )) for argument, argument_ty in zip(arguments, ty.domain): n_expr = argument.n_expr assert n_expr is not None, "should have been checked by parser" (env, ctx, _reason) = check(env, ctx, expr=n_expr, ty=argument_ty) return (env, ctx, ty.codomain) elif isinstance(ty, ExistentialTyVar): raise NotImplementedError() else: assert ( False ), f"Unexpected function_application_infer type: {ty.__class__.__name__}"
def test_binder_error() -> None: file_info = FileInfo( file_path="<stdin>", source_code="""\ let foo = let bar = 3 baz """, ) (syntax_tree, errors) = get_syntax_tree(file_info) assert not errors bindation = bind(file_info=file_info, syntax_tree=syntax_tree, global_scope=GLOBAL_SCOPE) assert bindation.errors == [ Error( file_info=file_info, code=ErrorCode.UNBOUND_NAME, severity=Severity.ERROR, message=("I couldn't find a binding in the current scope " + "with the name 'baz'."), range=Range(start=Position(line=2, character=2), end=Position(line=2, character=5)), notes=[ Note( file_info=file_info, message="Did you mean 'map' (a builtin)?", range=None, ), Note( file_info=file_info, message="Did you mean 'bar', defined here?", range=Range( start=Position(line=1, character=6), end=Position(line=1, character=9), ), ), ], ) ]
def test_diagnostics_across_multiple_files() -> None: file_info_1 = FileInfo( file_path="dummy1.pytch", source_code="""dummy1 line1 dummy1 line2 """, ) file_info_2 = FileInfo( file_path="dummy2.pytch", source_code="""dummy2 line1 dummy2 line2 """, ) error = Error( file_info=file_info_1, code=ErrorCode.NOT_A_REAL_ERROR, severity=Severity.ERROR, message="Look into this", range=Range( start=Position(line=0, character=7), end=Position(line=0, character=12) ), notes=[ Note( file_info=file_info_2, message="This is an additional point of interest", range=Range( start=Position(line=0, character=0), end=Position(line=0, character=5), ), ) ], ) lines = lines_to_string(get_error_lines(error, ascii=True)) print(lines) assert ( lines == """\ NOT_A_REAL_ERROR[9001] in dummy1.pytch, line 1, character 8: Error: Look into this +-----------------------------------------------------+ | dummy1.pytch | 1 | dummy1 line1 | | ^~~~~ Error: Look into this | 2 | dummy1 line2 | +-----------------------------------------------------+ | dummy2.pytch | 1 | dummy2 line1 | | ^~~~~ Note: This is an additional point of interest | 2 | dummy2 line2 | +-----------------------------------------------------+ """ )
def test_print_error() -> None: file_info = FileInfo( file_path="dummy.pytch", source_code="""line1 line2 line3 line4 """, ) error = Error( file_info=file_info, code=ErrorCode.NOT_A_REAL_ERROR, severity=Severity.ERROR, message="Look into this", range=Range( start=Position(line=1, character=3), end=Position(line=2, character=2) ), notes=[ Note( file_info=file_info, message="This is an additional point of interest", range=Range( start=Position(line=0, character=0), end=Position(line=0, character=5), ), ) ], ) lines = lines_to_string(get_error_lines(error, ascii=True)) print(lines) assert ( lines == """\ NOT_A_REAL_ERROR[9001] in dummy.pytch, line 2, character 4: Error: Look into this +-----------------------------------------------------+ | dummy.pytch | 1 | line1 | | ^~~~~ Note: This is an additional point of interest | 2 | line2 | | ^~~~ | 3 | line3 | | ~ Error: Look into this | 4 | line4 | +-----------------------------------------------------+ """ )
def check(env: Env, ctx: TypingContext, expr: Expr, ty: Ty) -> Tuple[Env, TypingContext, Optional[Reason]]: if isinstance(expr, LetExpr): # The typing rule for let-bindings is # # Ψ ⊢ e ⇒ A Ψ, x:A ⊢ e' ⇐ C # --------------------------- let # Ψ ⊢ let x = e in e' ⇐ C n_pattern = expr.n_pattern if n_pattern is None: return (env, ctx, InvalidSyntaxReason()) n_value = expr.n_value if n_value is None: return (env, ctx, InvalidSyntaxReason()) (env, ctx, value_ty) = infer(env, ctx, n_value) if not isinstance(n_pattern, VariablePattern): raise NotImplementedError( "TODO: patterns other than VariablePattern not supported") ctx = ctx.add_pattern_ty(n_pattern, value_ty) if tys_equal(value_ty, VOID_TY): env = env.add_error( Error( file_info=env.file_info, code=ErrorCode.CANNOT_BIND_TO_VOID, severity=Severity.ERROR, message= (f"This expression has type {ctx.ty_to_string(VOID_TY)}, " + "so it cannot be bound to a variable."), range=env.get_range_for_node(n_value), notes=[ Note( file_info=env.file_info, message="This is the variable it's being bound to.", range=env.get_range_for_node(n_pattern), ) ], )) n_next = expr.n_body if n_next is None: return (env, ctx, InvalidSyntaxReason()) return check(env, ctx, expr=n_next, ty=ty) elif isinstance(expr, DefExpr): # The typing rule for let-bindings is # # Ψ ⊢ e ⇒ A Ψ, x:A ⊢ e' ⇐ C # --------------------------- let # Ψ ⊢ let x = e in e' ⇐ C # # Note that we have to adapt this for function definitions by also # using the rule for typing lambdas. n_name = expr.n_name if n_name is None: return (env, ctx, InvalidSyntaxReason()) n_parameter_list = expr.n_parameter_list if n_parameter_list is None: return (env, ctx, InvalidSyntaxReason()) parameters = n_parameter_list.parameters if parameters is None: raise NotImplementedError( "TODO(missing): raise error for missing parameters") (env, ctx, function_ty) = infer_function_definition(env, ctx, expr) n_next = expr.n_next if n_next is None: raise NotImplementedError( "TODO(missing): raise error for missing function body") # parameters_with_tys: PVector[CtxElemExprHasTy] = PVector() # for n_argument, argument_ty in zip(parameters, ty.domain): # n_expr = n_argument.n_expr # if n_expr is None: # return (env, ctx, True) # parameters_with_tys = parameters_with_tys.append( # CtxElemExprHasTy(expr=n_expr, ty=argument_ty) # ) # ctx = ctx.add_elem(CtxElemExprsHaveTys(expr_tys=parameters_with_tys)) assert isinstance(n_name, VariablePattern) ctx = ctx.add_pattern_ty(n_name, function_ty) return check(env, ctx, expr=n_next, ty=ty) else: (env, ctx, actual_ty) = infer(env, ctx, expr=expr) (env, ctx, reason) = check_subtype(env, ctx, lhs=actual_ty, rhs=ty) if reason is not None: return (env, ctx, reason) else: env = env.add_error( Error( file_info=env.file_info, code=ErrorCode.INCOMPATIBLE_TYPES, severity=Severity.ERROR, message= (f"I was expecting this expression to have type {ctx.ty_to_string(ty)}, " + f"but it actually had type {ctx.ty_to_string(actual_ty)}." ), range=env.get_range_for_node(expr), notes=[], )) return (env, ctx, reason)
def test_binding_defs() -> None: file_info = FileInfo( file_path="<stdin>", source_code="""\ def foo(bar) => bar + baz bar """, ) (syntax_tree, errors) = get_syntax_tree(file_info) assert not errors bindation = bind(file_info=file_info, syntax_tree=syntax_tree, global_scope=GLOBAL_SCOPE) [containing_def] = Query(syntax_tree).find_instances(DefExpr) [_, bar_ident_definition] = Query(syntax_tree).find_instances(VariablePattern) [bar_ident_use, _, _] = Query(syntax_tree).find_instances(IdentifierExpr) assert bar_ident_use.t_identifier is not None assert bar_ident_use.t_identifier.text == "bar" assert bindation.get(bar_ident_use) == [bar_ident_definition] assert bindation.errors == [ Error( file_info=file_info, code=ErrorCode.UNBOUND_NAME, severity=Severity.ERROR, message=("I couldn't find a binding in the current scope " + "with the name 'baz'."), range=Range(start=Position(line=1, character=10), end=Position(line=1, character=13)), notes=[ Note( file_info=file_info, message="Did you mean 'map' (a builtin)?", range=None, ), Note( file_info=file_info, message="Did you mean 'bar', defined here?", range=Range( start=Position(line=0, character=8), end=Position(line=0, character=11), ), ), ], ), Error( file_info=file_info, code=ErrorCode.UNBOUND_NAME, severity=Severity.ERROR, message=("I couldn't find a binding in the current scope " + "with the name 'bar'."), range=Range(start=Position(line=2, character=0), end=Position(line=2, character=3)), notes=[ Note( file_info=file_info, message="Did you mean 'map' (a builtin)?", range=None, ) ], ), ]