3262: Fix handling of const patterns r=matklad a=flodiebold

E.g. in `match x { None => ... }`, `None` is a path pattern (resolving to the
option variant), not a binding. To determine this, we need to try to resolve the
name during lowering. This isn't too hard since we already need to resolve names
for macro expansion anyway (though maybe a bit hacky).

Fixes #1618.

Co-authored-by: Florian Diebold <florian.diebold@freiheit.com>
This commit is contained in:
bors[bot] 2020-02-22 00:27:09 +00:00 committed by GitHub
commit baf832d6d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 6 deletions

View File

@ -174,6 +174,7 @@ fn child_source(&self, db: &impl DefDatabase) -> InFile<ArenaMap<Self::ChildId,
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum StructKind {
Tuple,
Record,

View File

@ -15,6 +15,7 @@
use test_utils::tested_by;
use crate::{
adt::StructKind,
body::{Body, BodySourceMap, Expander, PatPtr},
builtin_type::{BuiltinFloat, BuiltinInt},
db::DefDatabase,
@ -22,11 +23,12 @@
ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp,
MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
},
item_scope::BuiltinShadowMode,
path::GenericArgs,
path::Path,
type_ref::{Mutability, TypeRef},
ConstLoc, ContainerId, DefWithBodyId, EnumLoc, FunctionLoc, Intern, ModuleDefId, StaticLoc,
StructLoc, TraitLoc, TypeAliasLoc, UnionLoc,
AdtId, ConstLoc, ContainerId, DefWithBodyId, EnumLoc, FunctionLoc, Intern, ModuleDefId,
StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc,
};
pub(super) fn lower(
@ -571,7 +573,37 @@ fn collect_pat(&mut self, pat: ast::Pat) -> PatId {
let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
let annotation = BindingAnnotation::new(bp.is_mutable(), bp.is_ref());
let subpat = bp.pat().map(|subpat| self.collect_pat(subpat));
Pat::Bind { name, mode: annotation, subpat }
if annotation == BindingAnnotation::Unannotated && subpat.is_none() {
// This could also be a single-segment path pattern. To
// decide that, we need to try resolving the name.
let (resolved, _) = self.expander.crate_def_map.resolve_path(
self.db,
self.expander.module.local_id,
&name.clone().into(),
BuiltinShadowMode::Other,
);
match resolved.take_values() {
Some(ModuleDefId::ConstId(_)) => Pat::Path(name.into()),
Some(ModuleDefId::EnumVariantId(_)) => {
// this is only really valid for unit variants, but
// shadowing other enum variants with a pattern is
// an error anyway
Pat::Path(name.into())
}
Some(ModuleDefId::AdtId(AdtId::StructId(s)))
if self.db.struct_data(s).variant_data.kind() != StructKind::Record =>
{
// Funnily enough, record structs *can* be shadowed
// by pattern bindings (but unit or tuple structs
// can't).
Pat::Path(name.into())
}
// shadowing statics is an error as well, so we just ignore that case here
_ => Pat::Bind { name, mode: annotation, subpat },
}
} else {
Pat::Bind { name, mode: annotation, subpat }
}
}
ast::Pat::TupleStructPat(p) => {
let path = p.path().and_then(|path| self.expander.parse_path(path));

View File

@ -48,7 +48,7 @@ pub enum Literal {
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Expr {
/// This is produced if syntax tree does not have a required expression piece.
/// This is produced if the syntax tree does not have a required expression piece.
Missing,
Path(Path),
If {

View File

@ -189,7 +189,9 @@ pub(super) fn infer_pat(
};
// use a new type variable if we got Ty::Unknown here
let ty = self.insert_type_vars_shallow(ty);
self.unify(&ty, expected);
if !self.unify(&ty, expected) {
// FIXME record mismatch, we need to change the type of self.type_mismatches for that
}
let ty = self.resolve_ty_as_possible(ty);
self.write_pat_ty(pat, ty.clone());
ty

View File

@ -1,4 +1,4 @@
use super::infer;
use super::{infer, infer_with_mismatches};
use insta::assert_snapshot;
use test_utils::covers;
@ -236,3 +236,47 @@ fn test(a1: A<u32>, o: Option<u64>) {
"###
);
}
#[test]
fn infer_const_pattern() {
assert_snapshot!(
infer_with_mismatches(r#"
enum Option<T> { None }
use Option::None;
struct Foo;
const Bar: usize = 1;
fn test() {
let a: Option<u32> = None;
let b: Option<i64> = match a {
None => None,
};
let _: () = match () { Foo => Foo }; // Expected mismatch
let _: () = match () { Bar => Bar }; // Expected mismatch
}
"#, true),
@r###"
[74; 75) '1': usize
[88; 310) '{ ...atch }': ()
[98; 99) 'a': Option<u32>
[115; 119) 'None': Option<u32>
[129; 130) 'b': Option<i64>
[146; 183) 'match ... }': Option<i64>
[152; 153) 'a': Option<u32>
[164; 168) 'None': Option<u32>
[172; 176) 'None': Option<i64>
[193; 194) '_': ()
[201; 224) 'match ... Foo }': Foo
[207; 209) '()': ()
[212; 215) 'Foo': Foo
[219; 222) 'Foo': Foo
[255; 256) '_': ()
[263; 286) 'match ... Bar }': usize
[269; 271) '()': ()
[274; 277) 'Bar': usize
[281; 284) 'Bar': usize
[201; 224): expected (), got Foo
[263; 286): expected (), got usize
"###
);
}