Replace the Dynamic type with the "Any" trait

The Any trait is a trait implemented by all objects automatically, and
replaces all use of the Dynamic type. This means that Inko is now
statically typed, instead of being gradually typed.

The Any type is introduced as it can be cast to any other type. Using
"Object" to represent something that could be anything does not work, as
objects can't be casted to other objects. Any being implemented by all
objects does allow one to cast it to an object.

Argument types must now be known, meaning you either have to specify a
default value, and explicit type, or both. In other words, this is no
longer valid:

    def foo(number) { }

For closures and lambdas the compiler still tries to infer the argument
types based on how these blocks are used.

The behaviour of omitting method return types also changed. Prior to
this commit, leaving out a return type would result in it being inferred
as Dynamic. Starting with this commit, omitting a method's return type
results in:

1. The compiler inferring the return type as Nil
2. The compiler inserting a `return` at the end of the method, unless a
   `return` is already present

Take for example this method:

    def init {
      @Number = 10
    }

Prior to this commit, the return type of `init` would be `Dynamic` and
the value returned would be `10` (an `Integer`). In this commit this is
changed so that `init` is inferred to return `Nil`, and the actual
return value is also `Nil` (not `10`). This matches how dynamic return
types were used in the standard library so far: to signal we don't
really care about the return value.

For closures and lambdas, the compiler still tries to infer the return
type based on the body; defaulting to Nil if no other type could be
determined.

== Reasons for moving to static typing

Inko was gradually typed for a few reasons. First, when I started
working on Inko I was leaning more towards making it a dynamic language
similar to Ruby or Python. As I spent more time working on it, I
realised I was starting to prefer a statically typed language more and
more.

I also felt that gradual typing would strike a balance between the rapid
prototyping capabilities of a dynamic language, and the safety of a
statically typed language. But in practise I found this not to be the
case. For example, in dynamic languages (e.g. Ruby) I spend a lot of
time jumping between running the code, and fixing silly errors such as
the use of undefined local variables; something a statically typed
language could easily detect.

Inko using statically typed code as a baseline (instead of dynamically
typed code) also made using dynamic types frustrating. Take this code
for example:

    def add(a, b) {
      a + b
    }

    let result = add(10, 20)

Here `a` and `b` are dynamically typed, so is the return type. Now
imagine we want to print the results:

    import std::stdio::stdout

    def add(a, b) {
      a + b
    }

    let result = add(10, 20)

    stdout.print(result)

This will fail to compile, as `stdout.print` expects a `ToString`, but
`result` is a `Dynamic` and the compiler does not know if the runtime
type implements `ToString`. This inevitably leads to a lot of cast
expressions like so:

    import std::stdio::stdout

    def add(a, b) {
      a + b
    }

    let result = add(10, 20)

    stdout.print(result as Integer)

If we're going to require developers to cast dynamic types almost every
time they use them, they won't be all that useful. And if they're not
useful, we should just get rid of them.

This doesn't mean gradual typing is a bad idea. In fact, I think it's a
great way of making a dynamically typed language more safe. TypeScript
is a good example: it takes JavaScript (dynamically typed), and adds
static typing on top. But supporting gradual typing in a statically
typed language just doesn't bring any benefits, and makes certain
optimisations more difficult or even impossible. And thus dynamic typing
is no more.

