Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes missing error on type unification. #5230

Merged
merged 5 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl TyImplTrait {

// Unify the "self" type param and the type that we are implementing for
handler.scope(|h| {
type_engine.unify(
type_engine.unify_with_self(
h,
engines,
implementing_for.type_id,
Expand Down Expand Up @@ -208,7 +208,7 @@ impl TyImplTrait {
// Unify the "self" type param from the abi declaration with
// the type that we are implementing for.
handler.scope(|h| {
type_engine.unify(
type_engine.unify_with_self(
h,
engines,
implementing_for.type_id,
Expand Down Expand Up @@ -348,7 +348,7 @@ impl TyImplTrait {

// Unify the "self" type param and the type that we are implementing for
handler.scope(|h| {
type_engine.unify(
type_engine.unify_with_self(
h,
engines,
implementing_for.type_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ fn unify_arguments_and_parameters(
for ((_, arg), param) in arguments.iter().zip(parameters.iter()) {
// unify the type of the argument with the type of the param
let unify_res = handler.scope(|handler| {
type_engine.unify(
type_engine.unify_with_generic(
handler,
engines,
arg.return_type,
Expand Down Expand Up @@ -574,12 +574,12 @@ pub(crate) fn monomorphize_method_application(

// unify method return type with current ctx.type_annotation().
handler.scope(|handler| {
type_engine.unify(
type_engine.unify_with_generic(
handler,
engines,
method.return_type.type_id,
ctx.type_annotation(),
&method.return_type.span(),
&call_path.span(),
"Function return type does not match up with local type annotation.",
None,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ fn type_check_field_arguments(
let ctx = ctx
.by_ref()
.with_help_text(UNIFY_STRUCT_FIELD_HELP_TEXT)
.with_type_annotation(struct_field.type_argument.type_id);
.with_type_annotation(struct_field.type_argument.type_id)
.with_unify_generic(true);
let value = match ty::TyExpression::type_check(handler, ctx, field.value.clone()) {
Ok(res) => res,
Err(_) => continue,
Expand Down Expand Up @@ -210,7 +211,7 @@ fn unify_field_arguments_and_struct_fields(
handler.scope(|handler| {
for struct_field in struct_fields.iter() {
if let Some(typed_field) = typed_fields.iter().find(|x| x.name == struct_field.name) {
type_engine.unify(
type_engine.unify_with_generic(
handler,
engines,
typed_field.value.return_type,
Expand Down
48 changes: 39 additions & 9 deletions sway-core/src/semantic_analysis/type_check_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub struct TypeCheckContext<'a> {
///
/// Assists type inference.
type_annotation: TypeId,
/// When true unify_with_type_annotation will use unify_with_generic instead of the default unify.
/// This ensures that expected generic types are unified to more specific received types.
unify_generic: bool,
/// While type-checking an `impl` (whether inherent or for a `trait`/`abi`) this represents the
/// type for which we are implementing. For example in `impl Foo {}` or `impl Trait for Foo
/// {}`, this represents the type ID of `Foo`.
Expand Down Expand Up @@ -102,6 +105,7 @@ impl<'a> TypeCheckContext<'a> {
namespace,
engines,
type_annotation: engines.te().insert(engines, TypeInfo::Unknown),
unify_generic: false,
self_type: None,
type_subst: TypeSubstMap::new(),
help_text: "",
Expand All @@ -126,6 +130,7 @@ impl<'a> TypeCheckContext<'a> {
TypeCheckContext {
namespace: self.namespace,
type_annotation: self.type_annotation,
unify_generic: self.unify_generic,
self_type: self.self_type,
type_subst: self.type_subst.clone(),
abi_mode: self.abi_mode.clone(),
Expand All @@ -144,6 +149,7 @@ impl<'a> TypeCheckContext<'a> {
TypeCheckContext {
namespace,
type_annotation: self.type_annotation,
unify_generic: self.unify_generic,
self_type: self.self_type,
type_subst: self.type_subst,
abi_mode: self.abi_mode,
Expand Down Expand Up @@ -190,6 +196,14 @@ impl<'a> TypeCheckContext<'a> {
}
}

/// Map this `TypeCheckContext` instance to a new one with the given type annotation.
pub(crate) fn with_unify_generic(self, unify_generic: bool) -> Self {
Self {
unify_generic,
..self
}
}

/// Map this `TypeCheckContext` instance to a new one with the given type subst.
pub(crate) fn with_type_subst(self, type_subst: &TypeSubstMap) -> Self {
Self {
Expand Down Expand Up @@ -267,6 +281,10 @@ impl<'a> TypeCheckContext<'a> {
self.type_annotation
}

pub(crate) fn unify_generic(&self) -> bool {
self.unify_generic
}

pub(crate) fn self_type(&self) -> Option<TypeId> {
self.self_type
}
Expand Down Expand Up @@ -328,15 +346,27 @@ impl<'a> TypeCheckContext<'a> {
/// Short-hand around `type_system::unify_`, where the `TypeCheckContext`
/// provides the type annotation and help text.
pub(crate) fn unify_with_type_annotation(&self, handler: &Handler, ty: TypeId, span: &Span) {
self.engines.te().unify(
handler,
self.engines(),
ty,
self.type_annotation(),
span,
self.help_text(),
None,
)
if self.unify_generic() {
self.engines.te().unify_with_generic(
handler,
self.engines(),
ty,
self.type_annotation(),
span,
self.help_text(),
None,
)
} else {
self.engines.te().unify(
handler,
self.engines(),
ty,
self.type_annotation(),
span,
self.help_text(),
None,
)
}
}

/// Short-hand for calling [Namespace::insert_symbol] with the `const_shadowing_mode` provided by
Expand Down
2 changes: 1 addition & 1 deletion sway-core/src/type_system/ast_elements/type_parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ impl TypeParameter {
type_id: sy_type_id,
..
}) => {
ctx.engines().te().unify(
ctx.engines().te().unify_with_generic(
handler,
ctx.engines(),
type_parameter.type_id,
Expand Down
90 changes: 88 additions & 2 deletions sway-core/src/type_system/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::{
use sway_error::{error::CompileError, type_error::TypeError};
use sway_types::span::Span;

use super::unify::unifier::UnifyKind;

#[derive(Debug, Default)]
pub struct TypeEngine {
pub(super) slab: ConcurrentSlab<TypeInfo>,
Expand Down Expand Up @@ -66,6 +68,64 @@ impl TypeEngine {
}
}

/// Make the types of `received` and `expected` equivalent (or produce an
/// error if there is a conflict between them).
///
/// More specifically, this function tries to make `received` equivalent to
/// `expected`.
#[allow(clippy::too_many_arguments)]
pub(crate) fn unify(
&self,
handler: &Handler,
engines: &Engines,
received: TypeId,
expected: TypeId,
span: &Span,
help_text: &str,
err_override: Option<CompileError>,
) {
self.unify_helper(
handler,
engines,
received,
expected,
span,
help_text,
err_override,
UnifyKind::Default,
);
}

/// Make the types of `received` and `expected` equivalent (or produce an
/// error if there is a conflict between them).
///
/// More specifically, this function tries to make `received` equivalent to
/// `expected`, except in cases where `received` has more type information
/// than `expected` (e.g. when `expected` is a self type and `received`
/// is not).
#[allow(clippy::too_many_arguments)]
pub(crate) fn unify_with_self(
&self,
handler: &Handler,
engines: &Engines,
received: TypeId,
expected: TypeId,
span: &Span,
help_text: &str,
err_override: Option<CompileError>,
) {
self.unify_helper(
handler,
engines,
received,
expected,
span,
help_text,
err_override,
UnifyKind::WithSelf,
);
}

/// Make the types of `received` and `expected` equivalent (or produce an
/// error if there is a conflict between them).
///
Expand All @@ -74,7 +134,7 @@ impl TypeEngine {
/// than `expected` (e.g. when `expected` is a generic type and `received`
/// is not).
#[allow(clippy::too_many_arguments)]
pub(crate) fn unify(
pub(crate) fn unify_with_generic(
&self,
handler: &Handler,
engines: &Engines,
Expand All @@ -83,6 +143,30 @@ impl TypeEngine {
span: &Span,
help_text: &str,
err_override: Option<CompileError>,
) {
self.unify_helper(
handler,
engines,
received,
expected,
span,
help_text,
err_override,
UnifyKind::WithGeneric,
);
}

#[allow(clippy::too_many_arguments)]
fn unify_helper(
&self,
handler: &Handler,
engines: &Engines,
received: TypeId,
expected: TypeId,
span: &Span,
help_text: &str,
err_override: Option<CompileError>,
unify_kind: UnifyKind,
) {
if !UnifyCheck::coercion(engines).check(received, expected) {
// create a "mismatched type" error unless the `err_override`
Expand All @@ -103,7 +187,9 @@ impl TypeEngine {
return;
}
let h = Handler::default();
Unifier::new(engines, help_text).unify(handler, received, expected, span);
let unifier = Unifier::new(engines, help_text, unify_kind);

unifier.unify(handler, received, expected, span);
match err_override {
Some(err_override) if h.has_errors() => {
handler.emit_err(err_override);
Expand Down
45 changes: 41 additions & 4 deletions sway-core/src/type_system/unify/unifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,45 @@ use crate::{engine_threading::*, language::ty, type_system::priv_prelude::*};

use super::occurs_check::OccursCheck;

pub(crate) enum UnifyKind {
/// Make the types of `received` and `expected` equivalent (or produce an
/// error if there is a conflict between them).
///
/// More specifically, this function tries to make `received` equivalent to
/// `expected`.
Default,
/// Make the types of `received` and `expected` equivalent (or produce an
/// error if there is a conflict between them).
///
/// More specifically, this function tries to make `received` equivalent to
/// `expected`, except in cases where `received` has more type information
/// than `expected` (e.g. when `expected` is a self type and `received`
/// is not).
WithSelf,
/// Make the types of `received` and `expected` equivalent (or produce an
/// error if there is a conflict between them).
///
/// More specifically, this function tries to make `received` equivalent to
/// `expected`, except in cases where `received` has more type information
/// than `expected` (e.g. when `expected` is a generic type and `received`
/// is not).
WithGeneric,
}

/// Helper struct to aid in type unification.
pub(crate) struct Unifier<'a> {
engines: &'a Engines,
help_text: String,
unify_kind: UnifyKind,
}

impl<'a> Unifier<'a> {
/// Creates a new [Unifier].
pub(crate) fn new(engines: &'a Engines, help_text: &str) -> Unifier<'a> {
pub(crate) fn new(engines: &'a Engines, help_text: &str, unify_kind: UnifyKind) -> Unifier<'a> {
Unifier {
engines,
help_text: help_text.to_string(),
unify_kind,
}
}

Expand Down Expand Up @@ -159,13 +186,23 @@ impl<'a> Unifier<'a> {
},
) if rn.as_str() == en.as_str() && rtc.eq(&etc, self.engines) => (),

(r @ UnknownGeneric { .. }, e) if !self.occurs_check(received, expected) => {
(r @ UnknownGeneric { .. }, e)
if !self.occurs_check(received, expected)
&& (matches!(self.unify_kind, UnifyKind::WithGeneric)
|| !matches!(
self.engines.te().get(expected),
TypeInfo::UnknownGeneric { .. }
)) =>
{
self.replace_received_with_expected(handler, received, expected, &r, e, span)
}
(r, e @ UnknownGeneric { .. }) if !self.occurs_check(expected, received) => {
(r, e @ UnknownGeneric { .. })
if !self.occurs_check(expected, received)
&& e.is_self_type()
&& matches!(self.unify_kind, UnifyKind::WithSelf) =>
{
self.replace_expected_with_received(handler, received, expected, r, &e, span)
}

// Type aliases and the types they encapsulate coerce to each other.
(Alias { ty, .. }, _) => self.unify(handler, ty.type_id, expected, span),
(_, Alias { ty, .. }) => self.unify(handler, received, ty.type_id, span),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[package]]
name = "generic_and_associated_type"
source = "member"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
license = "Apache-2.0"
name = "generic_and_associated_type"
entry = "main.sw"
implicit-std = false
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Loading
Loading