Skip to content

Commit 70828a8

Browse files
committed
Trait coherence: disallow inherent impls on external types
This improves the trait coherence checks for impl self case, and polishes a bit of the wording on the existing diagnostics. * Add CompileError::InherentImplForExternalType and diagnostics. Reason/issue/help now refer to "package" to match coherence scope * Enforce package-level check in impl-self type checking Reject inherent impls for external nominal types (struct/enum) Temporary whitelist: allow inherent impls on std::storage::StorageKey<_> This is a workaround so current code that uses this pattern keeps working.
1 parent 4f1af43 commit 70828a8

File tree

5 files changed

+100
-13
lines changed
  • sway-core/src/semantic_analysis/ast_node/declaration
  • sway-error/src
  • test/src/e2e_vm_tests/test_programs
    • should_fail/trait_coherence
    • should_pass/language/generic_transpose/src

5 files changed

+100
-13
lines changed

sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,56 @@ impl TyImplSelfOrTrait {
526526
implementing_for.type_id(),
527527
)?;
528528

529+
// Disallow inherent implementations for types defined outside the current package
530+
let current_pkg = ctx.namespace().current_package_ref();
531+
let is_external = match &*type_engine.get(implementing_for.type_id()) {
532+
TypeInfo::Struct(decl_id) => {
533+
let s = decl_engine.get_struct(decl_id);
534+
let pkg_name = s.call_path.prefixes.first().map(|p| p.as_str());
535+
match pkg_name {
536+
Some(name) => name != current_pkg.name().as_str(),
537+
None => false,
538+
}
539+
}
540+
TypeInfo::Enum(decl_id) => {
541+
let e = decl_engine.get_enum(decl_id);
542+
let pkg_name = e.call_path.prefixes.first().map(|p| p.as_str());
543+
match pkg_name {
544+
Some(name) => name != current_pkg.name().as_str(),
545+
None => false,
546+
}
547+
}
548+
_ => false,
549+
};
550+
551+
// Temporary workaround: allow inherent impls on `std::storage::StorageKey<_>`.
552+
let is_storage_key_in_std = match &*type_engine.get(implementing_for.type_id()) {
553+
TypeInfo::Struct(decl_id) => {
554+
let s = decl_engine.get_struct(decl_id);
555+
s.call_path.suffix.as_str() == "StorageKey"
556+
&& s.call_path.prefixes.len() >= 2
557+
&& s.call_path.prefixes[0].as_str() == "std"
558+
&& s.call_path.prefixes[1].as_str() == "storage"
559+
}
560+
_ => false,
561+
};
562+
563+
if is_external && !is_storage_key_in_std {
564+
let type_name = engines.help_out(implementing_for.type_id()).to_string();
565+
let type_definition_span = match &*type_engine.get(implementing_for.type_id()) {
566+
TypeInfo::Struct(decl_id) => {
567+
Some(decl_engine.get_struct(decl_id).span.clone())
568+
}
569+
TypeInfo::Enum(decl_id) => Some(decl_engine.get_enum(decl_id).span.clone()),
570+
_ => None,
571+
};
572+
return Err(handler.emit_err(CompileError::InherentImplForExternalType {
573+
type_name,
574+
span: implementing_for.span(),
575+
type_definition_span,
576+
}));
577+
}
578+
529579
implementing_for.type_id().check_type_parameter_bounds(
530580
handler,
531581
ctx.by_ref(),

sway-error/src/error.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,12 +1085,18 @@ pub enum CompileError {
10851085
},
10861086
#[error("This expression has type \"{argument_type}\", which does not implement \"std::marker::Error\". Panic expression arguments must implement \"Error\".")]
10871087
PanicExpressionArgumentIsNotError { argument_type: String, span: Span },
1088-
#[error("Coherence violation: only traits defined in this crate can be implemented for external types.")]
1088+
#[error("Coherence violation: only traits defined in this package can be implemented for external types.")]
10891089
IncoherentImplDueToOrphanRule {
10901090
trait_name: String,
10911091
type_name: String,
10921092
span: Span,
10931093
},
1094+
#[error("Coherence violation: inherent implementations are not allowed for external types.")]
1095+
InherentImplForExternalType {
1096+
type_name: String,
1097+
span: Span,
1098+
type_definition_span: Option<Span>,
1099+
},
10941100
}
10951101

