Skip to content

Commit e22e7ec

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 7d876d1 commit e22e7ec

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
@@ -527,6 +527,56 @@ impl TyImplSelfOrTrait {
527527
implementing_for.type_id,
528528
)?;
529529

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

sway-error/src/error.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,12 +1103,18 @@ pub enum CompileError {
11031103
max_num: u64,
11041104
span: Span,
11051105
},
1106-
#[error("Coherence violation: only traits defined in this crate can be implemented for external types.")]
1106+
#[error("Coherence violation: only traits defined in this package can be implemented for external types.")]
11071107
IncoherentImplDueToOrphanRule {
11081108
trait_name: String,
11091109
type_name: String,
11101110
span: Span,
11111111
},
1112+
#[error("Coherence violation: inherent implementations are not allowed for external types.")]
1113+
InherentImplForExternalType {
1114+
type_name: String,
1115+
span: Span,
1116+
type_definition_span: Option<Span>,
1117+
},
11121118
}
11131119

11141120
impl std::convert::From<TypeError> for CompileError {
@@ -1345,6 +1351,7 @@ impl Spanned for CompileError {
13451351
MaxNumOfPanicExpressionsReached { span, .. } => span.clone(),
13461352
MaxNumOfPanickingCallsReached { span, .. } => span.clone(),
13471353
IncoherentImplDueToOrphanRule { span, .. } => span.clone(),
1354+
InherentImplForExternalType { span, .. } => span.clone(),
13481355
}
13491356
}
13501357
}
@@ -3179,22 +3186,22 @@ impl ToDiagnostic for CompileError {
31793186
IncoherentImplDueToOrphanRule { trait_name, type_name, span } => Diagnostic {
31803187
reason: Some(Reason::new(
31813188
code(1),
3182-
"coherence violation: only traits defined in this module can be implemented for external types".into()
3189+
"coherence violation: only traits defined in this package can be implemented for external types".into()
31833190
)),
31843191
issue: Issue::error(
31853192
source_engine,
31863193
span.clone(),
31873194
format!(
3188-
"cannot implement `{trait_name}` for `{type_name}`: both originate outside this module"
3195+
"cannot implement `{trait_name}` for `{type_name}`: both originate outside this package"
31893196
),
31903197
),
31913198
hints: vec![],
31923199
help: {
31933200
let help = vec![
3194-
"only traits defined in this module can be implemented for external types".to_string(),
3201+
"only traits defined in this package can be implemented for external types".to_string(),
31953202
Diagnostic::help_empty_line(),
31963203
format!(
3197-
"move this impl into the module that defines `{type_name}`"
3204+
"move this impl into the package that defines `{type_name}`"
31983205
),
31993206
format!(
32003207
"or define and use a local trait instead of `{trait_name}` to avoid the orphan rule"
@@ -3203,6 +3210,31 @@ impl ToDiagnostic for CompileError {
32033210
help
32043211
},
32053212
},
3213+
InherentImplForExternalType { type_name, span, type_definition_span } => Diagnostic {
3214+
reason: Some(Reason::new(
3215+
code(1),
3216+
"coherence violation: inherent implementations must be defined in the type's defining package".into()
3217+
)),
3218+
issue: Issue::error(
3219+
source_engine,
3220+
span.clone(),
3221+
format!(
3222+
"cannot define inherent implementation for `{type_name}`: type is defined in a different package"
3223+
),
3224+
),
3225+
hints: match type_definition_span.clone() {
3226+
Some(def_span) => vec![Hint::info(
3227+
source_engine,
3228+
def_span,
3229+
format!("Type `{type_name}` is defined here."),
3230+
)],
3231+
None => vec![],
3232+
},
3233+
help: vec![
3234+
"move this impl into the package that defines the type".to_string(),
3235+
"or define and use a local trait instead to avoid the orphan rule".to_string(),
3236+
],
3237+
},
32063238
ABIDuplicateName { span, other_span: other, is_attribute } => Diagnostic {
32073239
reason: Some(Reason::new(code(1), "Duplicated name found for renamed ABI type".into())),
32083240
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 "impl_self_overlap_lib::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 `impl_self_overlap_lib::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)