Fixes #194
parent 177d616a
Pipeline #150627748 failed with stages
in 94 minutes and 15 seconds
......@@ -74,6 +74,7 @@ require 'inkoc/pass/track_module'
require 'inkoc/pass/add_implicit_import_symbols'
require 'inkoc/pass/compile_imported_modules'
require 'inkoc/pass/desugar_object'
require 'inkoc/pass/desugar_method'
require 'inkoc/pass/define_import_types'
require 'inkoc/pass/define_this_module_type'
require 'inkoc/pass/define_type'
......
......@@ -24,10 +24,6 @@ module Inkoc
name == Config::SELF_TYPE
end
def dynamic_type?
name == Config::DYNAMIC_TYPE
end
def never_type?
name == Config::NEVER_TYPE
end
......
......@@ -7,10 +7,10 @@ module Inkoc
include Predicates
include Inspect
attr_reader :name, :arguments, :type_parameters, :returns, :throws,
:method_bounds, :body, :location
attr_reader :name, :arguments, :type_parameters, :throws, :method_bounds,
:body, :location
attr_accessor :static
attr_accessor :static, :returns
# name - The name of the method.
# args - The arguments of the method.
......
......@@ -83,10 +83,6 @@ module Inkoc
false
end
def dynamic_type?
false
end
def never_type?
false
end
......@@ -122,6 +118,10 @@ module Inkoc
def trait_implementation?
false
end
def cast?
false
end
end
end
end
......@@ -40,14 +40,16 @@ module Inkoc
end
def throw_type
if expression.throw?
expression.type
elsif expression.send?
expression.block_type&.throw_type
elsif expression.identifier?
compare_with = expression.cast? ? expression.expression : expression
if compare_with.throw?
compare_with.type
elsif compare_with.send?
compare_with.block_type&.throw_type
elsif compare_with.identifier?
# The identifier might be a local variable, in which case "block_type"
# is not set.
expression.block_type&.throw_type
compare_with.block_type&.throw_type
end
end
end
......
......@@ -18,6 +18,10 @@ module Inkoc
def visitor_method
:on_type_cast
end
def cast?
true
end
end
end
end
......@@ -35,8 +35,6 @@ module Inkoc
case name.name
when Config::SELF_TYPE
late_binding? ? :on_self_type_with_late_binding : :on_self_type
when Config::DYNAMIC_TYPE
:on_dynamic_type
when Config::NEVER_TYPE
:on_never_type
else
......
......@@ -7,6 +7,7 @@ module Inkoc
Pass::PathToSource,
Pass::SourceToAst,
Pass::DesugarObject,
Pass::DesugarMethod,
Pass::DefineModuleType,
Pass::TrackModule,
Pass::InsertImplicitImports,
......
......@@ -42,10 +42,10 @@ module Inkoc
BYTE_ARRAY_CONST = 'ByteArray'
ARRAY_TYPE_PARAMETER = 'T'
OPTIONAL_CONST = 'Optional'
ANY_TRAIT_CONST = 'Any'
MODULE_TYPE = 'Module'
SELF_TYPE = 'Self'
DYNAMIC_TYPE = 'Dynamic'
NEVER_TYPE = 'Never'
MODULES_ATTRIBUTE = 'Modules'
......@@ -78,7 +78,6 @@ module Inkoc
RAW_INSTRUCTION_RECEIVER,
SELF_TYPE,
NEVER_TYPE,
DYNAMIC_TYPE
]
).freeze
......
......@@ -392,5 +392,12 @@ module Inkoc
location
)
end
def argument_type_missing(location)
error(
'You must provide an explicit type or default value for this argument',
location
)
end
end
end
......@@ -61,6 +61,8 @@ module Inkoc
if node.returns
type.return_type = define_type_instance(node.returns, scope)
else
type.return_type = new_any_type
end
if node.throws
......@@ -121,9 +123,7 @@ module Inkoc
def on_send(node, scope)
node.receiver_type = source = type_of_receiver(node, scope)
if source.dynamic?
send_to_dynamic_type(node, scope)
elsif source.error?
if source.error?
source
elsif source.optional?
send_to_optional_type(node, source, scope)
......@@ -132,12 +132,6 @@ module Inkoc
end
end
def send_to_dynamic_type(node, scope)
define_types(node.arguments, scope)
TypeSystem::Dynamic.new
end
def send_to_optional_type(node, source, scope)
nil_type = typedb.nil_type
rtype = send_to_known_type(node, source, scope)
......@@ -436,7 +430,7 @@ module Inkoc
def on_try_with_else(node, scope)
try_type = node.expression.type
throw_type = node.throw_type || TypeSystem::Dynamic.new
throw_type = node.throw_type || new_any_type
node.else_block_type = TypeSystem::Block.new(
name: Config::ELSE_BLOCK_NAME,
......@@ -516,7 +510,8 @@ module Inkoc
)
end
block_type = TypeSystem::Block.closure(typedb.block_type)
block_type = TypeSystem::Block
.closure(typedb.block_type, return_type: new_any_type)
new_scope = TypeScope
.new(type, block_type, @module, locals: node.body.locals)
......@@ -539,7 +534,9 @@ module Inkoc
# implementation. This ensures that a Self type refers to the type
# that the trait is implemented for, instead of referring to the type of
# the outer scope.
impl_block = TypeSystem::Block.closure(typedb.block_type)
impl_block = TypeSystem::Block
.closure(typedb.block_type, return_type: new_any_type)
impl_scope = TypeScope
.new(object, impl_block, @module, locals: node.body.locals)
......@@ -654,7 +651,9 @@ module Inkoc
end
def on_block(node, scope, expected_block = nil)
block_type = TypeSystem::Block.closure(typedb.block_type)
block_type = TypeSystem::Block
.closure(typedb.block_type, return_type: new_any_type)
locals = node.body.locals
new_scope = TypeScope.new(
......@@ -672,7 +671,9 @@ module Inkoc
end
def on_lambda(node, scope, expected_block = nil)
block_type = TypeSystem::Block.lambda(typedb.block_type)
block_type = TypeSystem::Block
.lambda(typedb.block_type, return_type: new_any_type)
new_scope = TypeScope.new(
@module.type,
block_type,
......@@ -901,7 +902,7 @@ module Inkoc
if name.string?
object.lookup_attribute(name.value).type
else
TypeSystem::Dynamic.new
new_any_type
end
end
alias on_raw_get_attribute_in_self on_raw_get_attribute
......@@ -1085,7 +1086,7 @@ module Inkoc
end
def on_raw_run_block(*)
TypeSystem::Dynamic.new
new_any_type
end
def on_raw_get_string_prototype(*)
......@@ -1144,7 +1145,7 @@ module Inkoc
end
def on_raw_time_system(*)
typedb.new_array_of_type(TypeSystem::Dynamic.new)
typedb.new_array_of_type(new_any_type)
end
def on_raw_string_to_upper(*)
......@@ -1156,7 +1157,7 @@ module Inkoc
end
def on_raw_string_to_byte_array(*)
TypeSystem::Dynamic.new
typedb.byte_array_type.new_instance
end
def on_raw_string_size(*)
......@@ -1199,8 +1200,8 @@ module Inkoc
node.arguments.fetch(1).type
end
def on_raw_process_receive_message(*)
TypeSystem::Dynamic.new
def on_raw_process_receive_message(node, *)
new_any_type
end
def on_raw_process_current(node, _)
......@@ -1222,7 +1223,7 @@ module Inkoc
if name.string?
object.lookup_attribute(name.value).type
else
TypeSystem::Dynamic.new
new_any_type
end
end
......@@ -1231,7 +1232,7 @@ module Inkoc
end
def on_raw_get_attribute_names(*)
typedb.new_array_of_type(typedb.string_type.new_instance)
new_any_type
end
def on_raw_attribute_exists(*)
......@@ -1283,7 +1284,7 @@ module Inkoc
end
def on_raw_file_time(*)
typedb.new_array_of_type(TypeSystem::Dynamic.new)
typedb.new_array_of_type(new_any_type)
end
def on_raw_directory_create(*)
......@@ -1335,13 +1336,13 @@ module Inkoc
end
def on_raw_stacktrace(*)
tuple = typedb.new_array_of_type(TypeSystem::Dynamic.new)
tuple = typedb.new_array_of_type(new_any_type)
typedb.new_array_of_type(tuple)
end
def on_raw_block_metadata(*)
TypeSystem::Dynamic.new
new_any_type
end
def on_raw_string_format_debug(*)
......@@ -1441,11 +1442,11 @@ module Inkoc
end
def on_raw_process_add_defer_to_caller(*)
TypeSystem::Block.closure(typedb.block_type)
TypeSystem::Block.closure(typedb.block_type, return_type: new_any_type)
end
def on_raw_set_default_panic_handler(*)
TypeSystem::Block.lambda(typedb.block_type)
TypeSystem::Block.lambda(typedb.block_type, return_type: new_any_type)
end
def on_raw_process_pin_thread(*)
......@@ -1469,7 +1470,7 @@ module Inkoc
end
def on_raw_function_call(*)
TypeSystem::Dynamic.new
new_any_type
end
def on_raw_pointer_attach(node, _)
......@@ -1477,11 +1478,11 @@ module Inkoc
end
def on_raw_pointer_read(*)
TypeSystem::Dynamic.new
new_any_type
end
def on_raw_pointer_write(*)
TypeSystem::Dynamic.new
new_any_type
end
def on_raw_pointer_from_address(node, _)
......@@ -1529,7 +1530,7 @@ module Inkoc
end
def on_raw_socket_receive_from(*)
typedb.new_array_of_type(TypeSystem::Dynamic.new)
typedb.new_array_of_type(new_any_type)
end
def on_raw_socket_send_to(*)
......@@ -1537,15 +1538,15 @@ module Inkoc
end
def on_raw_socket_address(*)
typedb.new_array_of_type(TypeSystem::Dynamic.new)
typedb.new_array_of_type(new_any_type)
end
def on_raw_socket_get_option(*)
TypeSystem::Dynamic.new
new_any_type
end
def on_raw_socket_set_option(*)
TypeSystem::Dynamic.new
new_any_type
end
def on_raw_socket_bind(*)
......@@ -1565,11 +1566,11 @@ module Inkoc
end
def on_raw_random_number(*)
TypeSystem::Dynamic.new
new_any_type
end
def on_raw_random_range(*)
TypeSystem::Dynamic.new
new_any_type
end
def on_raw_random_bytes(*)
......@@ -1687,14 +1688,15 @@ module Inkoc
end
def define_return_type(node, scope)
return unless node.returns
scope.block_type.infer_return_type = false
node.returns.late_binding = true
scope.block_type.return_type =
define_type_instance(node.returns, scope)
if node.returns
scope.block_type.infer_return_type = false
node.returns.late_binding = true
define_type_instance(node.returns, scope)
else
new_any_type
end
end
# Returns the type of an argument's default value, if any.
......@@ -1709,10 +1711,6 @@ module Inkoc
# Determines which type to use for an argument.
#
# Given the explicitly defined type (if any) and the type of the default
# value (if any) this method will determine which of the two should be
# used. If neither are given the Dynamic type is used.
#
# rubocop: disable Metrics/CyclomaticComplexity
# rubocop: disable Metrics/PerceivedComplexity
def determine_argument_type(
......@@ -1735,7 +1733,12 @@ module Inkoc
elsif value_type
value_type
else
default_type || TypeSystem::Dynamic.new
if default_type
default_type
else
diagnostics.argument_type_missing(node.location)
TypeSystem::Error.new
end
end
type.remap_using_method_bounds(block_type)
......
# frozen_string_literal: true
module Inkoc
module Pass
class DesugarMethod
include VisitorMethods
def initialize(mod, state)
@module = mod
@state = state
end
def run(ast)
process_node(ast)
[ast]
end
def on_body(node)
process_nodes(node.expressions)
end
def on_object(node)
on_body(node.body)
end
def on_trait(node)
on_body(node.body)
end
def on_trait_implementation(node)
on_body(node.body)
end
def on_reopen_object(node)
on_body(node.body)
end
def on_required_method(node)
return if node.returns
node.returns = AST::TypeName.new(
AST::Constant.new(Config::NIL_CONST, node.location),
[],
node.location
)
end
def on_method(node)
return if node.returns
last_expr = node.body.expressions.last
ret_loc = last_expr&.location || node.body.location
node.returns = AST::TypeName.new(
AST::Constant.new(Config::NIL_CONST, node.location),
[],
node.location
)
return if last_expr&.return?
# These two "if" statements are just hacks to prevent desugaring from
# messing up tail-recursive methods. We'll need a better solution in the
# self-hosting compiler.
if last_expr&.send? &&
last_expr.name == node.name &&
(last_expr.receiver.nil? || last_expr.receiver.self?)
return
end
if last_expr&.identifier? && last_expr.name == node.name
return
end
# Insert a `return` at the end, unless the last expression already is a
# `return` of some sort..
node.body.expressions.push(AST::Return.new(nil, ret_loc))
end
end
end
end
......@@ -17,7 +17,30 @@ module Inkoc
end
def on_body(node)
process_nodes(node.expressions)
impls = []
node.expressions.each do |expr|
next unless expr.object?
on_object(expr)
impls.push(add_any_implementation(expr)) if @module.import_bootstrap?
end
node.expressions.concat(impls)
end
def add_any_implementation(node)
AST::TraitImplementation.new(
AST::TypeName.new(
AST::Constant.new(Config::ANY_TRAIT_CONST, node.location),
[],
node.location
),
AST::Constant.new(node.name, node.location),
AST::Body.new([], node.location),
node.location
)
end
def on_object(node)
......
......@@ -582,7 +582,7 @@ module Inkoc
end
def local_exists(symbol, body, location)
register = body.register(TypeSystem::Dynamic.new)
register = body.register(typedb.boolean_type.new_instance)
body.instruct(:LocalExists, register, symbol, location)
end
......@@ -1633,8 +1633,9 @@ module Inkoc
)
block_reg = body.register(block_type)
ret_reg = body.register(return_type)
args_reg = body
.register(typedb.new_array_of_type(TypeSystem::Dynamic.new))
args_reg = body.register(
typedb.new_array_of_type(@module.lookup_any_type.new_instance)
)
name_reg = set_string(name, body, loc)
alt_name_reg = set_string(Config::UNKNOWN_MESSAGE_MESSAGE, body, loc)
......
......@@ -94,10 +94,6 @@ module Inkoc
@registers.allocate(type)
end
def register_dynamic
register(TypeSystem::Dynamic.new)
end
def instruct(*args)
instruction = current_block.instruct(*args)
instruction.register