10961102
impl std::convert::From<TypeError> for CompileError {
@@ -1325,6 +1331,7 @@ impl Spanned for CompileError {
13251331
} => enum_variant_name.span(),
13261332
PanicExpressionArgumentIsNotError { span, .. } => span.clone(),
13271333
IncoherentImplDueToOrphanRule { span, .. } => span.clone(),
1334+
InherentImplForExternalType { span, .. } => span.clone(),
13281335
}
13291336
}
13301337
}
@@ -3121,22 +3128,22 @@ impl ToDiagnostic for CompileError {
31213128
IncoherentImplDueToOrphanRule { trait_name, type_name, span } => Diagnostic {
31223129
reason: Some(Reason::new(
31233130
code(1),
3124-
"coherence violation: only traits defined in this module can be implemented for external types".into()
3131+
"coherence violation: only traits defined in this package can be implemented for external types".into()
31253132
)),
31263133
issue: Issue::error(
31273134
source_engine,
31283135
span.clone(),
31293136
format!(
3130-
"cannot implement `{trait_name}` for `{type_name}`: both originate outside this module"
3137+
"cannot implement `{trait_name}` for `{type_name}`: both originate outside this package"
31313138
),
31323139
),
31333140
hints: vec![],
31343141
help: {
31353142
let help = vec![
3136-
"only traits defined in this module can be implemented for external types".to_string(),
3143+
"only traits defined in this package can be implemented for external types".to_string(),
31373144
Diagnostic::help_empty_line(),
31383145
format!(
3139-
"move this impl into the module that defines `{type_name}`"
3146+
"move this impl into the package that defines `{type_name}`"
31403147
),
31413148
format!(
31423149
"or define and use a local trait instead of `{trait_name}` to avoid the orphan rule"
@@ -3145,6 +3152,31 @@ impl ToDiagnostic for CompileError {
31453152
help
31463153
},
31473154
},
3155+
InherentImplForExternalType { type_name, span, type_definition_span } => Diagnostic {
3156+
reason: Some(Reason::new(
3157+
code(1),
3158+
"coherence violation: inherent implementations must be defined in the type's defining package".into()
3159+
)),
3160+
issue: Issue::error(
3161+
source_engine,
3162+
span.clone(),
3163+
format!(
3164+
"cannot define inherent implementation for `{type_name}`: type is defined in a different package"
3165+
),
3166+
),
3167+
hints: match type_definition_span.clone() {
3168+
Some(def_span) => vec![Hint::info(
3169+
source_engine,
3170+
def_span,
3171+
format!("Type `{type_name}` is defined here."),
3172+
)],
3173+
None => vec![],
3174+
},
3175+
help: vec![
3176+
"move this impl into the package that defines the type".to_string(),
3177+
"or define and use a local trait instead to avoid the orphan rule".to_string(),
3178+
],
3179+
},
31483180
ABIDuplicateName { span, other_span: other, is_attribute } => Diagnostic {
31493181
reason: Some(Reason::new(code(1), "Duplicated name found for renamed ABI type".into())),
31503182
issue: Issue::error(
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
category = "fail"
22

3-
#check: $()Type contains duplicate declarations
4-
#check: $()Method "do_something" already declared in type "MyType".
3+
# Expect coherent error for inherent impl on external type.
4+
#check: $()coherence violation: inherent implementations must be defined in the type's defining package
5+
#check: $()cannot define inherent implementation for `MyType`

test/src/e2e_vm_tests/test_programs/should_fail/trait_coherence/orphan_rules/impl_orphan/stdout.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ output:
77
Building test/src/e2e_vm_tests/test_programs/should_fail/trait_coherence/orphan_rules/impl_orphan
88
Compiling library impl_orphan_lib (test/src/e2e_vm_tests/test_programs/should_fail/trait_coherence/orphan_rules/impl_orphan_lib)
99
Compiling library impl_orphan (test/src/e2e_vm_tests/test_programs/should_fail/trait_coherence/orphan_rules/impl_orphan)
10-
error: coherence violation: only traits defined in this module can be implemented for external types
10+
error: coherence violation: only traits defined in this package can be implemented for external types
1111
--> test/src/e2e_vm_tests/test_programs/should_fail/trait_coherence/orphan_rules/impl_orphan/src/lib.sw:4:1
1212
|
1313
...
1414
4 | impl Vec<bool> for bool {}
15-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot implement `Vec` for `bool`: both originate outside this module
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot implement `Vec` for `bool`: both originate outside this package
1616
|
17-
= help: only traits defined in this module can be implemented for external types
17+
= help: only traits defined in this package can be implemented for external types
1818
= help:
19-
= help: move this impl into the module that defines `bool`
19+
= help: move this impl into the package that defines `bool`
2020
= help: or define and use a local trait instead of `Vec` to avoid the orphan rule
2121
____
2222

test/src/e2e_vm_tests/test_programs/should_pass/language/generic_transpose/src/main.sw

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ impl<T, E> MyResult<T, E> {
1818
}
1919
}
2020

21-
impl<T, E> Option<MyResult<T, E>> {
22-
pub fn transpose(self) -> MyResult<Option<T>, E> {
21+
trait OptionTranspose<T, E> {
22+
fn transpose(self) -> MyResult<Option<T>, E>;
23+
}
24+
25+
impl<T, E> OptionTranspose<T, E> for Option<MyResult<T, E>> {
26+
fn transpose(self) -> MyResult<Option<T>, E> {
2327
match self {
2428
Some(MyResult::MyOk(x)) => MyResult::MyOk(Some(x)),
2529
Some(MyResult::MyErr(e)) => MyResult::MyErr(e),

0 commit comments

Comments
 (0)